From cf620856d33c5a822898de088a474fec82dd8953 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Wed, 9 Nov 2022 10:57:02 +0100 Subject: [PATCH 01/26] Add resource predicates For now it only handles `time_credits` and `time_receipts`. --- prusti-common/src/vir/to_viper.rs | 9 +++ prusti-contracts/prusti-contracts/src/lib.rs | 13 ++++ .../src/encoder/foldunfold/footprint.rs | 4 ++ .../mir/pure/interpreter/interpreter_poly.rs | 61 +++++++++---------- .../mir/pure/specifications/interface.rs | 8 +++ vir/defs/polymorphic/ast/common.rs | 26 ++++++++ vir/defs/polymorphic/ast/expr.rs | 43 ++++++++++++- vir/defs/polymorphic/ast/expr_transformers.rs | 52 ++++++++++++++++ vir/src/converter/polymorphic_to_legacy.rs | 7 +++ vir/src/converter/type_substitution.rs | 11 ++++ vir/src/legacy/ast/expr.rs | 19 +++++- vir/src/legacy/ast/expr_transformers.rs | 40 ++++++++++++ 12 files changed, 258 insertions(+), 35 deletions(-) diff --git a/prusti-common/src/vir/to_viper.rs b/prusti-common/src/vir/to_viper.rs index 10c1809a759..e0186773132 100644 --- a/prusti-common/src/vir/to_viper.rs +++ b/prusti-common/src/vir/to_viper.rs @@ -403,6 +403,15 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for Expr { perm.to_viper(context, ast), pos.to_viper(context, ast), ), + Expr::ResourceAccessPredicate(ref resource_name, ref amount, ref pos) => { + // Resource are encoded as a predicate with no arguments + ast.predicate_access_predicate_with_pos( + ast.predicate_access(&[], resource_name), + ast.perm_mul(amount.to_viper(context, ast), ast.full_perm()), + // ast.fractional_perm(amount.to_viper(context, ast), ast.int_lit(1)), + pos.to_viper(context, ast), + ) + } Expr::FieldAccessPredicate(ref loc, perm, ref pos) => ast .field_access_predicate_with_pos( loc.to_viper(context, ast), diff --git a/prusti-contracts/prusti-contracts/src/lib.rs b/prusti-contracts/prusti-contracts/src/lib.rs index 9c299eeb7ef..6abbc519564 100644 --- a/prusti-contracts/prusti-contracts/src/lib.rs +++ b/prusti-contracts/prusti-contracts/src/lib.rs @@ -338,4 +338,17 @@ pub fn snapshot_equality(_l: T, _r: T) -> bool { true } +/// This function is used to specify the number of time credits required in a +/// function specification. It defines the upper bound for its runtime. +pub fn time_credits(_n: usize) -> bool { + true +} + +/// This function is used to specify the number of time receipts ensured at the +/// end of the execution of a function in its specification. It defined the +/// lower bound for its runtime. +pub fn time_receipts(_n: usize) -> bool { + true +} + pub use private::*; diff --git a/prusti-viper/src/encoder/foldunfold/footprint.rs b/prusti-viper/src/encoder/foldunfold/footprint.rs index 6b801304989..c70add4935e 100644 --- a/prusti-viper/src/encoder/foldunfold/footprint.rs +++ b/prusti-viper/src/encoder/foldunfold/footprint.rs @@ -161,6 +161,10 @@ impl ExprFootprintGetter for vir::Expr { Some(perm).into_iter().collect() } + vir::Expr::ResourceAccessPredicate(_) => { + FxHashSet::default() + } + vir::Expr::MagicWand(_) => { // We don't track magic wands resources FxHashSet::default() diff --git a/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_poly.rs b/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_poly.rs index 4f270dbd146..4a1e95f60bc 100644 --- a/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_poly.rs +++ b/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_poly.rs @@ -425,33 +425,6 @@ impl<'p, 'v: 'p, 'tcx: 'v> BackwardMirInterpreter<'tcx> .with_span(span)?; match full_func_proc_name { - "prusti_contracts::old" => { - trace!("Encoding old expression {:?}", args[0]); - assert_eq!(args.len(), 1); - - // Return an error for unsupported old(..) types - - let encoded_rhs = self.mir_encoder.encode_old_expr( - vir::Expr::snap_app(encoded_args[0].clone()), - PRECONDITION_LABEL, - ); - let mut state = states[&target_block].clone(); - state.substitute_value(&encoded_lhs, encoded_rhs); - state - } - - "prusti_contracts::before_expiry" => { - trace!("Encoding before_expiry expression {:?}", args[0]); - assert_eq!(args.len(), 1); - let encoded_rhs = self.mir_encoder.encode_old_expr( - vir::Expr::snap_app(encoded_args[0].clone()), - WAND_LHS_LABEL, - ); - let mut state = states[&target_block].clone(); - state.substitute_value(&encoded_lhs, encoded_rhs); - state - } - "std::cmp::PartialEq::eq" | "core::cmp::PartialEq::eq" if self.encoder.has_structural_eq_impl( self.mir_encoder.get_operand_ty(&args[0]), @@ -575,12 +548,34 @@ impl<'p, 'v: 'p, 'tcx: 'v> BackwardMirInterpreter<'tcx> // Prusti-specific syntax // TODO: check we are in a spec function - "prusti_contracts::exists" - | "prusti_contracts::forall" - | "prusti_contracts::specification_entailment" - | "prusti_contracts::call_description" - | "prusti_contracts::snap" - | "prusti_contracts::snapshot_equality" => { + "prusti_contracts::old" => { + trace!("Encoding old expression {:?}", args[0]); + assert_eq!(args.len(), 1); + + // Return an error for unsupported old(..) types + + let encoded_rhs = self.mir_encoder.encode_old_expr( + vir::Expr::snap_app(encoded_args[0].clone()), + PRECONDITION_LABEL, + ); + let mut state = states[&target_block].clone(); + state.substitute_value(&encoded_lhs, encoded_rhs); + state + } + + "prusti_contracts::before_expiry" => { + trace!("Encoding before_expiry expression {:?}", args[0]); + assert_eq!(args.len(), 1); + let encoded_rhs = self.mir_encoder.encode_old_expr( + vir::Expr::snap_app(encoded_args[0].clone()), + WAND_LHS_LABEL, + ); + let mut state = states[&target_block].clone(); + state.substitute_value(&encoded_lhs, encoded_rhs); + state + } + + _ if full_func_proc_name.starts_with("prusti_contracts::") => { let expr = self.encoder.encode_prusti_operation( full_func_proc_name, span, diff --git a/prusti-viper/src/encoder/mir/pure/specifications/interface.rs b/prusti-viper/src/encoder/mir/pure/specifications/interface.rs index 5d3f3ae69c1..a4385034940 100644 --- a/prusti-viper/src/encoder/mir/pure/specifications/interface.rs +++ b/prusti-viper/src/encoder/mir/pure/specifications/interface.rs @@ -255,6 +255,14 @@ impl<'v, 'tcx: 'v> SpecificationEncoderInterface<'tcx> for crate::encoder::Encod vir_poly::Expr::snap_app(encoded_args[0].clone()), vir_poly::Expr::snap_app(encoded_args[1].clone()), )), + "prusti_contracts::time_credits" => Ok(vir_poly::Expr::resource_access_predicate( + vir_poly::ResourceType::TimeCredits, + encoded_args[0].clone(), + )), + "prusti_contracts::time_receipts" => Ok(vir_poly::Expr::resource_access_predicate( + vir_poly::ResourceType::TimeReceipts, + encoded_args[0].clone(), + )), _ => unimplemented!(), } } diff --git a/vir/defs/polymorphic/ast/common.rs b/vir/defs/polymorphic/ast/common.rs index 7e4fd4fe3d2..2e0b42e8f6f 100644 --- a/vir/defs/polymorphic/ast/common.rs +++ b/vir/defs/polymorphic/ast/common.rs @@ -502,6 +502,32 @@ impl Type { } } +#[derive( + Debug, Clone, serde::Serialize, serde::Deserialize, PartialOrd, Ord, PartialEq, Eq, Hash, +)] +pub enum ResourceType { + TimeCredits, + TimeReceipts, +} + +impl fmt::Display for ResourceType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ResourceType::TimeCredits => write!(f, "TimeCredits"), + ResourceType::TimeReceipts => write!(f, "TimeReceipts"), + } + } +} + +impl ResourceType { + pub fn encode_as_string(&self) -> String { + match self { + ResourceType::TimeCredits => "time_credits".to_string(), + ResourceType::TimeReceipts => "time_receipts".to_string(), + } + } +} + use crate::common::display; #[derive( diff --git a/vir/defs/polymorphic/ast/expr.rs b/vir/defs/polymorphic/ast/expr.rs index 12e3183dfaf..a485a9f2a3c 100644 --- a/vir/defs/polymorphic/ast/expr.rs +++ b/vir/defs/polymorphic/ast/expr.rs @@ -32,6 +32,8 @@ pub enum Expr { MagicWand(MagicWand), /// PredicateAccessPredicate: predicate_type, arg, permission amount PredicateAccessPredicate(PredicateAccessPredicate), + /// ResourceAccessPredicate: resource_type, amount + ResourceAccessPredicate(ResourceAccessPredicate), FieldAccessPredicate(FieldAccessPredicate), UnaryOp(UnaryOp), BinOp(BinOp), @@ -85,6 +87,9 @@ impl fmt::Display for Expr { Expr::PredicateAccessPredicate(predicate_access_predicate) => { predicate_access_predicate.fmt(f) } + Expr::ResourceAccessPredicate(resource_access_predicate) => { + resource_access_predicate.fmt(f) + } Expr::FieldAccessPredicate(field_access_predicate) => field_access_predicate.fmt(f), Expr::UnaryOp(unary_op) => unary_op.fmt(f), Expr::BinOp(bin_op) => bin_op.fmt(f), @@ -169,6 +174,7 @@ impl Expr { | Expr::LabelledOld(LabelledOld { position, .. }) | Expr::MagicWand(MagicWand { position, .. }) | Expr::PredicateAccessPredicate(PredicateAccessPredicate { position, .. }) + | Expr::ResourceAccessPredicate(ResourceAccessPredicate { position, .. }) | Expr::FieldAccessPredicate(FieldAccessPredicate { position, .. }) | Expr::UnaryOp(UnaryOp { position, .. }) | Expr::BinOp(BinOp { position, .. }) @@ -220,6 +226,7 @@ impl Expr { LabelledOld, MagicWand, PredicateAccessPredicate, + ResourceAccessPredicate, FieldAccessPredicate, UnaryOp, BinOp, @@ -268,6 +275,15 @@ impl Expr { }) } + pub fn resource_access_predicate(resource_type: ResourceType, amount: Expr) -> Self { + let pos = amount.pos(); + Expr::ResourceAccessPredicate(ResourceAccessPredicate { + resource_type, + amount: Box::new(amount), + position: pos, + }) + } + pub fn field_access_predicate(place: Expr, perm: PermAmount) -> Self { let pos = place.pos(); Expr::FieldAccessPredicate(FieldAccessPredicate { @@ -617,7 +633,10 @@ impl Expr { pub fn is_only_permissions(&self) -> bool { match self { - Expr::PredicateAccessPredicate(..) | Expr::FieldAccessPredicate(..) => true, + Expr::PredicateAccessPredicate(..) + // TODO check this not sure + | Expr::ResourceAccessPredicate(..) + | Expr::FieldAccessPredicate(..) => true, Expr::BinOp(BinOp { op_kind: BinaryOpKind::And, left, @@ -1048,6 +1067,7 @@ impl Expr { typ1 } Expr::ForAll(..) | Expr::Exists(..) => &Type::Bool, + Expr::ResourceAccessPredicate(..) => &Type::Bool, Expr::MagicWand(..) | Expr::PredicateAccessPredicate(..) | Expr::FieldAccessPredicate(..) @@ -1496,6 +1516,7 @@ impl Expr { match e { f @ Expr::PredicateAccessPredicate(..) => f, f @ Expr::FieldAccessPredicate(..) => f, + f @ Expr::ResourceAccessPredicate(..) => f, Expr::BinOp(BinOp { op_kind: BinaryOpKind::And, left, @@ -2059,6 +2080,26 @@ impl Hash for PredicateAccessPredicate { } } +#[derive( + Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, PartialOrd, Ord, +)] +pub struct ResourceAccessPredicate { + pub resource_type: ResourceType, + pub amount: Box, + pub position: Position, +} + +impl fmt::Display for ResourceAccessPredicate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "acc({}(), {}/1)", + self.resource_type.encode_as_string(), + self.amount + ) + } +} + #[derive(Debug, Clone, Eq, serde::Serialize, serde::Deserialize, PartialOrd, Ord)] pub struct FieldAccessPredicate { pub base: Box, diff --git a/vir/defs/polymorphic/ast/expr_transformers.rs b/vir/defs/polymorphic/ast/expr_transformers.rs index c6fe744b85f..a4e42b6a2ef 100644 --- a/vir/defs/polymorphic/ast/expr_transformers.rs +++ b/vir/defs/polymorphic/ast/expr_transformers.rs @@ -161,6 +161,19 @@ pub trait ExprFolder: Sized { }) } + fn fold_resource_access_predicate(&mut self, expr: ResourceAccessPredicate) -> Expr { + let ResourceAccessPredicate { + resource_type, + amount, + position, + } = expr; + Expr::ResourceAccessPredicate(ResourceAccessPredicate { + resource_type, + amount: self.fold_boxed(amount), + position, + }) + } + fn fold_field_access_predicate(&mut self, expr: FieldAccessPredicate) -> Expr { let FieldAccessPredicate { base, @@ -450,6 +463,9 @@ pub fn default_fold_expr(this: &mut T, e: Expr) -> Expr { Expr::PredicateAccessPredicate(predicate_access_predicate) => { this.fold_predicate_access_predicate(predicate_access_predicate) } + Expr::ResourceAccessPredicate(resource_access_predicate) => { + this.fold_resource_access_predicate(resource_access_predicate) + } Expr::FieldAccessPredicate(field_access_predicate) => { this.fold_field_access_predicate(field_access_predicate) } @@ -522,6 +538,10 @@ pub trait ExprWalker: Sized { let PredicateAccessPredicate { argument, .. } = expr; self.walk(argument); } + fn walk_resource_access_predicate(&mut self, expr: &ResourceAccessPredicate) { + let ResourceAccessPredicate { amount, .. } = expr; + self.walk(amount); + } fn walk_field_access_predicate(&mut self, expr: &FieldAccessPredicate) { let FieldAccessPredicate { base, .. } = expr; self.walk(base); @@ -708,6 +728,9 @@ pub fn default_walk_expr(this: &mut T, e: &Expr) { Expr::PredicateAccessPredicate(predicate_access_predicate) => { this.walk_predicate_access_predicate(predicate_access_predicate) } + Expr::ResourceAccessPredicate(resource_access_predicate) => { + this.walk_resource_access_predicate(resource_access_predicate) + } Expr::FieldAccessPredicate(field_access_predicate) => { this.walk_field_access_predicate(field_access_predicate) } @@ -835,6 +858,22 @@ pub trait FallibleExprFolder: Sized { })) } + fn fallible_fold_resource_access_predicate( + &mut self, + expr: ResourceAccessPredicate, + ) -> Result { + let ResourceAccessPredicate { + resource_type, + amount, + position, + } = expr; + Ok(Expr::ResourceAccessPredicate(ResourceAccessPredicate { + resource_type, + amount: self.fallible_fold_boxed(amount)?, + position, + })) + } + fn fallible_fold_field_access_predicate( &mut self, expr: FieldAccessPredicate, @@ -1152,6 +1191,9 @@ pub fn default_fallible_fold_expr>( Expr::PredicateAccessPredicate(predicate_access_predicate) => { this.fallible_fold_predicate_access_predicate(predicate_access_predicate) } + Expr::ResourceAccessPredicate(resource_access_predicate) => { + this.fallible_fold_resource_access_predicate(resource_access_predicate) + } Expr::FieldAccessPredicate(field_access_predicate) => { this.fallible_fold_field_access_predicate(field_access_predicate) } @@ -1234,6 +1276,13 @@ pub trait FallibleExprWalker: Sized { let PredicateAccessPredicate { argument, .. } = expr; self.fallible_walk(argument) } + fn fallible_walk_resource_access_predicate( + &mut self, + expr: &ResourceAccessPredicate, + ) -> Result<(), Self::Error> { + let ResourceAccessPredicate { amount, .. } = expr; + self.fallible_walk(amount) + } fn fallible_walk_field_access_predicate( &mut self, expr: &FieldAccessPredicate, @@ -1433,6 +1482,9 @@ pub fn default_fallible_walk_expr>( Expr::PredicateAccessPredicate(predicate_access_predicate) => { this.fallible_walk_predicate_access_predicate(predicate_access_predicate) } + Expr::ResourceAccessPredicate(resource_access_predicate) => { + this.fallible_walk_resource_access_predicate(resource_access_predicate) + } Expr::FieldAccessPredicate(field_access_predicate) => { this.fallible_walk_field_access_predicate(field_access_predicate) } diff --git a/vir/src/converter/polymorphic_to_legacy.rs b/vir/src/converter/polymorphic_to_legacy.rs index fae89b5261f..27617caa089 100644 --- a/vir/src/converter/polymorphic_to_legacy.rs +++ b/vir/src/converter/polymorphic_to_legacy.rs @@ -268,6 +268,13 @@ impl From for legacy::Expr { predicate_access_predicate.position.into(), ) } + polymorphic::Expr::ResourceAccessPredicate(resource_access_predicate) => { + legacy::Expr::ResourceAccessPredicate( + resource_access_predicate.resource_type.encode_as_string(), + Box::new((*resource_access_predicate.amount).into()), + resource_access_predicate.position.into(), + ) + } polymorphic::Expr::FieldAccessPredicate(field_access_predicate) => { legacy::Expr::FieldAccessPredicate( Box::new((*field_access_predicate.base).into()), diff --git a/vir/src/converter/type_substitution.rs b/vir/src/converter/type_substitution.rs index 24f2a36ef5e..8aac6fe23df 100644 --- a/vir/src/converter/type_substitution.rs +++ b/vir/src/converter/type_substitution.rs @@ -148,6 +148,9 @@ impl Generic for Expr { Expr::PredicateAccessPredicate(predicate_access_predicate) => { Expr::PredicateAccessPredicate(predicate_access_predicate.substitute(map)) } + Expr::ResourceAccessPredicate(resource_access_predicate) => { + Expr::ResourceAccessPredicate(resource_access_predicate.substitute(map)) + } Expr::FieldAccessPredicate(field_access_predicate) => { Expr::FieldAccessPredicate(field_access_predicate.substitute(map)) } @@ -240,6 +243,14 @@ impl Generic for PredicateAccessPredicate { } } +impl Generic for ResourceAccessPredicate { + fn substitute(self, map: &FxHashMap) -> Self { + let mut resource_access_predicate = self; + *resource_access_predicate.amount = resource_access_predicate.amount.substitute(map); + resource_access_predicate + } +} + impl Generic for FieldAccessPredicate { fn substitute(self, map: &FxHashMap) -> Self { let mut field_access_predicate = self; diff --git a/vir/src/legacy/ast/expr.rs b/vir/src/legacy/ast/expr.rs index 57f7598499f..20dcc54fd4f 100644 --- a/vir/src/legacy/ast/expr.rs +++ b/vir/src/legacy/ast/expr.rs @@ -31,6 +31,8 @@ pub enum Expr { MagicWand(Box, Box, Option, Position), /// PredicateAccessPredicate: predicate_name, arg, permission amount PredicateAccessPredicate(String, Box, PermAmount, Position), + /// ResourceAccessPredicate: resource name, amount + ResourceAccessPredicate(String, Box, Position), FieldAccessPredicate(Box, PermAmount, Position), UnaryOp(UnaryOpKind, Box, Position), BinOp(BinaryOpKind, Box, Box, Position), @@ -193,6 +195,9 @@ impl fmt::Display for Expr { Expr::PredicateAccessPredicate(ref pred_name, ref arg, perm, ref _pos) => { write!(f, "acc({pred_name}({arg}), {perm})") } + Expr::ResourceAccessPredicate(resouce_name, amount, _pos) => { + write!(f, "acc({}(), {}/1)", resouce_name, amount) + } Expr::FieldAccessPredicate(ref expr, perm, ref _pos) => { write!(f, "acc({expr}, {perm})") } @@ -373,6 +378,7 @@ impl Expr { | Expr::LabelledOld(_, _, p) | Expr::MagicWand(_, _, _, p) | Expr::PredicateAccessPredicate(_, _, _, p) + | Expr::ResourceAccessPredicate(_, _, p) | Expr::FieldAccessPredicate(_, _, p) | Expr::UnaryOp(_, _, p) | Expr::BinOp(_, _, _, p) @@ -407,6 +413,7 @@ impl Expr { Expr::PredicateAccessPredicate(x, y, z, _) => { Expr::PredicateAccessPredicate(x, y, z, pos) } + Expr::ResourceAccessPredicate(n, a, _) => Expr::ResourceAccessPredicate(n, a, pos), Expr::FieldAccessPredicate(x, y, _) => Expr::FieldAccessPredicate(x, y, pos), Expr::UnaryOp(x, y, _) => Expr::UnaryOp(x, y, pos), Expr::BinOp(x, y, z, _) => Expr::BinOp(x, y, z, pos), @@ -453,6 +460,11 @@ impl Expr { Expr::PredicateAccessPredicate(name.to_string(), box place, perm, pos) } + pub fn resource_access_predicate(name: S, amount: Expr) -> Self { + let pos = amount.pos(); + Expr::ResourceAccessPredicate(name.to_string(), box amount, pos) + } + pub fn field_access_predicate(place: Expr, perm: PermAmount) -> Self { let pos = place.pos(); Expr::FieldAccessPredicate(box place, perm, pos) @@ -773,7 +785,10 @@ impl Expr { pub fn is_only_permissions(&self) -> bool { match self { - Expr::PredicateAccessPredicate(..) | Expr::FieldAccessPredicate(..) => true, + Expr::PredicateAccessPredicate(..) + // TODO check this not sure + | Expr::ResourceAccessPredicate(..) + | Expr::FieldAccessPredicate(..) => true, Expr::BinOp(BinaryOpKind::And, box lhs, box rhs, _) => { lhs.is_only_permissions() && rhs.is_only_permissions() } @@ -1244,6 +1259,7 @@ impl Expr { typ1? } Expr::ForAll(..) | Expr::Exists(..) => &Type::Bool, + Expr::ResourceAccessPredicate(..) => &Type::Bool, Expr::MagicWand(..) | Expr::PredicateAccessPredicate(..) | Expr::FieldAccessPredicate(..) @@ -1687,6 +1703,7 @@ impl Expr { fn fold(&mut self, e: Expr) -> Expr { match e { f @ Expr::PredicateAccessPredicate(..) => f, + f @ Expr::ResourceAccessPredicate(..) => f, f @ Expr::FieldAccessPredicate(..) => f, Expr::BinOp(BinaryOpKind::And, y, z, p) => { self.fold_bin_op(BinaryOpKind::And, y, z, p) diff --git a/vir/src/legacy/ast/expr_transformers.rs b/vir/src/legacy/ast/expr_transformers.rs index aa490dd0e8f..38c361335c2 100644 --- a/vir/src/legacy/ast/expr_transformers.rs +++ b/vir/src/legacy/ast/expr_transformers.rs @@ -150,6 +150,18 @@ pub trait ExprFolder: Sized { self.fold_position(pos), ) } + fn fold_resource_access_predicate( + &mut self, + resource_name: String, + amount: Box, + pos: Position, + ) -> Expr { + Expr::ResourceAccessPredicate( + resource_name, + self.fold_boxed(amount), + self.fold_position(pos), + ) + } fn fold_field_access_predicate( &mut self, receiver: Box, @@ -358,6 +370,7 @@ pub fn default_fold_expr(this: &mut T, e: Expr) -> Expr { Expr::PredicateAccessPredicate(x, y, z, p) => { this.fold_predicate_access_predicate(x, y, z, p) } + Expr::ResourceAccessPredicate(n, a, p) => this.fold_resource_access_predicate(n, a, p), Expr::FieldAccessPredicate(x, y, p) => this.fold_field_access_predicate(x, y, p), Expr::UnaryOp(x, y, p) => this.fold_unary_op(x, y, p), Expr::BinOp(x, y, z, p) => this.fold_bin_op(x, y, z, p), @@ -438,6 +451,15 @@ pub trait ExprWalker: Sized { self.walk(arg); self.walk_position(pos); } + fn walk_resource_access_predicate( + &mut self, + _resource_name: &str, + amount: &Expr, + pos: &Position, + ) { + self.walk(amount); + self.walk_position(pos); + } fn walk_field_access_predicate( &mut self, receiver: &Expr, @@ -621,6 +643,9 @@ pub fn default_walk_expr(this: &mut T, e: &Expr) { Expr::PredicateAccessPredicate(ref x, ref y, z, ref p) => { this.walk_predicate_access_predicate(x, y, z, p) } + Expr::ResourceAccessPredicate(ref n, ref a, ref p) => { + this.walk_resource_access_predicate(n, a, p) + } Expr::FieldAccessPredicate(ref x, y, ref p) => this.walk_field_access_predicate(x, y, p), Expr::UnaryOp(x, ref y, ref p) => this.walk_unary_op(x, y, p), Expr::BinOp(x, ref y, ref z, ref p) => this.walk_bin_op(x, y, z, p), @@ -726,6 +751,18 @@ pub trait FallibleExprFolder: Sized { pos, )) } + fn fallible_fold_resource_access_predicate( + &mut self, + resource_name: String, + amount: Box, + pos: Position, + ) -> Result { + Ok(Expr::ResourceAccessPredicate( + resource_name, + self.fallible_fold_boxed(amount)?, + pos, + )) + } fn fallible_fold_field_access_predicate( &mut self, receiver: Box, @@ -981,6 +1018,9 @@ pub fn default_fallible_fold_expr>( Expr::PredicateAccessPredicate(x, y, z, p) => { this.fallible_fold_predicate_access_predicate(x, y, z, p) } + Expr::ResourceAccessPredicate(n, a, p) => { + this.fallible_fold_resource_access_predicate(n, a, p) + } Expr::FieldAccessPredicate(x, y, p) => this.fallible_fold_field_access_predicate(x, y, p), Expr::UnaryOp(x, y, p) => this.fallible_fold_unary_op(x, y, p), Expr::BinOp(x, y, z, p) => this.fallible_fold_bin_op(x, y, z, p), From 19027ad8ffbcabdbe0f1df573d986e5d4cbe953b Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Tue, 22 Nov 2022 10:00:53 +0100 Subject: [PATCH 02/26] Add tests for time resources --- .../tests/parse/fail/time_resources.rs | 11 +++++++++++ .../tests/parse/pass/time_resources.rs | 18 ++++++++++++++++++ .../fail/time-resources/negative_amount.rs | 5 +++++ .../fail/time-resources/time_resources.rs | 17 +++++++++++++++++ .../tests/typecheck/pass/time_resoucres.rs | 12 ++++++++++++ .../verify/fail/time-resources/condition.rs | 9 +++++++++ .../verify/fail/time-resources/constant.rs | 7 +++++++ .../fail/time-resources/negative_amount.rs | 5 +++++ .../verify/fail/time-resources/variable.rs | 8 ++++++++ .../verify/pass/time-resources/constant.rs | 13 +++++++++++++ .../tests/verify/pass/time-resources/loop.rs | 19 +++++++++++++++++++ 11 files changed, 124 insertions(+) create mode 100644 prusti-tests/tests/parse/fail/time_resources.rs create mode 100644 prusti-tests/tests/parse/pass/time_resources.rs create mode 100644 prusti-tests/tests/typecheck/fail/time-resources/negative_amount.rs create mode 100644 prusti-tests/tests/typecheck/fail/time-resources/time_resources.rs create mode 100644 prusti-tests/tests/typecheck/pass/time_resoucres.rs create mode 100644 prusti-tests/tests/verify/fail/time-resources/condition.rs create mode 100644 prusti-tests/tests/verify/fail/time-resources/constant.rs create mode 100644 prusti-tests/tests/verify/fail/time-resources/negative_amount.rs create mode 100644 prusti-tests/tests/verify/fail/time-resources/variable.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/constant.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/loop.rs diff --git a/prusti-tests/tests/parse/fail/time_resources.rs b/prusti-tests/tests/parse/fail/time_resources.rs new file mode 100644 index 00000000000..2888ac98a59 --- /dev/null +++ b/prusti-tests/tests/parse/fail/time_resources.rs @@ -0,0 +1,11 @@ +use prusti_contracts::*; + +// ignore-test: prusti-constract functions used outside specification check not yet implemented +fn main() { + let _i; + if time_credits(2) && time_receipts(1) { //~ ERROR + _i = 1; + } else { + _i = 2; + } +} diff --git a/prusti-tests/tests/parse/pass/time_resources.rs b/prusti-tests/tests/parse/pass/time_resources.rs new file mode 100644 index 00000000000..e01f97b8592 --- /dev/null +++ b/prusti-tests/tests/parse/pass/time_resources.rs @@ -0,0 +1,18 @@ +use prusti_contracts::*; + +#[requires(time_credits(n as usize))] +#[ensures(time_receipts(n as usize))] +fn foo(n: u32) -> u32 { + n +} + +#[requires(time_credits(10))] +#[ensures(time_receipts(10))] +fn main() { + let mut i = 0; + while i < 10 { + body_invariant!(time_credits(10 - i)); + body_invariant!(time_receipts(i)); + i += 1; + } +} diff --git a/prusti-tests/tests/typecheck/fail/time-resources/negative_amount.rs b/prusti-tests/tests/typecheck/fail/time-resources/negative_amount.rs new file mode 100644 index 00000000000..2d7f7ed65c0 --- /dev/null +++ b/prusti-tests/tests/typecheck/fail/time-resources/negative_amount.rs @@ -0,0 +1,5 @@ +use prusti_contracts::*; + +#[requires(time_credits(-1))] //~ ERROR +#[ensures(time_receipts(-1))] //~ ERROR +fn main() {} diff --git a/prusti-tests/tests/typecheck/fail/time-resources/time_resources.rs b/prusti-tests/tests/typecheck/fail/time-resources/time_resources.rs new file mode 100644 index 00000000000..556c838a9ee --- /dev/null +++ b/prusti-tests/tests/typecheck/fail/time-resources/time_resources.rs @@ -0,0 +1,17 @@ +use prusti_contracts::*; + +struct MyStruct { + time: usize, +} + +#[requires(time_credits(true))] //~ ERROR mismatched types +#[ensures(time_receipts("hello"))] //~ ERROR mismatched types +fn main() { + let mut i = 0; + while i < 10 { + body_invariant!(time_credits(MyStruct { time: 10 - i })); //~ ERROR mismatched types + body_invariant!(time_receipts((i, 0))); //~ ERROR mismatched types + i += 1; + } +} + diff --git a/prusti-tests/tests/typecheck/pass/time_resoucres.rs b/prusti-tests/tests/typecheck/pass/time_resoucres.rs new file mode 100644 index 00000000000..c7c915795fc --- /dev/null +++ b/prusti-tests/tests/typecheck/pass/time_resoucres.rs @@ -0,0 +1,12 @@ +use prusti_contracts::*; + +#[requires(time_credits(10))] +#[ensures(time_receipts(10))] +fn main() { + let mut i: usize = 0; + while i < 10 { + body_invariant!(time_credits(10 - i)); + body_invariant!(time_receipts(i)); + i += 1; + } +} diff --git a/prusti-tests/tests/verify/fail/time-resources/condition.rs b/prusti-tests/tests/verify/fail/time-resources/condition.rs new file mode 100644 index 00000000000..dbb8d09aba0 --- /dev/null +++ b/prusti-tests/tests/verify/fail/time-resources/condition.rs @@ -0,0 +1,9 @@ +use prusti_contracts::*; + +fn main() { + if time_credits(42) || time_receipts(12) { //~ ERROR + println!("foo"); + } else { + println!("bar"); + } +} diff --git a/prusti-tests/tests/verify/fail/time-resources/constant.rs b/prusti-tests/tests/verify/fail/time-resources/constant.rs new file mode 100644 index 00000000000..dfdc44fde70 --- /dev/null +++ b/prusti-tests/tests/verify/fail/time-resources/constant.rs @@ -0,0 +1,7 @@ +use prusti_contracts::*; + +#[requires(time_credits(0))] //~ ERROR +#[ensures(time_receipts(4))] //~ ERROR +fn main() -> () { + () +} diff --git a/prusti-tests/tests/verify/fail/time-resources/negative_amount.rs b/prusti-tests/tests/verify/fail/time-resources/negative_amount.rs new file mode 100644 index 00000000000..88e61c10adc --- /dev/null +++ b/prusti-tests/tests/verify/fail/time-resources/negative_amount.rs @@ -0,0 +1,5 @@ +use prusti_contracts::*; + +#[requires(time_credits(1 - 2))] //~ ERROR +#[ensures(time_receipts(2 - 4))] //~ ERROR +fn main() {} diff --git a/prusti-tests/tests/verify/fail/time-resources/variable.rs b/prusti-tests/tests/verify/fail/time-resources/variable.rs new file mode 100644 index 00000000000..22144c5b3e2 --- /dev/null +++ b/prusti-tests/tests/verify/fail/time-resources/variable.rs @@ -0,0 +1,8 @@ +use prusti_contracts::*; + +#[requires(time_credits(50))] +#[ensures(time_receipts(10))] +fn main() { + let c = time_credits(42); //~ ERROR + let r = time_receipts(10); //~ ERROR +} diff --git a/prusti-tests/tests/verify/pass/time-resources/constant.rs b/prusti-tests/tests/verify/pass/time-resources/constant.rs new file mode 100644 index 00000000000..7093597d0a4 --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/constant.rs @@ -0,0 +1,13 @@ +use prusti_contracts::*; + +#[requires(time_credits(2))] +#[ensures(time_receipts(2))] +fn main() { + constant(); +} + +#[requires(time_credits(1))] +#[ensures(time_receipts(1))] +fn constant() -> u32 { + 42 +} diff --git a/prusti-tests/tests/verify/pass/time-resources/loop.rs b/prusti-tests/tests/verify/pass/time-resources/loop.rs new file mode 100644 index 00000000000..0ade55042be --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/loop.rs @@ -0,0 +1,19 @@ +use prusti_contracts::*; + +#[requires(time_credits(4 * n as usize + 2))] +#[ensures(time_receipts(4 * n as usize + 2))] +fn sum(n: u32) -> u32 { + let mut i = 0; + let mut res = 0; + while i < n { + body_invariant!(time_credits(4 * (n - i) as usize)); + body_invariant!(time_receipts(4 * i as usize + 2)); + res += i; + i += 1; + } + res +} + +fn main() { + // sum(4); +} From 72ecf6acf67654f4966f3057206a262420852245 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Tue, 22 Nov 2022 10:01:09 +0100 Subject: [PATCH 03/26] Fix unfolding issue --- prusti-common/src/vir/optimizations/folding/expressions.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/prusti-common/src/vir/optimizations/folding/expressions.rs b/prusti-common/src/vir/optimizations/folding/expressions.rs index caf85905ff8..9f2de7afd92 100644 --- a/prusti-common/src/vir/optimizations/folding/expressions.rs +++ b/prusti-common/src/vir/optimizations/folding/expressions.rs @@ -417,6 +417,13 @@ impl ast::FallibleExprFolder for ExprOptimizer { Err(()) } + fn fallible_fold_resource_access_predicate( + &mut self, + _resource_access_predicate: vir::polymorphic::ResourceAccessPredicate, + ) -> Result { + Err(()) + } + fn fallible_fold_field_access_predicate( &mut self, _field_access_predicate: ast::FieldAccessPredicate, From 76fdd0edaa8ee31ec0a2db44f1068345d6100c23 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Wed, 23 Nov 2022 16:33:04 +0100 Subject: [PATCH 04/26] Add resource predicates generation --- .../src/vir/optimizations/bitvectors/mod.rs | 1 + .../vir/optimizations/folding/expressions.rs | 6 +- .../predicates/delete_unused_predicates.rs | 56 ++++++++++++++----- prusti-common/src/vir/to_viper.rs | 3 +- .../src/encoder/definition_collector.rs | 17 ++++++ .../src/encoder/foldunfold/footprint.rs | 5 +- vir/defs/polymorphic/ast/expr.rs | 2 +- vir/defs/polymorphic/ast/predicate.rs | 18 ++++++ vir/src/converter/polymorphic_to_legacy.rs | 3 + vir/src/converter/type_substitution.rs | 1 + vir/src/legacy/ast/predicate.rs | 10 ++++ 11 files changed, 99 insertions(+), 23 deletions(-) diff --git a/prusti-common/src/vir/optimizations/bitvectors/mod.rs b/prusti-common/src/vir/optimizations/bitvectors/mod.rs index 4cdfad5c298..dad9c30f6f8 100644 --- a/prusti-common/src/vir/optimizations/bitvectors/mod.rs +++ b/prusti-common/src/vir/optimizations/bitvectors/mod.rs @@ -68,6 +68,7 @@ pub fn replace_all_ints(program: &mut vir_poly::Program) { } } vir_poly::Predicate::Bodyless(..) => {} + vir_poly::Predicate::ResourceAccess(_) => {} } } let types = [ diff --git a/prusti-common/src/vir/optimizations/folding/expressions.rs b/prusti-common/src/vir/optimizations/folding/expressions.rs index 9f2de7afd92..5e320db1d6c 100644 --- a/prusti-common/src/vir/optimizations/folding/expressions.rs +++ b/prusti-common/src/vir/optimizations/folding/expressions.rs @@ -418,9 +418,9 @@ impl ast::FallibleExprFolder for ExprOptimizer { } fn fallible_fold_resource_access_predicate( - &mut self, - _resource_access_predicate: vir::polymorphic::ResourceAccessPredicate, - ) -> Result { + &mut self, + _resource_access_predicate: vir::polymorphic::ResourceAccessPredicate, + ) -> Result { Err(()) } diff --git a/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs b/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs index 7e5863873e6..483d3c8dd4f 100644 --- a/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs +++ b/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs @@ -28,7 +28,7 @@ fn collect_info_from_methods_and_functions( collector } -/// Computes a map of Predicate to the predicates used in that predicate +/// Computes a map of Predicate to the predicates used in that predicate fn get_used_predicates_in_predicate_map( predicates: &[Predicate], ) -> FxHashMap> { @@ -65,6 +65,7 @@ fn get_used_predicates_in_predicate_map( map.insert(p.typ.clone(), res); } Predicate::Bodyless(_, _) => { /* ignore */ } + Predicate::ResourceAccess(_) => { /* ignore */ } } } map @@ -120,18 +121,21 @@ pub fn delete_unused_predicates( ); // Remove the bodies of predicats that are never folded or unfolded - predicates.iter_mut().for_each(|predicate| { - let predicate_type = &predicate.get_type().clone(); - if !folded_predicates.contains(predicate_type) { - if let Predicate::Struct(sp) = predicate { - debug!("Removed body of {}", predicate_type); - sp.body = None; - - // since the predicate now has no body update the predicate map accordingly - predicates_in_predicates_map.remove(predicate_type); + predicates + .iter_mut() + .filter(|p| !p.is_resource_predicate()) + .for_each(|predicate| { + let predicate_type = &predicate.get_type().clone(); + if !folded_predicates.contains(predicate_type) { + if let Predicate::Struct(sp) = predicate { + debug!("Removed body of {}", predicate_type); + sp.body = None; + + // since the predicate now has no body update the predicate map accordingly + predicates_in_predicates_map.remove(predicate_type); + } } - } - }); + }); debug!( "Map of predicates used in predicates {:?}", @@ -144,8 +148,15 @@ pub fn delete_unused_predicates( ); debug!("All the used predicates are {:?}", &reachable_predicates); + debug!( + "All the used resource predicates are {:?}", + &collector.used_resource_predicates + ); - predicates.retain(|p| reachable_predicates.contains(p.get_type())); + predicates.retain(|p| match p { + Predicate::ResourceAccess(typ) => collector.used_resource_predicates.contains(typ), + _ => reachable_predicates.contains(p.get_type()), + }); predicates } @@ -155,13 +166,16 @@ struct UsedPredicateCollector { used_predicates: FxHashSet, /// set of all predicates that are folded or unfolded folded_predicates: FxHashSet, + /// set of all resource predicates that are used + used_resource_predicates: FxHashSet, } impl UsedPredicateCollector { fn new() -> Self { UsedPredicateCollector { - used_predicates: FxHashSet::default(), - folded_predicates: FxHashSet::default(), + used_predicates: FxHashSet::new(), + folded_predicates: FxHashSet::new(), + used_resource_predicates: FxHashSet::new(), } } } @@ -179,6 +193,18 @@ impl ExprWalker for UsedPredicateCollector { ExprWalker::walk(self, argument); } + fn walk_resource_access_predicate( + &mut self, + ResourceAccessPredicate { + resource_type, + amount, + .. + }: &ResourceAccessPredicate, + ) { + self.used_resource_predicates.insert(resource_type.clone()); + ExprWalker::walk(self, amount); + } + fn walk_unfolding( &mut self, Unfolding { diff --git a/prusti-common/src/vir/to_viper.rs b/prusti-common/src/vir/to_viper.rs index e0186773132..199c4156048 100644 --- a/prusti-common/src/vir/to_viper.rs +++ b/prusti-common/src/vir/to_viper.rs @@ -407,7 +407,7 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for Expr { // Resource are encoded as a predicate with no arguments ast.predicate_access_predicate_with_pos( ast.predicate_access(&[], resource_name), - ast.perm_mul(amount.to_viper(context, ast), ast.full_perm()), + ast.int_perm_mul(amount.to_viper(context, ast), ast.full_perm()), // ast.fractional_perm(amount.to_viper(context, ast), ast.int_lit(1)), pos.to_viper(context, ast), ) @@ -819,6 +819,7 @@ impl<'v> ToViper<'v, viper::Predicate<'v>> for Predicate { Predicate::Bodyless(name, this) => { ast.predicate(name, &[this.to_viper_decl(context, ast)], None) } + Predicate::ResourceAccess(typ) => ast.predicate(typ, &[], None), } } } diff --git a/prusti-viper/src/encoder/definition_collector.rs b/prusti-viper/src/encoder/definition_collector.rs index 875d6755ec5..922116b5272 100644 --- a/prusti-viper/src/encoder/definition_collector.rs +++ b/prusti-viper/src/encoder/definition_collector.rs @@ -50,6 +50,7 @@ pub(super) fn collect_definitions( method_names: methods.iter().map(|method| method.name()).collect(), unfolded_predicates: unfolded_predicate_collector.unfolded_predicates, new_unfolded_predicates: Default::default(), + used_resource_type: Default::default(), used_predicates: Default::default(), used_fields: Default::default(), used_domains: Default::default(), @@ -72,6 +73,8 @@ struct Collector<'p, 'v: 'p, 'tcx: 'v> { /// The list of encoded methods for checking that they do not clash with /// functions. method_names: FxHashSet, + /// The set of all resource predicates that are mentioned in the method. + used_resource_type: FxHashSet, /// The set of all predicates that are mentioned in the method. used_predicates: FxHashSet, /// The set of predicates whose bodies have to be included because they are @@ -193,6 +196,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> Collector<'p, 'v, 'tcx> { }; predicates.push(predicate); } + for resource in &self.used_resource_type { + predicates.push(vir::Predicate::ResourceAccess(resource.clone())); + } predicates.push(vir::Predicate::Bodyless( vir::Type::typed_ref("DeadBorrowToken$"), vir_local! { borrow: Int }, @@ -883,6 +889,17 @@ impl<'p, 'v: 'p, 'tcx: 'v> FallibleExprWalker for Collector<'p, 'v, 'tcx> { self.used_predicates.insert(predicate_type.clone()); FallibleExprWalker::fallible_walk(self, argument) } + fn fallible_walk_resource_access_predicate( + &mut self, + vir::ResourceAccessPredicate { + resource_type, + amount, + .. + }: &vir::ResourceAccessPredicate, + ) -> SpannedEncodingResult<()> { + self.used_resource_type.insert(resource_type.clone()); + FallibleExprWalker::fallible_walk(self, amount) + } fn fallible_walk_unfolding( &mut self, vir::Unfolding { diff --git a/prusti-viper/src/encoder/foldunfold/footprint.rs b/prusti-viper/src/encoder/foldunfold/footprint.rs index c70add4935e..a0ed4e2fa85 100644 --- a/prusti-viper/src/encoder/foldunfold/footprint.rs +++ b/prusti-viper/src/encoder/foldunfold/footprint.rs @@ -161,9 +161,7 @@ impl ExprFootprintGetter for vir::Expr { Some(perm).into_iter().collect() } - vir::Expr::ResourceAccessPredicate(_) => { - FxHashSet::default() - } + vir::Expr::ResourceAccessPredicate(_) => FxHashSet::default(), vir::Expr::MagicWand(_) => { // We don't track magic wands resources @@ -216,6 +214,7 @@ impl PredicateFootprintGetter for vir::Predicate { } } vir::Predicate::Bodyless(_, _) => FxHashSet::default(), + vir::Predicate::ResourceAccess(_) => FxHashSet::default(), } } } diff --git a/vir/defs/polymorphic/ast/expr.rs b/vir/defs/polymorphic/ast/expr.rs index a485a9f2a3c..2ec8c99812b 100644 --- a/vir/defs/polymorphic/ast/expr.rs +++ b/vir/defs/polymorphic/ast/expr.rs @@ -635,7 +635,7 @@ impl Expr { match self { Expr::PredicateAccessPredicate(..) // TODO check this not sure - | Expr::ResourceAccessPredicate(..) + | Expr::ResourceAccessPredicate(..) | Expr::FieldAccessPredicate(..) => true, Expr::BinOp(BinOp { op_kind: BinaryOpKind::And, diff --git a/vir/defs/polymorphic/ast/predicate.rs b/vir/defs/polymorphic/ast/predicate.rs index 419926293e1..fb8dbade3d9 100644 --- a/vir/defs/polymorphic/ast/predicate.rs +++ b/vir/defs/polymorphic/ast/predicate.rs @@ -13,6 +13,7 @@ pub enum Predicate { Struct(StructPredicate), Enum(EnumPredicate), Bodyless(Type, LocalVar), + ResourceAccess(ResourceType), } impl fmt::Display for Predicate { @@ -21,6 +22,7 @@ impl fmt::Display for Predicate { Predicate::Struct(p) => write!(f, "{}", p), Predicate::Enum(p) => write!(f, "{}", p), Predicate::Bodyless(name, this) => write!(f, "bodyless_predicate {}({});", name, this), + Predicate::ResourceAccess(resouce_type) => write!(f, "{}", resouce_type), } } } @@ -97,12 +99,19 @@ impl Predicate { variants, }) } + /// Construct a predicate that corresponds to a specific resource type. + pub fn new_resource(typ: ResourceType) -> Predicate { + Predicate::ResourceAccess(typ) + } /// A `self` place getter. pub fn self_place(&self) -> Expr { match self { Predicate::Struct(p) => p.this.clone().into(), Predicate::Enum(p) => p.this.clone().into(), Predicate::Bodyless(_, this) => this.clone().into(), + Predicate::ResourceAccess(_) => { + unreachable!("Resouce access predicate does not have `self` place.") + } } } /// The predicate type getter. @@ -111,6 +120,9 @@ impl Predicate { Predicate::Struct(p) => &p.typ, Predicate::Enum(p) => &p.typ, Predicate::Bodyless(typ, _) => typ, + Predicate::ResourceAccess(_) => { + unreachable!("Resouce access predicate does not have types.") + } } } /// The predicate name getter. @@ -119,6 +131,7 @@ impl Predicate { Predicate::Struct(p) => p.typ.name(), Predicate::Enum(p) => p.typ.name(), Predicate::Bodyless(ref typ, _) => typ.name(), + Predicate::ResourceAccess(typ) => typ.encode_as_string(), } } pub fn body(&self) -> Option { @@ -126,6 +139,7 @@ impl Predicate { Predicate::Struct(struct_predicate) => struct_predicate.body.clone(), Predicate::Enum(enum_predicate) => Some(enum_predicate.body()), Predicate::Bodyless(_, _) => None, + Predicate::ResourceAccess(_) => None, } } pub fn is_struct_with_empty_body(&self) -> bool { @@ -134,6 +148,9 @@ impl Predicate { _ => false, } } + pub fn is_resource_predicate(&self) -> bool { + matches!(self, Predicate::ResourceAccess(..)) + } } impl WithIdentifier for Predicate { @@ -142,6 +159,7 @@ impl WithIdentifier for Predicate { Predicate::Struct(p) => p.get_identifier(), Predicate::Enum(p) => p.get_identifier(), Predicate::Bodyless(typ, _) => typ.encode_as_string(), + Predicate::ResourceAccess(typ) => typ.encode_as_string(), } } } diff --git a/vir/src/converter/polymorphic_to_legacy.rs b/vir/src/converter/polymorphic_to_legacy.rs index 27617caa089..7274e29535b 100644 --- a/vir/src/converter/polymorphic_to_legacy.rs +++ b/vir/src/converter/polymorphic_to_legacy.rs @@ -555,6 +555,9 @@ impl From for legacy::Predicate { polymorphic::Predicate::Bodyless(typ, local_var) => { legacy::Predicate::Bodyless(typ.encode_as_string(), local_var.into()) } + polymorphic::Predicate::ResourceAccess(typ) => { + legacy::Predicate::ResourceAccess(typ.encode_as_string()) + } } } } diff --git a/vir/src/converter/type_substitution.rs b/vir/src/converter/type_substitution.rs index 8aac6fe23df..7c0e188cc4b 100644 --- a/vir/src/converter/type_substitution.rs +++ b/vir/src/converter/type_substitution.rs @@ -484,6 +484,7 @@ impl Generic for Predicate { Predicate::Bodyless(label, local_var) => { Predicate::Bodyless(label, local_var.substitute(map)) } + p @ Predicate::ResourceAccess(_) => p, } } } diff --git a/vir/src/legacy/ast/predicate.rs b/vir/src/legacy/ast/predicate.rs index 66c0dafeac3..1fb2b63c7d8 100644 --- a/vir/src/legacy/ast/predicate.rs +++ b/vir/src/legacy/ast/predicate.rs @@ -12,6 +12,7 @@ pub enum Predicate { Struct(StructPredicate), Enum(EnumPredicate), Bodyless(String, LocalVar), + ResourceAccess(String), } impl fmt::Display for Predicate { @@ -20,6 +21,7 @@ impl fmt::Display for Predicate { Predicate::Struct(p) => write!(f, "{p}"), Predicate::Enum(p) => write!(f, "{p}"), Predicate::Bodyless(name, this) => write!(f, "bodyless_predicate {name}({this});"), + Predicate::ResourceAccess(typ) => write!(f, "{typ}"), } } } @@ -104,6 +106,9 @@ impl Predicate { Predicate::Struct(p) => p.this.clone().into(), Predicate::Enum(p) => p.this.clone().into(), Predicate::Bodyless(_, this) => this.clone().into(), + Predicate::ResourceAccess(_) => { + unreachable!("Resource access does not have `self` place.") + } } } /// The predicate name getter. @@ -112,6 +117,7 @@ impl Predicate { Predicate::Struct(p) => &p.name, Predicate::Enum(p) => &p.name, Predicate::Bodyless(ref name, _) => name, + Predicate::ResourceAccess(typ) => &typ, } } pub fn body(&self) -> Option { @@ -119,6 +125,7 @@ impl Predicate { Predicate::Struct(struct_predicate) => struct_predicate.body.clone(), Predicate::Enum(enum_predicate) => Some(enum_predicate.body()), Predicate::Bodyless(_, _) => None, + Predicate::ResourceAccess(_) => None, } } @@ -129,6 +136,7 @@ impl Predicate { Predicate::Struct(p) => p.visit_expressions(visitor), Predicate::Enum(p) => p.visit_expressions(visitor), Predicate::Bodyless(..) => {} + Predicate::ResourceAccess(_) => {} } } @@ -139,6 +147,7 @@ impl Predicate { Predicate::Struct(p) => p.visit_expressions_mut(visitor), Predicate::Enum(p) => p.visit_expressions_mut(visitor), Predicate::Bodyless(..) => {} + Predicate::ResourceAccess(_) => {} } } } @@ -149,6 +158,7 @@ impl WithIdentifier for Predicate { Predicate::Struct(p) => p.get_identifier(), Predicate::Enum(p) => p.get_identifier(), Predicate::Bodyless(name, _) => name.clone(), + Predicate::ResourceAccess(typ) => typ.clone(), } } } From b674e34bc7748d5a97fe084a516946b80b59d2db Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Fri, 25 Nov 2022 13:55:01 +0100 Subject: [PATCH 05/26] Feedback --- prusti-common/src/vir/to_viper.rs | 4 ++-- prusti-contracts/prusti-contracts/src/lib.rs | 2 +- .../tests/{parse => typecheck}/fail/time_resources.rs | 0 .../pass/{time_resoucres.rs => time_resources.rs} | 0 prusti-viper/src/encoder/definition_collector.rs | 8 ++++---- 5 files changed, 7 insertions(+), 7 deletions(-) rename prusti-tests/tests/{parse => typecheck}/fail/time_resources.rs (100%) rename prusti-tests/tests/typecheck/pass/{time_resoucres.rs => time_resources.rs} (100%) diff --git a/prusti-common/src/vir/to_viper.rs b/prusti-common/src/vir/to_viper.rs index 199c4156048..1bd0debf4e6 100644 --- a/prusti-common/src/vir/to_viper.rs +++ b/prusti-common/src/vir/to_viper.rs @@ -407,8 +407,8 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for Expr { // Resource are encoded as a predicate with no arguments ast.predicate_access_predicate_with_pos( ast.predicate_access(&[], resource_name), - ast.int_perm_mul(amount.to_viper(context, ast), ast.full_perm()), - // ast.fractional_perm(amount.to_viper(context, ast), ast.int_lit(1)), + // ast.int_perm_mul(amount.to_viper(context, ast), ast.full_perm()), + ast.fractional_perm(amount.to_viper(context, ast), ast.int_lit(1)), pos.to_viper(context, ast), ) } diff --git a/prusti-contracts/prusti-contracts/src/lib.rs b/prusti-contracts/prusti-contracts/src/lib.rs index 6abbc519564..0f4e1a4972d 100644 --- a/prusti-contracts/prusti-contracts/src/lib.rs +++ b/prusti-contracts/prusti-contracts/src/lib.rs @@ -345,7 +345,7 @@ pub fn time_credits(_n: usize) -> bool { } /// This function is used to specify the number of time receipts ensured at the -/// end of the execution of a function in its specification. It defined the +/// end of the execution of a function in its specification. It defines the /// lower bound for its runtime. pub fn time_receipts(_n: usize) -> bool { true diff --git a/prusti-tests/tests/parse/fail/time_resources.rs b/prusti-tests/tests/typecheck/fail/time_resources.rs similarity index 100% rename from prusti-tests/tests/parse/fail/time_resources.rs rename to prusti-tests/tests/typecheck/fail/time_resources.rs diff --git a/prusti-tests/tests/typecheck/pass/time_resoucres.rs b/prusti-tests/tests/typecheck/pass/time_resources.rs similarity index 100% rename from prusti-tests/tests/typecheck/pass/time_resoucres.rs rename to prusti-tests/tests/typecheck/pass/time_resources.rs diff --git a/prusti-viper/src/encoder/definition_collector.rs b/prusti-viper/src/encoder/definition_collector.rs index 922116b5272..d272e53cfc3 100644 --- a/prusti-viper/src/encoder/definition_collector.rs +++ b/prusti-viper/src/encoder/definition_collector.rs @@ -50,7 +50,7 @@ pub(super) fn collect_definitions( method_names: methods.iter().map(|method| method.name()).collect(), unfolded_predicates: unfolded_predicate_collector.unfolded_predicates, new_unfolded_predicates: Default::default(), - used_resource_type: Default::default(), + used_resource_types: Default::default(), used_predicates: Default::default(), used_fields: Default::default(), used_domains: Default::default(), @@ -74,7 +74,7 @@ struct Collector<'p, 'v: 'p, 'tcx: 'v> { /// functions. method_names: FxHashSet, /// The set of all resource predicates that are mentioned in the method. - used_resource_type: FxHashSet, + used_resource_types: FxHashSet, /// The set of all predicates that are mentioned in the method. used_predicates: FxHashSet, /// The set of predicates whose bodies have to be included because they are @@ -196,7 +196,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> Collector<'p, 'v, 'tcx> { }; predicates.push(predicate); } - for resource in &self.used_resource_type { + for resource in &self.used_resource_types { predicates.push(vir::Predicate::ResourceAccess(resource.clone())); } predicates.push(vir::Predicate::Bodyless( @@ -897,7 +897,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> FallibleExprWalker for Collector<'p, 'v, 'tcx> { .. }: &vir::ResourceAccessPredicate, ) -> SpannedEncodingResult<()> { - self.used_resource_type.insert(resource_type.clone()); + self.used_resource_types.insert(resource_type.clone()); FallibleExprWalker::fallible_walk(self, amount) } fn fallible_walk_unfolding( From 5f54d60396692729f42f896142a021f3fc36700a Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Tue, 29 Nov 2022 11:58:53 +0100 Subject: [PATCH 06/26] Add flag to enable time reasoning --- .../tests/parse/pass/time_resources.rs | 2 ++ .../condition.rs} | 5 +++- .../fail/time-resources/negative_amount.rs | 2 ++ .../fail/time-resources/time_resources.rs | 3 +- .../tests/typecheck/pass/time_resources.rs | 2 ++ .../verify/fail/time-resources/condition.rs | 9 ------ .../verify/fail/time-resources/constant.rs | 2 ++ .../fail/time-resources/negative_amount.rs | 2 ++ .../verify/fail/time-resources/variable.rs | 3 ++ .../verify/pass/time-resources/constant.rs | 2 ++ .../tests/verify/pass/time-resources/flag.rs | 17 +++++++++++ .../tests/verify/pass/time-resources/loop.rs | 2 ++ prusti-utils/src/config.rs | 6 ++++ .../mir/pure/specifications/interface.rs | 29 ++++++++++++++----- 14 files changed, 67 insertions(+), 19 deletions(-) rename prusti-tests/tests/typecheck/fail/{time_resources.rs => time-resources/condition.rs} (65%) delete mode 100644 prusti-tests/tests/verify/fail/time-resources/condition.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/flag.rs diff --git a/prusti-tests/tests/parse/pass/time_resources.rs b/prusti-tests/tests/parse/pass/time_resources.rs index e01f97b8592..ce0fa74d6d1 100644 --- a/prusti-tests/tests/parse/pass/time_resources.rs +++ b/prusti-tests/tests/parse/pass/time_resources.rs @@ -1,3 +1,5 @@ +// compile-flags: -Ptime_reasoning=true + use prusti_contracts::*; #[requires(time_credits(n as usize))] diff --git a/prusti-tests/tests/typecheck/fail/time_resources.rs b/prusti-tests/tests/typecheck/fail/time-resources/condition.rs similarity index 65% rename from prusti-tests/tests/typecheck/fail/time_resources.rs rename to prusti-tests/tests/typecheck/fail/time-resources/condition.rs index 2888ac98a59..9cfb4adf5f9 100644 --- a/prusti-tests/tests/typecheck/fail/time_resources.rs +++ b/prusti-tests/tests/typecheck/fail/time-resources/condition.rs @@ -1,9 +1,12 @@ +// compile-flags: -Ptime_reasoning=true + use prusti_contracts::*; // ignore-test: prusti-constract functions used outside specification check not yet implemented fn main() { let _i; - if time_credits(2) && time_receipts(1) { //~ ERROR + if time_credits(2) && time_receipts(1) { + //~ ERROR _i = 1; } else { _i = 2; diff --git a/prusti-tests/tests/typecheck/fail/time-resources/negative_amount.rs b/prusti-tests/tests/typecheck/fail/time-resources/negative_amount.rs index 2d7f7ed65c0..bf4e0bb80ad 100644 --- a/prusti-tests/tests/typecheck/fail/time-resources/negative_amount.rs +++ b/prusti-tests/tests/typecheck/fail/time-resources/negative_amount.rs @@ -1,3 +1,5 @@ +// compile-flags: -Ptime_reasoning=true + use prusti_contracts::*; #[requires(time_credits(-1))] //~ ERROR diff --git a/prusti-tests/tests/typecheck/fail/time-resources/time_resources.rs b/prusti-tests/tests/typecheck/fail/time-resources/time_resources.rs index 556c838a9ee..4191290c330 100644 --- a/prusti-tests/tests/typecheck/fail/time-resources/time_resources.rs +++ b/prusti-tests/tests/typecheck/fail/time-resources/time_resources.rs @@ -1,3 +1,5 @@ +// compile-flags: -Ptime_reasoning=true + use prusti_contracts::*; struct MyStruct { @@ -14,4 +16,3 @@ fn main() { i += 1; } } - diff --git a/prusti-tests/tests/typecheck/pass/time_resources.rs b/prusti-tests/tests/typecheck/pass/time_resources.rs index c7c915795fc..80f21784a65 100644 --- a/prusti-tests/tests/typecheck/pass/time_resources.rs +++ b/prusti-tests/tests/typecheck/pass/time_resources.rs @@ -1,3 +1,5 @@ +// compile-flags: -Ptime_reasoning=true + use prusti_contracts::*; #[requires(time_credits(10))] diff --git a/prusti-tests/tests/verify/fail/time-resources/condition.rs b/prusti-tests/tests/verify/fail/time-resources/condition.rs deleted file mode 100644 index dbb8d09aba0..00000000000 --- a/prusti-tests/tests/verify/fail/time-resources/condition.rs +++ /dev/null @@ -1,9 +0,0 @@ -use prusti_contracts::*; - -fn main() { - if time_credits(42) || time_receipts(12) { //~ ERROR - println!("foo"); - } else { - println!("bar"); - } -} diff --git a/prusti-tests/tests/verify/fail/time-resources/constant.rs b/prusti-tests/tests/verify/fail/time-resources/constant.rs index dfdc44fde70..7dc52d7b221 100644 --- a/prusti-tests/tests/verify/fail/time-resources/constant.rs +++ b/prusti-tests/tests/verify/fail/time-resources/constant.rs @@ -1,3 +1,5 @@ +// compile-flags: -Ptime_reasoning=true + use prusti_contracts::*; #[requires(time_credits(0))] //~ ERROR diff --git a/prusti-tests/tests/verify/fail/time-resources/negative_amount.rs b/prusti-tests/tests/verify/fail/time-resources/negative_amount.rs index 88e61c10adc..35399f538a2 100644 --- a/prusti-tests/tests/verify/fail/time-resources/negative_amount.rs +++ b/prusti-tests/tests/verify/fail/time-resources/negative_amount.rs @@ -1,3 +1,5 @@ +// compile-flags: -Ptime_reasoning=true + use prusti_contracts::*; #[requires(time_credits(1 - 2))] //~ ERROR diff --git a/prusti-tests/tests/verify/fail/time-resources/variable.rs b/prusti-tests/tests/verify/fail/time-resources/variable.rs index 22144c5b3e2..96bec3bcee0 100644 --- a/prusti-tests/tests/verify/fail/time-resources/variable.rs +++ b/prusti-tests/tests/verify/fail/time-resources/variable.rs @@ -1,5 +1,8 @@ +// compile-flags: -Ptime_reasoning=true + use prusti_contracts::*; +// ignore-test: prusti-constract functions used outside specification check not yet implemented #[requires(time_credits(50))] #[ensures(time_receipts(10))] fn main() { diff --git a/prusti-tests/tests/verify/pass/time-resources/constant.rs b/prusti-tests/tests/verify/pass/time-resources/constant.rs index 7093597d0a4..113786df4c2 100644 --- a/prusti-tests/tests/verify/pass/time-resources/constant.rs +++ b/prusti-tests/tests/verify/pass/time-resources/constant.rs @@ -1,3 +1,5 @@ +// compile-flags: -Ptime_reasoning=true + use prusti_contracts::*; #[requires(time_credits(2))] diff --git a/prusti-tests/tests/verify/pass/time-resources/flag.rs b/prusti-tests/tests/verify/pass/time-resources/flag.rs new file mode 100644 index 00000000000..3895ef3c716 --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/flag.rs @@ -0,0 +1,17 @@ +use prusti_contracts::*; + +#[requires(time_credits(1))] +#[ensures(time_receipts(1))] +fn sum(n: u32) -> u32 { + let mut i = 0; + let mut res = 0; + while i < n { + res += i; + i += 1; + } + res +} + +fn main() { + // sum(4); +} diff --git a/prusti-tests/tests/verify/pass/time-resources/loop.rs b/prusti-tests/tests/verify/pass/time-resources/loop.rs index 0ade55042be..cda421827dd 100644 --- a/prusti-tests/tests/verify/pass/time-resources/loop.rs +++ b/prusti-tests/tests/verify/pass/time-resources/loop.rs @@ -1,3 +1,5 @@ +// compile-flags: -Ptime_reasoning=true + use prusti_contracts::*; #[requires(time_credits(4 * n as usize + 2))] diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index 4abf70ba6cc..11064644534 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -138,6 +138,7 @@ lazy_static::lazy_static! { settings.set_default::>("dump_fold_unfold_state_of_blocks", None).unwrap(); settings.set_default("print_hash", false).unwrap(); settings.set_default("enable_cache", true).unwrap(); + settings.set_default("time_reasoning", false).unwrap(); settings.set_default("cargo_path", "cargo").unwrap(); settings.set_default("cargo_command", "check").unwrap(); @@ -363,6 +364,11 @@ pub fn check_panics() -> bool { read_setting("check_panics") } +/// When enabled, the time reasoning constrains are verified. Otherwise they are ignored. +pub fn time_reasoning() -> bool { + read_setting("time_reasoning") +} + /// When enabled, the encoded program is simplified before it is passed to /// the Viper backend. pub fn simplify_encoding() -> bool { diff --git a/prusti-viper/src/encoder/mir/pure/specifications/interface.rs b/prusti-viper/src/encoder/mir/pure/specifications/interface.rs index a4385034940..37df5a0cf8e 100644 --- a/prusti-viper/src/encoder/mir/pure/specifications/interface.rs +++ b/prusti-viper/src/encoder/mir/pure/specifications/interface.rs @@ -27,6 +27,7 @@ use crate::encoder::{ mir_encoder::{MirEncoder, PlaceEncoder, PRECONDITION_LABEL}, snapshot::interface::SnapshotEncoderInterface, }; +use prusti_common::config; use prusti_rustc_interface::{ hir::def_id::DefId, middle::{mir, ty::subst::SubstsRef}, @@ -255,14 +256,26 @@ impl<'v, 'tcx: 'v> SpecificationEncoderInterface<'tcx> for crate::encoder::Encod vir_poly::Expr::snap_app(encoded_args[0].clone()), vir_poly::Expr::snap_app(encoded_args[1].clone()), )), - "prusti_contracts::time_credits" => Ok(vir_poly::Expr::resource_access_predicate( - vir_poly::ResourceType::TimeCredits, - encoded_args[0].clone(), - )), - "prusti_contracts::time_receipts" => Ok(vir_poly::Expr::resource_access_predicate( - vir_poly::ResourceType::TimeReceipts, - encoded_args[0].clone(), - )), + "prusti_contracts::time_credits" => { + if config::time_reasoning() { + Ok(vir_poly::Expr::resource_access_predicate( + vir_poly::ResourceType::TimeCredits, + encoded_args[0].clone(), + )) + } else { + Ok(true.into()) + } + } + "prusti_contracts::time_receipts" => { + if config::time_reasoning() { + Ok(vir_poly::Expr::resource_access_predicate( + vir_poly::ResourceType::TimeReceipts, + encoded_args[0].clone(), + )) + } else { + Ok(true.into()) + } + } _ => unimplemented!(), } } From 09ef94540ce975550a4ecc289a940894693ea91f Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Tue, 29 Nov 2022 15:59:01 +0100 Subject: [PATCH 07/26] Add more test for time reasoning --- .../verify/fail/time-resources/bounds.rs | 19 +++++++++++++++++++ .../verify/fail/time-resources/functions.rs | 15 +++++++++++++++ .../tests/verify/pass/time-resources/flag.rs | 3 +++ .../verify/pass/time-resources/functions.rs | 14 ++++++++++++++ .../tests/verify/pass/time-resources/loop.rs | 4 +--- .../verify/pass/time-resources/lower_bound.rs | 14 ++++++++++++++ .../verify/pass/time-resources/upper_bonud.rs | 14 ++++++++++++++ 7 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 prusti-tests/tests/verify/fail/time-resources/bounds.rs create mode 100644 prusti-tests/tests/verify/fail/time-resources/functions.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/functions.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/lower_bound.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/upper_bonud.rs diff --git a/prusti-tests/tests/verify/fail/time-resources/bounds.rs b/prusti-tests/tests/verify/fail/time-resources/bounds.rs new file mode 100644 index 00000000000..f6750ef5f25 --- /dev/null +++ b/prusti-tests/tests/verify/fail/time-resources/bounds.rs @@ -0,0 +1,19 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +// This tests that we can't call functions only with upper bounds in a function with a lower bound specification and inversely +#[requires(time_credits(2))] +fn foo() { + constant(); +} + +#[ensures(time_receipts(1))] +fn constant() -> u32 { + 42 +} + +#[ensures(time_receipts(3))] +fn main() { + foo(); +} diff --git a/prusti-tests/tests/verify/fail/time-resources/functions.rs b/prusti-tests/tests/verify/fail/time-resources/functions.rs new file mode 100644 index 00000000000..10868352d0d --- /dev/null +++ b/prusti-tests/tests/verify/fail/time-resources/functions.rs @@ -0,0 +1,15 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +// This tests that function with time specification can only call function with time specification + +#[requires(time_credits(2))] +#[ensures(time_receipts(2))] +fn main() { + constant(); +} + +fn constant() -> u32 { + 42 +} diff --git a/prusti-tests/tests/verify/pass/time-resources/flag.rs b/prusti-tests/tests/verify/pass/time-resources/flag.rs index 3895ef3c716..cd8f0994c18 100644 --- a/prusti-tests/tests/verify/pass/time-resources/flag.rs +++ b/prusti-tests/tests/verify/pass/time-resources/flag.rs @@ -1,5 +1,8 @@ +// compile-flags: -Ptime_reasoning=false + use prusti_contracts::*; +// This test checks that setting the time reasoning option to false ignores timing constrains #[requires(time_credits(1))] #[ensures(time_receipts(1))] fn sum(n: u32) -> u32 { diff --git a/prusti-tests/tests/verify/pass/time-resources/functions.rs b/prusti-tests/tests/verify/pass/time-resources/functions.rs new file mode 100644 index 00000000000..6537f7f031c --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/functions.rs @@ -0,0 +1,14 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +#[requires(time_credits(1))] +#[ensures(time_receipts(1))] +fn foo() -> u32 { + 42 +} + +// This tests that calling a function with time constrains in a function without time constrant is okay +fn main() { + sum(4); +} diff --git a/prusti-tests/tests/verify/pass/time-resources/loop.rs b/prusti-tests/tests/verify/pass/time-resources/loop.rs index cda421827dd..22c6e0687e1 100644 --- a/prusti-tests/tests/verify/pass/time-resources/loop.rs +++ b/prusti-tests/tests/verify/pass/time-resources/loop.rs @@ -16,6 +16,4 @@ fn sum(n: u32) -> u32 { res } -fn main() { - // sum(4); -} +fn main() {} diff --git a/prusti-tests/tests/verify/pass/time-resources/lower_bound.rs b/prusti-tests/tests/verify/pass/time-resources/lower_bound.rs new file mode 100644 index 00000000000..4dc469ea6de --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/lower_bound.rs @@ -0,0 +1,14 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +// This tests that we can use function with lower bound in function without specification +fn main() { + constant(); +} + +// This tests that only specifing the lower bound is ok +#[ensures(time_receipts(1))] +fn constant() -> u32 { + 42 +} diff --git a/prusti-tests/tests/verify/pass/time-resources/upper_bonud.rs b/prusti-tests/tests/verify/pass/time-resources/upper_bonud.rs new file mode 100644 index 00000000000..26994554116 --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/upper_bonud.rs @@ -0,0 +1,14 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +// This tests that we can use function with upper bound in function without specification +fn main() { + constant(); +} + +// This tests that only specifing the upper bound is ok +#[requires(time_receipts(1))] +fn constant() -> u32 { + 42 +} From 72ab44303309108f2036addc2c27f57261439131 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Tue, 13 Dec 2022 17:41:13 +0100 Subject: [PATCH 08/26] Add new tick builtin method --- .../src/vir/optimizations/methods/purifier.rs | 7 ++-- prusti-common/src/vir/optimizations/mod.rs | 1 + .../predicates/delete_unused_predicates.rs | 7 ++-- .../verify/pass/time-resources/functions.rs | 2 +- .../tests/verify/pass/time-resources/loop.rs | 8 ++--- prusti-viper/src/encoder/builtin_encoder.rs | 7 +++- .../src/encoder/definition_collector.rs | 9 +++++ .../src/encoder/foldunfold/requirements.rs | 10 ++++-- .../mir/pure/specifications/encoder_poly.rs | 10 ++++++ .../mir/pure/specifications/interface.rs | 22 ++++-------- prusti-viper/src/encoder/mod.rs | 1 + prusti-viper/src/encoder/procedure_encoder.rs | 8 ++++- .../src/encoder/resources/interface.rs | 22 ++++++++++++ prusti-viper/src/encoder/resources/mod.rs | 36 +++++++++++++++++++ vir/defs/polymorphic/ast/common.rs | 10 ++++++ vir/defs/polymorphic/utils.rs | 13 ++++++- 16 files changed, 142 insertions(+), 31 deletions(-) create mode 100644 prusti-viper/src/encoder/resources/interface.rs create mode 100644 prusti-viper/src/encoder/resources/mod.rs diff --git a/prusti-common/src/vir/optimizations/methods/purifier.rs b/prusti-common/src/vir/optimizations/methods/purifier.rs index d9fd6cdcb2a..fbd9f497485 100644 --- a/prusti-common/src/vir/optimizations/methods/purifier.rs +++ b/prusti-common/src/vir/optimizations/methods/purifier.rs @@ -214,7 +214,9 @@ impl ast::StmtWalker for VarCollector { if is_purifiable_method(method_name) { self.is_pure_context = true; } - assert!(arguments.is_empty()); + for arg in arguments { + self.walk_expr(arg); + } for target in targets { self.walk_local_var(target); } @@ -464,8 +466,7 @@ impl ast::StmtFolder for VarPurifier { mut targets, }: ast::MethodCall, ) -> ast::Stmt { - assert!(targets.len() == 1); - if self.pure_vars.contains(&targets[0]) { + if targets.len() == 1 && self.pure_vars.contains(&targets[0]) { let target = &targets[0]; let replacement = self .replacements diff --git a/prusti-common/src/vir/optimizations/mod.rs b/prusti-common/src/vir/optimizations/mod.rs index 6c0432ec74c..8e2bc11eeac 100644 --- a/prusti-common/src/vir/optimizations/mod.rs +++ b/prusti-common/src/vir/optimizations/mod.rs @@ -122,6 +122,7 @@ pub fn optimize_program(p: Program, source_file_name: &str) -> Program { program.viper_predicates = predicates::delete_unused_predicates( &program.methods, &program.functions, + &program.builtin_methods, program.viper_predicates, ); } diff --git a/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs b/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs index 483d3c8dd4f..5047df21b33 100644 --- a/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs +++ b/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs @@ -7,7 +7,7 @@ use crate::vir::polymorphic_vir::{ ast::*, cfg::CfgMethod, - utils::{walk_functions, walk_methods}, + utils::{walk_builtin_methods, walk_functions, walk_methods}, }; use fxhash::{FxHashMap, FxHashSet}; use log::debug; @@ -15,10 +15,12 @@ use log::debug; fn collect_info_from_methods_and_functions( methods: &[CfgMethod], functions: &[Function], + buildin_methods: &[BodylessMethod], ) -> UsedPredicateCollector { let mut collector = UsedPredicateCollector::new(); walk_methods(methods, &mut collector); walk_functions(functions, &mut collector); + walk_builtin_methods(buildin_methods, &mut collector); // DeadBorrowToken$ is a used predicate but it does not appear in VIR // becaue it is only created when viper code is created from VIR @@ -108,9 +110,10 @@ fn visit_predicate( pub fn delete_unused_predicates( methods: &[CfgMethod], functions: &[Function], + buildin_methods: &[BodylessMethod], mut predicates: Vec, ) -> Vec { - let collector = collect_info_from_methods_and_functions(methods, functions); + let collector = collect_info_from_methods_and_functions(methods, functions, buildin_methods); let used_predicates_in_functions_and_methods = collector.used_predicates; let folded_predicates = collector.folded_predicates; let mut predicates_in_predicates_map = get_used_predicates_in_predicate_map(&predicates); diff --git a/prusti-tests/tests/verify/pass/time-resources/functions.rs b/prusti-tests/tests/verify/pass/time-resources/functions.rs index 6537f7f031c..22f1000a977 100644 --- a/prusti-tests/tests/verify/pass/time-resources/functions.rs +++ b/prusti-tests/tests/verify/pass/time-resources/functions.rs @@ -10,5 +10,5 @@ fn foo() -> u32 { // This tests that calling a function with time constrains in a function without time constrant is okay fn main() { - sum(4); + foo(); } diff --git a/prusti-tests/tests/verify/pass/time-resources/loop.rs b/prusti-tests/tests/verify/pass/time-resources/loop.rs index 22c6e0687e1..42f1549c5ef 100644 --- a/prusti-tests/tests/verify/pass/time-resources/loop.rs +++ b/prusti-tests/tests/verify/pass/time-resources/loop.rs @@ -2,14 +2,14 @@ use prusti_contracts::*; -#[requires(time_credits(4 * n as usize + 2))] -#[ensures(time_receipts(4 * n as usize + 2))] +#[requires(time_credits(n as usize + 1))] +#[ensures(time_receipts(n as usize + 1))] fn sum(n: u32) -> u32 { let mut i = 0; let mut res = 0; while i < n { - body_invariant!(time_credits(4 * (n - i) as usize)); - body_invariant!(time_receipts(4 * i as usize + 2)); + body_invariant!(time_credits((n - i) as usize)); + body_invariant!(time_receipts(i as usize + 1)); res += i; i += 1; } diff --git a/prusti-viper/src/encoder/builtin_encoder.rs b/prusti-viper/src/encoder/builtin_encoder.rs index c2067129cb4..346ca1c1e69 100644 --- a/prusti-viper/src/encoder/builtin_encoder.rs +++ b/prusti-viper/src/encoder/builtin_encoder.rs @@ -9,7 +9,7 @@ use vir_crate::polymorphic::{self as vir}; use vir_crate::common::identifier::WithIdentifier; use super::errors::EncodingResult; use super::high::builtin_functions::HighBuiltinFunctionEncoderInterface; -use super::versioning; +use super::{versioning, resources}; const PRIMITIVE_VALID_DOMAIN_NAME: &str = "PrimitiveValidDomain"; @@ -23,6 +23,7 @@ pub enum BuiltinMethodKind { HavocBV(vir::BitVector), HavocRef, BumpMemVersion, + Tick, } #[derive(Clone, Debug, Hash, Eq, PartialEq)] @@ -77,6 +78,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinEncoder<'p, 'v, 'tcx> { BuiltinMethodKind::HavocF64 => "builtin$havoc_f64".to_string(), BuiltinMethodKind::HavocRef => "builtin$havoc_ref".to_string(), BuiltinMethodKind::BumpMemVersion => versioning::bump_mem_version_name().to_string(), + BuiltinMethodKind::Tick => resources::tick_name().to_string(), } } @@ -92,6 +94,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinEncoder<'p, 'v, 'tcx> { BuiltinMethodKind::BumpMemVersion => { return Ok(versioning::bump_mem_version_definition()); } + BuiltinMethodKind::Tick =>{ + return resources::tick_definition(); + } }; Ok(vir::BodylessMethod { name: method_name, diff --git a/prusti-viper/src/encoder/definition_collector.rs b/prusti-viper/src/encoder/definition_collector.rs index d272e53cfc3..b67c528fefc 100644 --- a/prusti-viper/src/encoder/definition_collector.rs +++ b/prusti-viper/src/encoder/definition_collector.rs @@ -132,6 +132,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> Collector<'p, 'v, 'tcx> { self.fallible_walk_expr(body)?; } } + for vir::BodylessMethod { pres, posts, .. } in self.encoder.get_builtin_methods().values() { + // make sure we include builtin pres and posts expressions of builtin methods + for expr in pres { + self.fallible_walk_expr(expr)?; + } + for expr in posts { + self.fallible_walk_expr(expr)?; + } + } vir::utils::fallible_walk_methods(methods, self)?; self.used_predicates .extend(self.unfolded_predicates.iter().cloned()); diff --git a/prusti-viper/src/encoder/foldunfold/requirements.rs b/prusti-viper/src/encoder/foldunfold/requirements.rs index bbb5c96d099..9f677677624 100644 --- a/prusti-viper/src/encoder/foldunfold/requirements.rs +++ b/prusti-viper/src/encoder/foldunfold/requirements.rs @@ -91,13 +91,17 @@ impl RequiredStmtPermissionsGetter for vir::Stmt { ref targets, .. }) => { - // Preconditions and postconditions are empty - assert!(arguments.is_empty()); + // Preconditions and postconditions are empty or does not require permissions + let arguments_perm = arguments + .iter() + .map(|expr| expr.get_required_stmt_permissions(predicates).into_iter()) + .flatten(); FxHashSet::from_iter( targets .iter() .cloned() - .map(|v| Acc(vir::Expr::local(v), PermAmount::Write)), + .map(|v| Acc(vir::Expr::local(v), PermAmount::Write)) + .chain(arguments_perm), ) } diff --git a/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs b/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs index e0e7b13c189..62eb872f55e 100644 --- a/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs +++ b/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs @@ -133,6 +133,16 @@ pub(super) fn inline_spec_item<'tcx>( .replace_multiple_places(&body_replacements)) } +pub(super) fn encode_time_specifications<'tcx>( + amount: &vir_crate::polymorphic::Expr, + resource_type: vir_crate::polymorphic::ResourceType, +) -> vir_crate::polymorphic::Expr { + let amount_non_negative = vir_crate::polymorphic::Expr::ge_cmp(amount.clone(), 0.into()); + let resource_access_predicate = + vir_crate::polymorphic::Expr::resource_access_predicate(resource_type, amount.clone()); + vir_crate::polymorphic::Expr::and(amount_non_negative, resource_access_predicate) +} + pub(super) fn encode_quantifier<'tcx>( encoder: &Encoder<'_, 'tcx>, _span: Span, diff --git a/prusti-viper/src/encoder/mir/pure/specifications/interface.rs b/prusti-viper/src/encoder/mir/pure/specifications/interface.rs index 37df5a0cf8e..31bcbfd3fbe 100644 --- a/prusti-viper/src/encoder/mir/pure/specifications/interface.rs +++ b/prusti-viper/src/encoder/mir/pure/specifications/interface.rs @@ -19,7 +19,9 @@ use crate::encoder::{ encoder_high::{ encode_quantifier_high, inline_closure_high, inline_spec_item_high, }, - encoder_poly::{encode_quantifier, inline_closure, inline_spec_item}, + encoder_poly::{ + encode_quantifier, encode_time_specifications, inline_closure, inline_spec_item, + }, }, PureEncodingContext, }, @@ -256,21 +258,11 @@ impl<'v, 'tcx: 'v> SpecificationEncoderInterface<'tcx> for crate::encoder::Encod vir_poly::Expr::snap_app(encoded_args[0].clone()), vir_poly::Expr::snap_app(encoded_args[1].clone()), )), - "prusti_contracts::time_credits" => { - if config::time_reasoning() { - Ok(vir_poly::Expr::resource_access_predicate( - vir_poly::ResourceType::TimeCredits, - encoded_args[0].clone(), - )) - } else { - Ok(true.into()) - } - } - "prusti_contracts::time_receipts" => { + "prusti_contracts::time_credits" | "prusti_contracts::time_receipts" => { if config::time_reasoning() { - Ok(vir_poly::Expr::resource_access_predicate( - vir_poly::ResourceType::TimeReceipts, - encoded_args[0].clone(), + Ok(encode_time_specifications( + &encoded_args[0], + vir_poly::ResourceType::from_function_name(fn_name), )) } else { Ok(true.into()) diff --git a/prusti-viper/src/encoder/mod.rs b/prusti-viper/src/encoder/mod.rs index 5faec050a9b..5ffecad4c56 100644 --- a/prusti-viper/src/encoder/mod.rs +++ b/prusti-viper/src/encoder/mod.rs @@ -31,3 +31,4 @@ mod purifier; pub mod counterexamples; mod definition_collector; mod versioning; +mod resources; diff --git a/prusti-viper/src/encoder/procedure_encoder.rs b/prusti-viper/src/encoder/procedure_encoder.rs index cbbd0767652..72709138088 100644 --- a/prusti-viper/src/encoder/procedure_encoder.rs +++ b/prusti-viper/src/encoder/procedure_encoder.rs @@ -20,6 +20,7 @@ use crate::encoder::mir_encoder::PRECONDITION_LABEL; use crate::encoder::mir_successor::MirSuccessor; use crate::encoder::places::{Local, LocalVariableManager, Place}; use crate::encoder::Encoder; +use crate::encoder::resources::interface::ResourcesEncoderInterface; use crate::encoder::snapshot::interface::SnapshotEncoderInterface; use crate::encoder::mir::procedures::encoder::specification_blocks::SpecificationBlocks; use crate::error_unsupported; @@ -472,6 +473,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // Encode preconditions self.encode_preconditions(start_cfg_block, precondition_weakening)?; + if config::time_reasoning() { + // If time reasoning is enabled, add a call to tick after preconditions + self.cfg_method.add_stmt(start_cfg_block, self.encoder.get_tick_call(1)); + } + // Encode postcondition self.encode_postconditions(return_cfg_block, postcondition_strengthening)?; @@ -3406,7 +3412,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { pre_func_spec, ) = self.encode_precondition_expr(&procedure_contract, substs, fake_expr_spans)?; let pos = self.register_error(call_site_span, ErrorCtxt::ExhaleMethodPrecondition); - stmts.push(vir::Stmt::Assert( vir::Assert { + stmts.push(vir::Stmt::Exhale( vir::Exhale { expr: replace_fake_exprs(pre_func_spec), position: pos, })); diff --git a/prusti-viper/src/encoder/resources/interface.rs b/prusti-viper/src/encoder/resources/interface.rs new file mode 100644 index 00000000000..7cc8c87267b --- /dev/null +++ b/prusti-viper/src/encoder/resources/interface.rs @@ -0,0 +1,22 @@ +// © 2022, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use super::super::builtin_encoder::BuiltinMethodKind; +use vir_crate::polymorphic as vir; + +pub trait ResourcesEncoderInterface { + fn get_tick_call(&self, amount: usize) -> vir::Stmt; +} + +impl<'v, 'tcx> ResourcesEncoderInterface for super::super::Encoder<'v, 'tcx> { + fn get_tick_call(&self, amount: usize) -> vir::Stmt { + vir::Stmt::MethodCall(vir::MethodCall { + method_name: self.encode_builtin_method_use(BuiltinMethodKind::Tick), + arguments: vec![amount.into()], + targets: vec![], + }) + } +} diff --git a/prusti-viper/src/encoder/resources/mod.rs b/prusti-viper/src/encoder/resources/mod.rs new file mode 100644 index 00000000000..9a2141a577c --- /dev/null +++ b/prusti-viper/src/encoder/resources/mod.rs @@ -0,0 +1,36 @@ +// © 2022, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +pub(super) mod interface; + +use prusti_common::vir_local; +use vir_crate::polymorphic::{self as vir}; + +const TICK_NAME: &str = "builtin$tick"; + +pub fn tick_name() -> &'static str { + TICK_NAME +} + +pub fn tick_definition() -> vir::BodylessMethod { + let n = vir_local! { n: {vir::Type::Int} }; + vir::BodylessMethod { + name: TICK_NAME.to_string(), + formal_args: vec![n.clone()], + formal_returns: vec![], + pres: vec![ + vir::Expr::ge_cmp(n.clone().into(), 0.into()), + vir::Expr::resource_access_predicate( + vir::common::ResourceType::TimeCredits, + n.clone().into(), + ), + ], + posts: vec![vir::Expr::resource_access_predicate( + vir::common::ResourceType::TimeReceipts, + n.clone().into(), + )], + } +} diff --git a/vir/defs/polymorphic/ast/common.rs b/vir/defs/polymorphic/ast/common.rs index 2e0b42e8f6f..850a301eb4d 100644 --- a/vir/defs/polymorphic/ast/common.rs +++ b/vir/defs/polymorphic/ast/common.rs @@ -520,6 +520,16 @@ impl fmt::Display for ResourceType { } impl ResourceType { + pub fn from_function_name(function_name: &str) -> ResourceType { + match function_name { + "prusti_contracts::time_credits" => ResourceType::TimeCredits, + "prusti_contracts::time_receipts" => ResourceType::TimeReceipts, + _ => unreachable!( + "Fonction name: {} does not match any resource type.", + function_name + ), + } + } pub fn encode_as_string(&self) -> String { match self { ResourceType::TimeCredits => "time_credits".to_string(), diff --git a/vir/defs/polymorphic/utils.rs b/vir/defs/polymorphic/utils.rs index 5a4c1cc650d..2ef0e369ff6 100644 --- a/vir/defs/polymorphic/utils.rs +++ b/vir/defs/polymorphic/utils.rs @@ -120,6 +120,18 @@ pub fn walk_method(method: &CfgMethod, walker: &mut (impl StmtWalker + ExprWalke }); } +/// Walks all preconditions and postconditions of the given buildin methods +pub fn walk_builtin_methods(methods: &[BodylessMethod], walker: &mut impl ExprWalker) { + for BodylessMethod { pres, posts, .. } in methods { + for expr in pres { + ExprWalker::walk(walker, expr); + } + for expr in posts { + ExprWalker::walk(walker, expr); + } + } +} + /// Walks all Expressions in the provided functions (including pre and post conditions) pub fn walk_functions(functions: &[Function], walker: &mut impl ExprWalker) { for function in functions { @@ -136,7 +148,6 @@ pub fn walk_functions(functions: &[Function], walker: &mut impl ExprWalker) { } } } - /// Walks all Statements and Expressions in the provided methods pub fn fallible_walk_methods( methods: &[CfgMethod], From d341f9989f9a911eab0959a5e7d3f8354b16b8e1 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Wed, 14 Dec 2022 18:06:08 +0100 Subject: [PATCH 09/26] Add calls to tick in loops --- prusti-tests/tests/verify/pass/time-resources/loop.rs | 6 +++++- prusti-viper/src/encoder/procedure_encoder.rs | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/prusti-tests/tests/verify/pass/time-resources/loop.rs b/prusti-tests/tests/verify/pass/time-resources/loop.rs index 42f1549c5ef..dce0b00d120 100644 --- a/prusti-tests/tests/verify/pass/time-resources/loop.rs +++ b/prusti-tests/tests/verify/pass/time-resources/loop.rs @@ -16,4 +16,8 @@ fn sum(n: u32) -> u32 { res } -fn main() {} +#[requires(time_credits(12))] +#[ensures(time_receipts(12))] +fn main() { + sum(10); +} diff --git a/prusti-viper/src/encoder/procedure_encoder.rs b/prusti-viper/src/encoder/procedure_encoder.rs index 72709138088..7e245622d98 100644 --- a/prusti-viper/src/encoder/procedure_encoder.rs +++ b/prusti-viper/src/encoder/procedure_encoder.rs @@ -933,8 +933,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } // We'll add later more statements at the end of inv_pre_block, to havoc local variables let fnspec_span = { - let (stmts, fnspec_span) = + let (mut stmts, fnspec_span) = self.encode_loop_invariant_inhale_fnspec_stmts(loop_head, before_invariant_block, false)?; + if config::time_reasoning() { + stmts.push(self.encoder.get_tick_call(1)); + } self.cfg_method.add_stmts(inv_post_block_fnspc, stmts); fnspec_span }; { @@ -5385,7 +5388,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { }); stmts.extend(obtain_predicates); - stmts.push(vir::Stmt::Assert( vir::Assert { + stmts.push(vir::Stmt::Exhale( vir::Exhale { expr: func_spec.into_iter().conjoin(), position: assert_pos, })); From 9366b68ac8cc41af94b04e8abf405eb1cc4e493e Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Fri, 16 Dec 2022 11:23:28 +0100 Subject: [PATCH 10/26] Update tests --- .../tests/verify/fail/time-resources/bounds.rs | 13 +++++-------- .../tests/verify/fail/time-resources/functions.rs | 2 +- .../verify/fail/time-resources/only_lower_bound.rs | 14 ++++++++++++++ .../tests/verify/pass/time-resources/flag.rs | 4 +--- .../tests/verify/pass/time-resources/functions.rs | 3 ++- .../verify/pass/time-resources/lower_bound.rs | 14 -------------- .../verify/pass/time-resources/upper_bonud.rs | 14 -------------- 7 files changed, 23 insertions(+), 41 deletions(-) create mode 100644 prusti-tests/tests/verify/fail/time-resources/only_lower_bound.rs delete mode 100644 prusti-tests/tests/verify/pass/time-resources/lower_bound.rs delete mode 100644 prusti-tests/tests/verify/pass/time-resources/upper_bonud.rs diff --git a/prusti-tests/tests/verify/fail/time-resources/bounds.rs b/prusti-tests/tests/verify/fail/time-resources/bounds.rs index f6750ef5f25..cd9d22d6b3f 100644 --- a/prusti-tests/tests/verify/fail/time-resources/bounds.rs +++ b/prusti-tests/tests/verify/fail/time-resources/bounds.rs @@ -2,18 +2,15 @@ use prusti_contracts::*; -// This tests that we can't call functions only with upper bounds in a function with a lower bound specification and inversely -#[requires(time_credits(2))] -fn foo() { - constant(); -} +// This tests that we can't call functions only with upper bounds in a function with lower and upper bounds specification -#[ensures(time_receipts(1))] +#[requires(time_credits(1))] fn constant() -> u32 { 42 } -#[ensures(time_receipts(3))] +#[requires(time_credits(2))] +#[ensures(time_receipts(2))] fn main() { - foo(); + foo(); //~ ERROR } diff --git a/prusti-tests/tests/verify/fail/time-resources/functions.rs b/prusti-tests/tests/verify/fail/time-resources/functions.rs index 10868352d0d..1b7ddd16a18 100644 --- a/prusti-tests/tests/verify/fail/time-resources/functions.rs +++ b/prusti-tests/tests/verify/fail/time-resources/functions.rs @@ -7,7 +7,7 @@ use prusti_contracts::*; #[requires(time_credits(2))] #[ensures(time_receipts(2))] fn main() { - constant(); + constant(); //~ ERROR } fn constant() -> u32 { diff --git a/prusti-tests/tests/verify/fail/time-resources/only_lower_bound.rs b/prusti-tests/tests/verify/fail/time-resources/only_lower_bound.rs new file mode 100644 index 00000000000..77456cf8d1d --- /dev/null +++ b/prusti-tests/tests/verify/fail/time-resources/only_lower_bound.rs @@ -0,0 +1,14 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +// This tests that omitting the upper bound fails +#[ensures(time_receipts(1))] +fn main() { + let mut i = 0; + let mut _n = 0; + while i < 42 { + _n += 1; + i += 1; + } +} \ No newline at end of file diff --git a/prusti-tests/tests/verify/pass/time-resources/flag.rs b/prusti-tests/tests/verify/pass/time-resources/flag.rs index cd8f0994c18..0902ea8ed9f 100644 --- a/prusti-tests/tests/verify/pass/time-resources/flag.rs +++ b/prusti-tests/tests/verify/pass/time-resources/flag.rs @@ -3,8 +3,6 @@ use prusti_contracts::*; // This test checks that setting the time reasoning option to false ignores timing constrains -#[requires(time_credits(1))] -#[ensures(time_receipts(1))] fn sum(n: u32) -> u32 { let mut i = 0; let mut res = 0; @@ -16,5 +14,5 @@ fn sum(n: u32) -> u32 { } fn main() { - // sum(4); + sum(4); } diff --git a/prusti-tests/tests/verify/pass/time-resources/functions.rs b/prusti-tests/tests/verify/pass/time-resources/functions.rs index 22f1000a977..0ca05b5995c 100644 --- a/prusti-tests/tests/verify/pass/time-resources/functions.rs +++ b/prusti-tests/tests/verify/pass/time-resources/functions.rs @@ -8,7 +8,8 @@ fn foo() -> u32 { 42 } -// This tests that calling a function with time constrains in a function without time constrant is okay +// This tests that calling a function with time constrains in a function without time_receipts is okay +#[requires(time_credits(2))] fn main() { foo(); } diff --git a/prusti-tests/tests/verify/pass/time-resources/lower_bound.rs b/prusti-tests/tests/verify/pass/time-resources/lower_bound.rs deleted file mode 100644 index 4dc469ea6de..00000000000 --- a/prusti-tests/tests/verify/pass/time-resources/lower_bound.rs +++ /dev/null @@ -1,14 +0,0 @@ -// compile-flags: -Ptime_reasoning=true - -use prusti_contracts::*; - -// This tests that we can use function with lower bound in function without specification -fn main() { - constant(); -} - -// This tests that only specifing the lower bound is ok -#[ensures(time_receipts(1))] -fn constant() -> u32 { - 42 -} diff --git a/prusti-tests/tests/verify/pass/time-resources/upper_bonud.rs b/prusti-tests/tests/verify/pass/time-resources/upper_bonud.rs deleted file mode 100644 index 26994554116..00000000000 --- a/prusti-tests/tests/verify/pass/time-resources/upper_bonud.rs +++ /dev/null @@ -1,14 +0,0 @@ -// compile-flags: -Ptime_reasoning=true - -use prusti_contracts::*; - -// This tests that we can use function with upper bound in function without specification -fn main() { - constant(); -} - -// This tests that only specifing the upper bound is ok -#[requires(time_receipts(1))] -fn constant() -> u32 { - 42 -} From 4f9b4dae0e1893eba926fac115682f2dec3d6ca4 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Wed, 21 Dec 2022 11:26:54 +0100 Subject: [PATCH 11/26] Add more tests --- .../verify/fail/time-resources/bounds.rs | 2 +- .../tests/verify/fail/time-resources/loop.rs | 21 ++++++++ .../tests/verify/pass/time-resources/ifs.rs | 41 +++++++++++++++ .../tests/verify/pass/time-resources/impl.rs | 30 +++++++++++ .../verify/pass/time-resources/linked_list.rs | 52 +++++++++++++++++++ .../tests/verify/pass/time-resources/res.rs | 4 ++ 6 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 prusti-tests/tests/verify/fail/time-resources/loop.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/ifs.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/impl.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/linked_list.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/res.rs diff --git a/prusti-tests/tests/verify/fail/time-resources/bounds.rs b/prusti-tests/tests/verify/fail/time-resources/bounds.rs index cd9d22d6b3f..9770d8095ab 100644 --- a/prusti-tests/tests/verify/fail/time-resources/bounds.rs +++ b/prusti-tests/tests/verify/fail/time-resources/bounds.rs @@ -12,5 +12,5 @@ fn constant() -> u32 { #[requires(time_credits(2))] #[ensures(time_receipts(2))] fn main() { - foo(); //~ ERROR + constant(); //~ ERROR } diff --git a/prusti-tests/tests/verify/fail/time-resources/loop.rs b/prusti-tests/tests/verify/fail/time-resources/loop.rs new file mode 100644 index 00000000000..3c868575f42 --- /dev/null +++ b/prusti-tests/tests/verify/fail/time-resources/loop.rs @@ -0,0 +1,21 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +#[requires(time_credits(n as usize + 1))] +#[ensures(time_receipts(n as usize + 1))] //~ ERROR +fn sum(n: u32) -> u32 { + let mut i = 0; // To be correct it should be n + let mut res = 0; + while 0 < i { + body_invariant!(time_credits(i as usize)); + body_invariant!(time_receipts(1 + (n - i) as usize)); //~ ERROR + res += i; + i -= 1; + } + res +} + +#[requires(time_credits(1))] +#[ensures(time_receipts(1))] +fn main() {} diff --git a/prusti-tests/tests/verify/pass/time-resources/ifs.rs b/prusti-tests/tests/verify/pass/time-resources/ifs.rs new file mode 100644 index 00000000000..64a775daf48 --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/ifs.rs @@ -0,0 +1,41 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +#[requires(time_credits(n as usize + 1))] +#[ensures(time_receipts(n as usize + 1))] +fn sum(n: u32) -> u32 { + let mut i = n; + let mut res = 0; + while 0 < i { + body_invariant!(time_credits(i as usize)); + body_invariant!(time_receipts(1 + (n - i) as usize)); + res += i; + i -= 1; + } + res +} + +#[requires(time_credits(if b { 1 } else { n as usize + 2}))] +#[ensures(time_receipts(if b { 1 } else { n as usize + 2}))] +fn foo(b: bool, n: u32) -> u32 { + if b { + 42 + } else { + sum(n) + } +} + +#[requires(time_credits(n as usize + 2))] +#[ensures(time_receipts(1))] +fn bar(b: bool, n: u32) -> u32 { + if b { + 42 + } else { + sum(n) + } +} + +#[requires(time_credits(1))] +#[ensures(time_receipts(1))] +fn main() {} diff --git a/prusti-tests/tests/verify/pass/time-resources/impl.rs b/prusti-tests/tests/verify/pass/time-resources/impl.rs new file mode 100644 index 00000000000..e7d52e37b68 --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/impl.rs @@ -0,0 +1,30 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +#[requires(time_credits(if n <= 0 { 1 } else { n as usize + 1}, Add(...)))] +#[ensures(time_receipts(if n <= 0 { 1 } else { n as usize + 1}, Add(...)))] +fn sum(n: i32) -> i32 { + let mut i = 0; + let mut res = 0; + while i < n { + body_invariant!(time_credits((n - i) as usize)); + body_invariant!(time_receipts(i as usize + 1)); + res += i; + i += 1; + } + res +} + +#[requires(time_credits(2))] +#[ensures(time_receipts(2))] +fn foo() { + sum(-12); +} + +#[requires(time_credits(1 + 42 + 1 + 2))] +#[ensures(time_receipts(1 + 42 + 1 + 2))] +fn main() { + sum(42); + foo(); +} diff --git a/prusti-tests/tests/verify/pass/time-resources/linked_list.rs b/prusti-tests/tests/verify/pass/time-resources/linked_list.rs new file mode 100644 index 00000000000..66950eeef8a --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/linked_list.rs @@ -0,0 +1,52 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +enum LinkedList { + Empty, + Cons(T, Box>), +} + +impl LinkedList { + #[requires(time_credits(1))] + #[ensures(time_receipts(1))] + #[ensures(result.pure_len() == 0)] + fn new_empty() -> Self { + LinkedList::Empty + } + + #[requires(time_credits(1))] + #[ensures(time_receipts(1))] + #[ensures(result.pure_len() == self.pure_len() + 1)] + fn cons(self, head: T) -> Self { + LinkedList::Cons(head, Box::new(self)) + } + + #[pure] + fn pure_len(&self) -> usize { + match self { + LinkedList::Empty => 0, + LinkedList::Cons(_, tail) => tail.pure_len() + 1, + } + } + + #[requires(time_credits(self.pure_len() + 1))] + #[ensures(time_receipts(result + 1))] + #[ensures(self.pure_len() == result)] + fn len(&self) -> usize { + match self { + LinkedList::Empty => 0, + LinkedList::Cons(_, tail) => tail.len() + 1, + } + } +} + +#[requires(time_credits(7))] +#[ensures(time_receipts(7))] +fn main() { + let list = LinkedList::new_empty(); + let list1 = list.cons(1); + let list2 = list1.cons(2); + let len = list2.len(); + assert!(len == 2); +} diff --git a/prusti-tests/tests/verify/pass/time-resources/res.rs b/prusti-tests/tests/verify/pass/time-resources/res.rs new file mode 100644 index 00000000000..d7f99e27ea5 --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/res.rs @@ -0,0 +1,4 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + From dbbd4f03f2d0a2c820262e4c09575fd2d95b8ae6 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Fri, 23 Dec 2022 17:31:09 +0100 Subject: [PATCH 12/26] Remove calls to tick to an exhale and an inhale for error reporting --- prusti-viper/src/encoder/builtin_encoder.rs | 5 - .../src/encoder/errors/error_manager.rs | 122 +++++++++++------- prusti-viper/src/encoder/procedure_encoder.rs | 6 +- .../src/encoder/resources/interface.rs | 32 +++-- prusti-viper/src/encoder/resources/mod.rs | 29 ----- vir/defs/polymorphic/ast/expr.rs | 7 + 6 files changed, 109 insertions(+), 92 deletions(-) diff --git a/prusti-viper/src/encoder/builtin_encoder.rs b/prusti-viper/src/encoder/builtin_encoder.rs index 346ca1c1e69..d3fa2cef051 100644 --- a/prusti-viper/src/encoder/builtin_encoder.rs +++ b/prusti-viper/src/encoder/builtin_encoder.rs @@ -23,7 +23,6 @@ pub enum BuiltinMethodKind { HavocBV(vir::BitVector), HavocRef, BumpMemVersion, - Tick, } #[derive(Clone, Debug, Hash, Eq, PartialEq)] @@ -78,7 +77,6 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinEncoder<'p, 'v, 'tcx> { BuiltinMethodKind::HavocF64 => "builtin$havoc_f64".to_string(), BuiltinMethodKind::HavocRef => "builtin$havoc_ref".to_string(), BuiltinMethodKind::BumpMemVersion => versioning::bump_mem_version_name().to_string(), - BuiltinMethodKind::Tick => resources::tick_name().to_string(), } } @@ -94,9 +92,6 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinEncoder<'p, 'v, 'tcx> { BuiltinMethodKind::BumpMemVersion => { return Ok(versioning::bump_mem_version_definition()); } - BuiltinMethodKind::Tick =>{ - return resources::tick_definition(); - } }; Ok(vir::BodylessMethod { name: method_name, diff --git a/prusti-viper/src/encoder/errors/error_manager.rs b/prusti-viper/src/encoder/errors/error_manager.rs index 9a0a6e3804e..b9c9a1c1493 100644 --- a/prusti-viper/src/encoder/errors/error_manager.rs +++ b/prusti-viper/src/encoder/errors/error_manager.rs @@ -4,16 +4,13 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use vir_crate::polymorphic::Position; +use super::PositionManager; +use log::{debug, trace}; +use prusti_interface::{data::ProcedureDefId, PrustiError}; +use prusti_rustc_interface::{errors::MultiSpan, span::source_map::SourceMap}; use rustc_hash::FxHashMap; -use prusti_rustc_interface::span::source_map::SourceMap; -use prusti_rustc_interface::errors::MultiSpan; use viper::VerificationError; -use prusti_interface::PrustiError; -use log::{debug, trace}; -use super::PositionManager; -use prusti_interface::data::ProcedureDefId; - +use vir_crate::polymorphic::Position; /// The cause of a panic!() #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] @@ -180,6 +177,10 @@ pub enum ErrorCtxt { /// The state that fold-unfold algorithm deduced as unreachable, is actually /// reachable. UnreachableFoldingState, + // There is not enough time credtis to execute this code. + NotEnoughTimeCredits, + // The code did not create enough time receipts. + NotEnoughTimeReceipts, } /// The error manager @@ -204,7 +205,11 @@ impl<'tcx> ErrorManager<'tcx> { } /// Register a new VIR position. - pub fn register_span>(&mut self, def_id: ProcedureDefId, span: T) -> Position { + pub fn register_span>( + &mut self, + def_id: ProcedureDefId, + span: T, + ) -> Position { self.position_manager.register_span(def_id, span) } @@ -215,11 +220,20 @@ impl<'tcx> ErrorManager<'tcx> { /// Register the ErrorCtxt on an existing VIR position. pub fn set_error(&mut self, pos: Position, error_ctxt: ErrorCtxt) { - trace!("Register error {:?} at position id {:?}", error_ctxt, pos.id()); - assert_ne!(pos, Position::default(), "Trying to register an error on a default position"); + trace!( + "Register error {:?} at position id {:?}", + error_ctxt, + pos.id() + ); + assert_ne!( + pos, + 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, + existing_error_ctxt, + &error_ctxt, "An existing error context would be overwritten.\n\ Position id: {}\n\ Existing error context: {:?}\n\ @@ -235,7 +249,11 @@ impl<'tcx> ErrorManager<'tcx> { /// Creates a new position with `error_ctxt` that is linked to `pos`. This /// method is used for setting the surrounding context position of an /// expression's position. - pub fn set_surrounding_error_context(&mut self, pos: Position, error_ctxt: ErrorCtxt) -> Position { + pub fn set_surrounding_error_context( + &mut self, + pos: Position, + error_ctxt: ErrorCtxt, + ) -> Position { let surrounding_position = self.duplicate_position(pos); self.set_error(surrounding_position, error_ctxt); self.inner_positions.insert(surrounding_position.id(), pos); @@ -244,14 +262,22 @@ impl<'tcx> ErrorManager<'tcx> { /// Register a new VIR position with the given ErrorCtxt. /// Equivalent to calling `set_error` on the output of `register_span`. - pub fn register_error>(&mut self, span: T, error_ctxt: ErrorCtxt, def_id: ProcedureDefId) -> Position { + pub fn register_error>( + &mut self, + span: T, + error_ctxt: ErrorCtxt, + def_id: ProcedureDefId, + ) -> Position { let pos = self.register_span(def_id, span); + debug!("Error {:?} registered at pos {:?}", error_ctxt, pos); self.set_error(pos, error_ctxt); pos } pub fn get_def_id(&self, ver_error: &VerificationError) -> Option { - ver_error.offending_pos_id.as_ref() + ver_error + .offending_pos_id + .as_ref() .and_then(|id| id.parse().ok()) .and_then(|id| self.position_manager.def_id.get(&id).copied()) } @@ -291,9 +317,9 @@ impl<'tcx> ErrorManager<'tcx> { None => None }; - let opt_error_ctxts = opt_pos_id - .and_then(|pos_id| self.error_contexts.get(&pos_id)); - let opt_error_span = opt_pos_id.and_then(|pos_id| self.position_manager.source_span.get(&pos_id)); + let opt_error_ctxts = opt_pos_id.and_then(|pos_id| self.error_contexts.get(&pos_id)); + let opt_error_span = + opt_pos_id.and_then(|pos_id| self.position_manager.source_span.get(&pos_id)); let opt_cause_span = opt_reason_pos_id.and_then(|reason_pos_id| { let res = self.position_manager.source_span.get(&reason_pos_id); if res.is_none() { @@ -309,7 +335,7 @@ impl<'tcx> ErrorManager<'tcx> { ver_error, error_span, opt_cause_span, - error_ctxt + error_ctxt, ) } else { debug!("Unregistered verification error: {:?}", ver_error); @@ -320,32 +346,30 @@ impl<'tcx> ErrorManager<'tcx> { }; match opt_pos_id { - Some(ref pos_id) => { - PrustiError::internal( - format!( - "unregistered verification error: [{}; {}] {}", - ver_error.full_id, pos_id, ver_error.message - ), - error_span - ).set_help( - "This could be caused by too small assertion timeout. \ + Some(ref pos_id) => PrustiError::internal( + format!( + "unregistered verification error: [{}; {}] {}", + ver_error.full_id, pos_id, ver_error.message + ), + error_span, + ) + .set_help( + "This could be caused by too small assertion timeout. \ Try increasing it by setting the configuration parameter \ - ASSERT_TIMEOUT to a larger value." - ) - } - None => { - PrustiError::internal( - format!( - "unregistered verification error: [{}] {}", - ver_error.full_id, ver_error.message - ), - error_span - ).set_help( - "This could be caused by too small assertion timeout. \ + ASSERT_TIMEOUT to a larger value.", + ), + None => PrustiError::internal( + format!( + "unregistered verification error: [{}] {}", + ver_error.full_id, ver_error.message + ), + error_span, + ) + .set_help( + "This could be caused by too small assertion timeout. \ Try increasing it by setting the configuration parameter \ - ASSERT_TIMEOUT to a larger value." - ) - } + ASSERT_TIMEOUT to a larger value.", + ), } } } @@ -402,7 +426,7 @@ impl<'tcx> ErrorManager<'tcx> { .set_help("This might be a bug in the Rust compiler.") } - ("assert.failed:assertion.false", ErrorCtxt::ExhaleMethodPrecondition) => { + ("exhale.failed:assertion.false", ErrorCtxt::ExhaleMethodPrecondition) => { PrustiError::verification("precondition might not hold.", error_span) .set_failing_assertion(opt_cause_span) } @@ -431,7 +455,7 @@ impl<'tcx> ErrorManager<'tcx> { ).push_primary_span(opt_cause_span) } - ("assert.failed:assertion.false", ErrorCtxt::AssertLoopInvariantOnEntry) => { + ("exhale.failed:assertion.false", ErrorCtxt::AssertLoopInvariantOnEntry) => { PrustiError::verification("loop invariant might not hold in the first loop iteration.", error_span) .push_primary_span(opt_cause_span) } @@ -443,7 +467,7 @@ impl<'tcx> ErrorManager<'tcx> { ).push_primary_span(opt_cause_span) } - ("assert.failed:assertion.false", ErrorCtxt::AssertLoopInvariantAfterIteration) => { + ("exhale.failed:assertion.false", ErrorCtxt::AssertLoopInvariantAfterIteration) => { PrustiError::verification( "loop invariant might not hold after a loop iteration that preserves the loop condition.", error_span @@ -696,6 +720,12 @@ impl<'tcx> ErrorManager<'tcx> { error_span ) } + ("exhale.failed:insufficient.permission" | "assert.failed:insufficient.permission", ErrorCtxt::NotEnoughTimeCredits) => { + PrustiError::verification("Not enough time credits".to_string(), error_span) + } + ("exhale.failed:insufficient.permission" | "assert.failed:insufficient.permission", ErrorCtxt::NotEnoughTimeReceipts) => { + PrustiError::verification("Not enough time receipts".to_string(), error_span) + } (full_err_id, ErrorCtxt::Unexpected) => { PrustiError::internal( diff --git a/prusti-viper/src/encoder/procedure_encoder.rs b/prusti-viper/src/encoder/procedure_encoder.rs index 7e245622d98..c52c19e5f90 100644 --- a/prusti-viper/src/encoder/procedure_encoder.rs +++ b/prusti-viper/src/encoder/procedure_encoder.rs @@ -475,7 +475,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { if config::time_reasoning() { // If time reasoning is enabled, add a call to tick after preconditions - self.cfg_method.add_stmt(start_cfg_block, self.encoder.get_tick_call(1)); + self.cfg_method.add_stmts(start_cfg_block, self.get_tick_call(self.mir.span, 1)); } // Encode postcondition @@ -936,7 +936,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let (mut stmts, fnspec_span) = self.encode_loop_invariant_inhale_fnspec_stmts(loop_head, before_invariant_block, false)?; if config::time_reasoning() { - stmts.push(self.encoder.get_tick_call(1)); + stmts.extend(self.get_tick_call(fnspec_span.clone(), 1).into_iter()); } self.cfg_method.add_stmts(inv_post_block_fnspc, stmts); fnspec_span }; @@ -7001,7 +7001,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { }) } - fn register_error>(&self, span: T, error_ctxt: ErrorCtxt) -> vir::Position { + pub fn register_error>(&self, span: T, error_ctxt: ErrorCtxt) -> vir::Position { self.mir_encoder.register_error(span, error_ctxt) } } diff --git a/prusti-viper/src/encoder/resources/interface.rs b/prusti-viper/src/encoder/resources/interface.rs index 7cc8c87267b..ff3cb8317db 100644 --- a/prusti-viper/src/encoder/resources/interface.rs +++ b/prusti-viper/src/encoder/resources/interface.rs @@ -4,19 +4,33 @@ // 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 super::super::builtin_encoder::BuiltinMethodKind; +use super::super::errors::ErrorCtxt; +use prusti_rustc_interface::errors::MultiSpan; use vir_crate::polymorphic as vir; pub trait ResourcesEncoderInterface { - fn get_tick_call(&self, amount: usize) -> vir::Stmt; + fn get_tick_call>(&self, span: T, amount: usize) -> Vec; } -impl<'v, 'tcx> ResourcesEncoderInterface for super::super::Encoder<'v, 'tcx> { - fn get_tick_call(&self, amount: usize) -> vir::Stmt { - vir::Stmt::MethodCall(vir::MethodCall { - method_name: self.encode_builtin_method_use(BuiltinMethodKind::Tick), - arguments: vec![amount.into()], - targets: vec![], - }) +impl<'p, 'v: 'p, 'tcx: 'v> ResourcesEncoderInterface + for super::super::procedure_encoder::ProcedureEncoder<'p, 'v, 'tcx> +{ + fn get_tick_call>(&self, span: T, amount: usize) -> Vec { + let pos = self.register_error(span, ErrorCtxt::NotEnoughTimeCredits); + vec![ + vir::Stmt::Exhale(vir::Exhale { + expr: vir::Expr::resource_access_predicate( + vir::common::ResourceType::TimeCredits, + amount.into(), + ).set_pos(pos), + position: pos, + }), + vir::Stmt::Inhale(vir::Inhale { + expr: vir::Expr::resource_access_predicate( + vir::common::ResourceType::TimeReceipts, + amount.into(), + ), + }), + ] } } diff --git a/prusti-viper/src/encoder/resources/mod.rs b/prusti-viper/src/encoder/resources/mod.rs index 9a2141a577c..f3e200b468f 100644 --- a/prusti-viper/src/encoder/resources/mod.rs +++ b/prusti-viper/src/encoder/resources/mod.rs @@ -5,32 +5,3 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. pub(super) mod interface; - -use prusti_common::vir_local; -use vir_crate::polymorphic::{self as vir}; - -const TICK_NAME: &str = "builtin$tick"; - -pub fn tick_name() -> &'static str { - TICK_NAME -} - -pub fn tick_definition() -> vir::BodylessMethod { - let n = vir_local! { n: {vir::Type::Int} }; - vir::BodylessMethod { - name: TICK_NAME.to_string(), - formal_args: vec![n.clone()], - formal_returns: vec![], - pres: vec![ - vir::Expr::ge_cmp(n.clone().into(), 0.into()), - vir::Expr::resource_access_predicate( - vir::common::ResourceType::TimeCredits, - n.clone().into(), - ), - ], - posts: vec![vir::Expr::resource_access_predicate( - vir::common::ResourceType::TimeReceipts, - n.clone().into(), - )], - } -} diff --git a/vir/defs/polymorphic/ast/expr.rs b/vir/defs/polymorphic/ast/expr.rs index 2ec8c99812b..4169a94d447 100644 --- a/vir/defs/polymorphic/ast/expr.rs +++ b/vir/defs/polymorphic/ast/expr.rs @@ -826,6 +826,12 @@ impl Expr { ) { self.non_pure = true; } + fn walk_resource_access_predicate( + &mut self, + _resource_access_predicate: &ResourceAccessPredicate, + ) { + self.non_pure = true; + } } let mut walker = PurityFinder { non_pure: false }; walker.walk(self); @@ -1143,6 +1149,7 @@ impl Expr { return_type: Type::Bool, .. }) + | Expr::ResourceAccessPredicate(..) | Expr::ForAll(..) | Expr::Exists(..) => true, Expr::BinOp(BinOp { op_kind, .. }) => { From 4881b52e8685ffd37bb548b851e819da1b337174 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Fri, 23 Dec 2022 17:31:32 +0100 Subject: [PATCH 13/26] More tests and add an expression simplifier for methods Implications are brocken with time credits and receipts contraints as they are impure but Prusti translate them into ors which viper doesn't accept. Simplifying those expression solve this issue as they are changed back to an implication. --- .../vir/optimizations/functions/simplifier.rs | 99 +++++++++---------- .../src/vir/optimizations/methods/mod.rs | 4 +- .../vir/optimizations/methods/simplifier.rs | 42 ++++++++ .../tests/verify/fail/time-resources/loop.rs | 9 +- .../tests/verify/pass/time-resources/impl.rs | 20 +++- .../tests/verify/pass/time-resources/loop.rs | 14 +++ prusti-utils/src/config.rs | 3 + 7 files changed, 132 insertions(+), 59 deletions(-) create mode 100644 prusti-common/src/vir/optimizations/methods/simplifier.rs diff --git a/prusti-common/src/vir/optimizations/functions/simplifier.rs b/prusti-common/src/vir/optimizations/functions/simplifier.rs index 0bfeca90f26..199ac89ab1d 100644 --- a/prusti-common/src/vir/optimizations/functions/simplifier.rs +++ b/prusti-common/src/vir/optimizations/functions/simplifier.rs @@ -20,8 +20,9 @@ impl Simplifier for ast::Function { /// fn simplify(mut self) -> Self { trace!("[enter] simplify = {}", self); - let new_body = self.body.map(|b| b.simplify()); - self.body = new_body; + self.body = self.body.map(|b| b.simplify()); + self.posts = self.posts.into_iter().map(|p| p.simplify()).collect(); + self.pres = self.pres.into_iter().map(|p| p.simplify()).collect(); trace!("[exit] simplify = {}", self); self } @@ -30,8 +31,11 @@ impl Simplifier for ast::Function { impl Simplifier for ast::Expr { #[must_use] fn simplify(self) -> Self { + trace!("[enter] simplify = {:?}", self); let mut folder = ExprSimplifier {}; - folder.fold(self) + let res = folder.fold(self); + trace!("[exit] simplify = {:?}", res); + res } } @@ -46,13 +50,14 @@ impl ExprSimplifier { argument: box ast::Expr::Const(ast::ConstExpr { value: ast::Const::Bool(b), - .. + position: inner_pos, }), position: pos, }) => ast::Expr::Const(ast::ConstExpr { value: ast::Const::Bool(!b), position: pos, - }), + }) + .set_default_pos(inner_pos), ast::Expr::UnaryOp(ast::UnaryOp { op_kind: ast::UnaryOpKind::Not, argument: @@ -60,7 +65,7 @@ impl ExprSimplifier { op_kind: ast::BinaryOpKind::EqCmp, box left, box right, - .. + position: inner_pos, }), position: pos, }) => ast::Expr::BinOp(ast::BinOp { @@ -68,33 +73,36 @@ impl ExprSimplifier { left: box left, right: box right, position: pos, - }), + }) + .set_default_pos(inner_pos), ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::And, left: box ast::Expr::Const(ast::ConstExpr { value: ast::Const::Bool(b1), - .. + position: inner_pos1, }), right: box ast::Expr::Const(ast::ConstExpr { value: ast::Const::Bool(b2), - .. + position: inner_pos2, }), position: pos, }) => ast::Expr::Const(ast::ConstExpr { value: ast::Const::Bool(b1 && b2), position: pos, - }), + }) + .set_default_pos(inner_pos1) + .set_default_pos(inner_pos2), ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::And, left: box ast::Expr::Const(ast::ConstExpr { value: ast::Const::Bool(b), - .. + position: inner_pos, }), right: box conjunct, - .. + position: pos, }) | ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::And, @@ -102,25 +110,21 @@ impl ExprSimplifier { right: box ast::Expr::Const(ast::ConstExpr { value: ast::Const::Bool(b), - .. + position: inner_pos, }), - .. - }) => { - if b { - conjunct - } else { - false.into() - } - } + position: pos, + }) => if b { conjunct } else { false.into() } + .set_pos(pos) + .set_default_pos(inner_pos), ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::Or, left: box ast::Expr::Const(ast::ConstExpr { value: ast::Const::Bool(b), - .. + position: inner_pos, }), right: box disjunct, - .. + position: pos, }) | ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::Or, @@ -128,52 +132,43 @@ impl ExprSimplifier { right: box ast::Expr::Const(ast::ConstExpr { value: ast::Const::Bool(b), - .. + position: inner_pos, }), - .. - }) => { - if b { - true.into() - } else { - disjunct - } - } + position: pos, + }) => if b { true.into() } else { disjunct } + .set_pos(pos) + .set_default_pos(inner_pos), ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::Implies, left: guard, right: box ast::Expr::Const(ast::ConstExpr { value: ast::Const::Bool(b), - .. + position: inner_pos, }), position: pos, - }) => { - if b { - true.into() - } else { - ast::Expr::UnaryOp(ast::UnaryOp { - op_kind: ast::UnaryOpKind::Not, - argument: guard, - position: pos, - }) - } + }) => if b { + Into::::into(true).set_pos(pos) + } else { + ast::Expr::UnaryOp(ast::UnaryOp { + op_kind: ast::UnaryOpKind::Not, + argument: guard, + position: pos, + }) } + .set_default_pos(inner_pos), ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::Implies, left: box ast::Expr::Const(ast::ConstExpr { value: ast::Const::Bool(b), - .. + position: inner_pos, }), right: box body, - .. - }) => { - if b { - body - } else { - true.into() - } - } + position: pos, + }) => if b { body } else { true.into() } + .set_pos(pos) + .set_default_pos(inner_pos), ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::And, left: box op1, diff --git a/prusti-common/src/vir/optimizations/methods/mod.rs b/prusti-common/src/vir/optimizations/methods/mod.rs index 5d1abf78ae9..be75e899679 100644 --- a/prusti-common/src/vir/optimizations/methods/mod.rs +++ b/prusti-common/src/vir/optimizations/methods/mod.rs @@ -13,6 +13,7 @@ mod purifier; mod quantifier_fixer; mod unfolding_fixer; mod var_remover; +mod simplifier; use super::log_method; use crate::{config::Optimizations, vir::polymorphic_vir::cfg::CfgMethod}; @@ -20,7 +21,7 @@ use crate::{config::Optimizations, vir::polymorphic_vir::cfg::CfgMethod}; use self::{ assert_remover::remove_trivial_assertions, cfg_cleaner::clean_cfg, empty_if_remover::remove_empty_if, purifier::purify_vars, quantifier_fixer::fix_quantifiers, - unfolding_fixer::fix_unfoldings, var_remover::remove_unused_vars, + simplifier::simplify_expr, unfolding_fixer::fix_unfoldings, var_remover::remove_unused_vars, }; #[allow(clippy::let_and_return)] @@ -57,6 +58,7 @@ pub fn optimize_method_encoding( }; let cfg = apply!(fix_unfoldings, cfg); let cfg = apply!(fix_quantifiers, cfg); + let cfg = apply!(simplify_expr, cfg); let cfg = apply!(remove_empty_if, cfg); let cfg = apply!(remove_unused_vars, cfg); let cfg = apply!(remove_trivial_assertions, cfg); diff --git a/prusti-common/src/vir/optimizations/methods/simplifier.rs b/prusti-common/src/vir/optimizations/methods/simplifier.rs new file mode 100644 index 00000000000..ccbb6106311 --- /dev/null +++ b/prusti-common/src/vir/optimizations/methods/simplifier.rs @@ -0,0 +1,42 @@ +// © 2022, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use ::vir::polymorphic::*; +use log::debug; +use std::mem; + +use crate::vir::optimizations::functions::Simplifier; + +/// This optimization transformes ors were the RHS is impure into implications. +/// This happens in the case of an implication with a resource access predicate in the RHS. + +pub fn simplify_expr(mut cfg: CfgMethod) -> CfgMethod { + debug!("Simplifying exprs in {}", cfg.name()); + let mut sentinel_stmt = Stmt::comment("moved out stmt"); + for block in &mut cfg.basic_blocks { + for stmt in &mut block.stmts { + mem::swap(&mut sentinel_stmt, stmt); + sentinel_stmt = sentinel_stmt.simplify(); + mem::swap(&mut sentinel_stmt, stmt); + } + } + cfg +} + +struct StmtSimplifier; + +impl Simplifier for Stmt { + fn simplify(self) -> Self { + let mut folder = StmtSimplifier; + folder.fold(self) + } +} + +impl StmtFolder for StmtSimplifier { + fn fold_expr(&mut self, expr: Expr) -> Expr { + expr.simplify() + } +} diff --git a/prusti-tests/tests/verify/fail/time-resources/loop.rs b/prusti-tests/tests/verify/fail/time-resources/loop.rs index 3c868575f42..b53d292ab2d 100644 --- a/prusti-tests/tests/verify/fail/time-resources/loop.rs +++ b/prusti-tests/tests/verify/fail/time-resources/loop.rs @@ -2,14 +2,15 @@ use prusti_contracts::*; -#[requires(time_credits(n as usize + 1))] -#[ensures(time_receipts(n as usize + 1))] //~ ERROR +// #[requires(n > 0)] +#[requires(time_credits(n as usize + 2))] +#[ensures(time_receipts(n as usize + 1))] //~ ERROR Not enough time receipts fn sum(n: u32) -> u32 { - let mut i = 0; // To be correct it should be n + let mut i = 1; // To be correct it should be n let mut res = 0; while 0 < i { body_invariant!(time_credits(i as usize)); - body_invariant!(time_receipts(1 + (n - i) as usize)); //~ ERROR + body_invariant!(time_receipts(1 + (n - i) as usize)); //~ ERROR Not enough time receipts res += i; i -= 1; } diff --git a/prusti-tests/tests/verify/pass/time-resources/impl.rs b/prusti-tests/tests/verify/pass/time-resources/impl.rs index e7d52e37b68..59a38479062 100644 --- a/prusti-tests/tests/verify/pass/time-resources/impl.rs +++ b/prusti-tests/tests/verify/pass/time-resources/impl.rs @@ -2,8 +2,8 @@ use prusti_contracts::*; -#[requires(time_credits(if n <= 0 { 1 } else { n as usize + 1}, Add(...)))] -#[ensures(time_receipts(if n <= 0 { 1 } else { n as usize + 1}, Add(...)))] +#[requires(time_credits(if n <= 0 { 1 } else { n as usize + 1}))] +#[ensures(time_receipts(if n <= 0 { 1 } else { n as usize + 1}))] fn sum(n: i32) -> i32 { let mut i = 0; let mut res = 0; @@ -16,6 +16,22 @@ fn sum(n: i32) -> i32 { res } +#[requires(n <= 0 ==> time_credits(1))] +#[requires(n > 0 ==> time_credits(n as usize + 1))] +#[ensures(n <= 0 ==> time_receipts(1))] +#[ensures(n > 0 ==> time_receipts(n as usize + 1))] +fn sum2(n: i32) -> i32 { + let mut i = 0; + let mut res = 0; + while i < n { + body_invariant!(time_credits((n - i) as usize)); + body_invariant!(time_receipts(i as usize + 1)); + res += i; + i += 1; + } + res +} + #[requires(time_credits(2))] #[ensures(time_receipts(2))] fn foo() { diff --git a/prusti-tests/tests/verify/pass/time-resources/loop.rs b/prusti-tests/tests/verify/pass/time-resources/loop.rs index dce0b00d120..eda534d8b6b 100644 --- a/prusti-tests/tests/verify/pass/time-resources/loop.rs +++ b/prusti-tests/tests/verify/pass/time-resources/loop.rs @@ -16,6 +16,20 @@ fn sum(n: u32) -> u32 { res } +#[requires(time_credits(n as usize + 1))] +#[ensures(time_receipts(n as usize + 1))] +fn sum2(n: u32) -> u32 { + let mut i = n; // To be correct it should be n + let mut res = 0; + while 0 < i { + body_invariant!(time_credits(i as usize)); + body_invariant!(time_receipts(1 + (n - i) as usize)); + res += i; + i -= 1; + } + res +} + #[requires(time_credits(12))] #[ensures(time_receipts(12))] fn main() { diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index 11064644534..c7586fc72cc 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -23,6 +23,7 @@ pub struct Optimizations { pub purify_vars: bool, pub fix_quantifiers: bool, pub fix_unfoldings: bool, + pub simplify_expr: bool, pub remove_unused_vars: bool, pub remove_trivial_assertions: bool, pub clean_cfg: bool, @@ -38,6 +39,7 @@ impl Optimizations { purify_vars: false, fix_quantifiers: false, fix_unfoldings: false, + simplify_expr: false, remove_unused_vars: false, remove_trivial_assertions: false, clean_cfg: false, @@ -54,6 +56,7 @@ impl Optimizations { fix_quantifiers: true, // Disabled because https://github.com/viperproject/prusti-dev/issues/892 has been fixed fix_unfoldings: false, + simplify_expr: true, remove_unused_vars: true, remove_trivial_assertions: true, clean_cfg: true, From 659b98f241b088b2459d2cb74fc9688cfc2ae199 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Fri, 23 Dec 2022 17:50:04 +0100 Subject: [PATCH 14/26] Formating --- prusti-common/src/vir/optimizations/methods/simplifier.rs | 2 +- prusti-viper/src/encoder/builtin_encoder.rs | 2 +- vir/defs/polymorphic/ast/expr.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/prusti-common/src/vir/optimizations/methods/simplifier.rs b/prusti-common/src/vir/optimizations/methods/simplifier.rs index ccbb6106311..1db69a7f8ab 100644 --- a/prusti-common/src/vir/optimizations/methods/simplifier.rs +++ b/prusti-common/src/vir/optimizations/methods/simplifier.rs @@ -4,9 +4,9 @@ // 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 ::vir::polymorphic::*; use log::debug; use std::mem; +use vir::polymorphic::*; use crate::vir::optimizations::functions::Simplifier; diff --git a/prusti-viper/src/encoder/builtin_encoder.rs b/prusti-viper/src/encoder/builtin_encoder.rs index d3fa2cef051..c2067129cb4 100644 --- a/prusti-viper/src/encoder/builtin_encoder.rs +++ b/prusti-viper/src/encoder/builtin_encoder.rs @@ -9,7 +9,7 @@ use vir_crate::polymorphic::{self as vir}; use vir_crate::common::identifier::WithIdentifier; use super::errors::EncodingResult; use super::high::builtin_functions::HighBuiltinFunctionEncoderInterface; -use super::{versioning, resources}; +use super::versioning; const PRIMITIVE_VALID_DOMAIN_NAME: &str = "PrimitiveValidDomain"; diff --git a/vir/defs/polymorphic/ast/expr.rs b/vir/defs/polymorphic/ast/expr.rs index 4169a94d447..5e25a29c188 100644 --- a/vir/defs/polymorphic/ast/expr.rs +++ b/vir/defs/polymorphic/ast/expr.rs @@ -827,7 +827,7 @@ impl Expr { self.non_pure = true; } fn walk_resource_access_predicate( - &mut self, + &mut self, _resource_access_predicate: &ResourceAccessPredicate, ) { self.non_pure = true; From e958a4a381404752865c99b71c24089e4f6d2b98 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Tue, 3 Jan 2023 17:40:33 +0100 Subject: [PATCH 15/26] Add position to time credits/receipts expr --- .../mir/pure/specifications/encoder_poly.rs | 19 +++++++++++++++++-- .../mir/pure/specifications/interface.rs | 3 +++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs b/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs index 62eb872f55e..7ae315d8c88 100644 --- a/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs +++ b/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs @@ -6,7 +6,7 @@ use crate::{ encoder::{ - errors::{EncodingError, EncodingResult, SpannedEncodingResult, WithSpan}, + errors::{EncodingError, EncodingResult, ErrorCtxt, SpannedEncodingResult, WithSpan}, high::types::HighTypeEncoderInterface, mir::{ pure::{specifications::utils::extract_closure_from_ty, PureFunctionEncoderInterface}, @@ -133,13 +133,28 @@ pub(super) fn inline_spec_item<'tcx>( .replace_multiple_places(&body_replacements)) } +fn error_ctxt(resource_type: &vir_crate::polymorphic::ResourceType) -> ErrorCtxt { + match resource_type { + vir_crate::polymorphic::ResourceType::TimeCredits => ErrorCtxt::NotEnoughTimeCredits, + vir_crate::polymorphic::ResourceType::TimeReceipts => ErrorCtxt::NotEnoughTimeReceipts, + } +} + pub(super) fn encode_time_specifications<'tcx>( + encoder: &Encoder<'_, 'tcx>, amount: &vir_crate::polymorphic::Expr, resource_type: vir_crate::polymorphic::ResourceType, + parent_def_id: DefId, + span: Span, ) -> vir_crate::polymorphic::Expr { + let pos = + encoder + .error_manager() + .register_error(span, error_ctxt(&resource_type), parent_def_id); let amount_non_negative = vir_crate::polymorphic::Expr::ge_cmp(amount.clone(), 0.into()); let resource_access_predicate = - vir_crate::polymorphic::Expr::resource_access_predicate(resource_type, amount.clone()); + vir_crate::polymorphic::Expr::resource_access_predicate(resource_type, amount.clone()) + .set_pos(pos); vir_crate::polymorphic::Expr::and(amount_non_negative, resource_access_predicate) } diff --git a/prusti-viper/src/encoder/mir/pure/specifications/interface.rs b/prusti-viper/src/encoder/mir/pure/specifications/interface.rs index 31bcbfd3fbe..a639687dfbc 100644 --- a/prusti-viper/src/encoder/mir/pure/specifications/interface.rs +++ b/prusti-viper/src/encoder/mir/pure/specifications/interface.rs @@ -261,8 +261,11 @@ impl<'v, 'tcx: 'v> SpecificationEncoderInterface<'tcx> for crate::encoder::Encod "prusti_contracts::time_credits" | "prusti_contracts::time_receipts" => { if config::time_reasoning() { Ok(encode_time_specifications( + self, &encoded_args[0], vir_poly::ResourceType::from_function_name(fn_name), + parent_def_id, + span, )) } else { Ok(true.into()) From c36040b7757e2bbc6e0dd2fd39524f6b88fc2666 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Wed, 4 Jan 2023 13:56:52 +0100 Subject: [PATCH 16/26] Add temporary error reporting --- .../src/encoder/errors/error_manager.rs | 27 +++++++++++++++++-- .../src/encoder/resources/interface.rs | 2 ++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/prusti-viper/src/encoder/errors/error_manager.rs b/prusti-viper/src/encoder/errors/error_manager.rs index b9c9a1c1493..8a1c516f561 100644 --- a/prusti-viper/src/encoder/errors/error_manager.rs +++ b/prusti-viper/src/encoder/errors/error_manager.rs @@ -721,10 +721,33 @@ impl<'tcx> ErrorManager<'tcx> { ) } ("exhale.failed:insufficient.permission" | "assert.failed:insufficient.permission", ErrorCtxt::NotEnoughTimeCredits) => { - PrustiError::verification("Not enough time credits".to_string(), error_span) + PrustiError::verification("Not enough time credits.".to_string(), error_span) } ("exhale.failed:insufficient.permission" | "assert.failed:insufficient.permission", ErrorCtxt::NotEnoughTimeReceipts) => { - PrustiError::verification("Not enough time receipts".to_string(), error_span) + PrustiError::verification("Not enough time receipts.".to_string(), error_span) + } + + // TODO: this is a temporary fix for detecting time credits/receipts errors + ("exhale.failed:insufficient.permission", ErrorCtxt::ExhaleMethodPrecondition) if ver_error.message.contains("There might be insufficient permission to access time_credits()") => { + PrustiError::verification("Not enough time credits to call function.".to_string(), error_span) + } + ("assert.failed:insufficient.permission", ErrorCtxt::AssertMethodPostcondition) if ver_error.message.contains("There might be insufficient permission to access time_receipts()") => { + PrustiError::verification("Not enough time receipts at the end of the function.".to_string(), error_span) + } + ("assert.failed:insufficient.permission", ErrorCtxt::AssertMethodPostcondition) if ver_error.message.contains("There might be insufficient permission to access time_credits()") => { + PrustiError::verification("Not enough time credits at the end of the function.".to_string(), error_span) + } + ("exhale.failed:insufficient.permission", ErrorCtxt::AssertLoopInvariantOnEntry) if ver_error.message.contains("There might be insufficient permission to access time_credits()") => { + PrustiError::verification("Not enough time credits for invariant in the first loop iteration.".to_string(), error_span) + } + ("exhale.failed:insufficient.permission", ErrorCtxt::AssertLoopInvariantOnEntry) if ver_error.message.contains("There might be insufficient permission to access time_receipts()") => { + PrustiError::verification("Not enough time receipts for invariant in the first loop iteration.".to_string(), error_span) + } + ("exhale.failed:insufficient.permission", ErrorCtxt::AssertLoopInvariantAfterIteration) if ver_error.message.contains("There might be insufficient permission to access time_credits()") => { + PrustiError::verification("Not enough time credits for invariant after a loop iteration that preserves the loop condition.".to_string(), error_span) + } + ("exhale.failed:insufficient.permission", ErrorCtxt::AssertLoopInvariantAfterIteration) if ver_error.message.contains("There might be insufficient permission to access time_receipts()") => { + PrustiError::verification("Not enough time receipts for invariant after a loop iteration that preserves the loop condition.".to_string(), error_span) } (full_err_id, ErrorCtxt::Unexpected) => { diff --git a/prusti-viper/src/encoder/resources/interface.rs b/prusti-viper/src/encoder/resources/interface.rs index ff3cb8317db..65c8278f98b 100644 --- a/prusti-viper/src/encoder/resources/interface.rs +++ b/prusti-viper/src/encoder/resources/interface.rs @@ -12,6 +12,8 @@ pub trait ResourcesEncoderInterface { fn get_tick_call>(&self, span: T, amount: usize) -> Vec; } +// TODO: differentiate between loops' and functions' bodies errors + impl<'p, 'v: 'p, 'tcx: 'v> ResourcesEncoderInterface for super::super::procedure_encoder::ProcedureEncoder<'p, 'v, 'tcx> { From 5ba2c1f7b3bcf9ad6c2ec69fc16dcd0c827c6e4e Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Wed, 4 Jan 2023 15:03:01 +0100 Subject: [PATCH 17/26] Add more complex tests --- .../verify/fail/time-resources/array_cmp.rs | 20 +++++++ .../verify/fail/time-resources/bounds.rs | 7 +-- .../verify/fail/time-resources/constant.rs | 10 +++- .../time-resources/ensures_time_credits.rs | 27 +++++++++ .../verify/fail/time-resources/functions.rs | 8 +-- .../tests/verify/fail/time-resources/loop.rs | 48 ++++++++++++++-- .../fail/time-resources/negative_amount.rs | 12 +++- .../fail/time-resources/only_lower_bound.rs | 2 +- .../verify/pass/time-resources/array_2d.rs | 28 ++++++++++ .../verify/pass/time-resources/array_cmp.rs | 34 +++++++++++ .../time-resources/ensures_time_credits.rs | 41 ++++++++++++++ .../verify/pass/time-resources/linked_list.rs | 1 + .../tests/verify/pass/time-resources/loop.rs | 56 ++++++++++++++++++- .../tests/verify/pass/time-resources/rec.rs | 25 +++++++++ .../tests/verify/pass/time-resources/res.rs | 35 ++++++++++++ 15 files changed, 335 insertions(+), 19 deletions(-) create mode 100644 prusti-tests/tests/verify/fail/time-resources/array_cmp.rs create mode 100644 prusti-tests/tests/verify/fail/time-resources/ensures_time_credits.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/array_2d.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/array_cmp.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/ensures_time_credits.rs create mode 100644 prusti-tests/tests/verify/pass/time-resources/rec.rs diff --git a/prusti-tests/tests/verify/fail/time-resources/array_cmp.rs b/prusti-tests/tests/verify/fail/time-resources/array_cmp.rs new file mode 100644 index 00000000000..5a4e729f8d1 --- /dev/null +++ b/prusti-tests/tests/verify/fail/time-resources/array_cmp.rs @@ -0,0 +1,20 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +#[requires(a.len() == b.len())] +#[requires(time_credits(a.len() + 1))] +#[ensures(time_receipts(a.len() + 1))] +fn opt_array_cmp(a: &[T], b: &[T]) -> bool { //~ ERROR Not enough time receipts at the end of the function. + let mut i = 0; + while i < a.len() { + if a[i] != b[i] { + return false; + } + i += 1; + } + return true; +} + +#[requires(time_credits(1))] +fn main() {} \ No newline at end of file diff --git a/prusti-tests/tests/verify/fail/time-resources/bounds.rs b/prusti-tests/tests/verify/fail/time-resources/bounds.rs index 9770d8095ab..f08dab2b215 100644 --- a/prusti-tests/tests/verify/fail/time-resources/bounds.rs +++ b/prusti-tests/tests/verify/fail/time-resources/bounds.rs @@ -3,14 +3,13 @@ use prusti_contracts::*; // This tests that we can't call functions only with upper bounds in a function with lower and upper bounds specification - #[requires(time_credits(1))] fn constant() -> u32 { 42 } #[requires(time_credits(2))] -#[ensures(time_receipts(2))] -fn main() { - constant(); //~ ERROR +#[ensures(time_receipts(2))] +fn main() { //~ ERROR Not enough time receipts at the end of the function. + constant(); } diff --git a/prusti-tests/tests/verify/fail/time-resources/constant.rs b/prusti-tests/tests/verify/fail/time-resources/constant.rs index 7dc52d7b221..5dcc9e367f3 100644 --- a/prusti-tests/tests/verify/fail/time-resources/constant.rs +++ b/prusti-tests/tests/verify/fail/time-resources/constant.rs @@ -2,8 +2,12 @@ use prusti_contracts::*; -#[requires(time_credits(0))] //~ ERROR -#[ensures(time_receipts(4))] //~ ERROR -fn main() -> () { +#[requires(time_credits(0))] +#[ensures(time_receipts(1))] +fn main() -> () { //~ ERROR Not enough time credits. () } + +#[requires(time_credits(1))] +#[ensures(time_receipts(4))] +fn foo() {} //~ ERROR Not enough time receipts at the end of the function. diff --git a/prusti-tests/tests/verify/fail/time-resources/ensures_time_credits.rs b/prusti-tests/tests/verify/fail/time-resources/ensures_time_credits.rs new file mode 100644 index 00000000000..b28308c099f --- /dev/null +++ b/prusti-tests/tests/verify/fail/time-resources/ensures_time_credits.rs @@ -0,0 +1,27 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +enum LinkedList { + Empty, + Cons(T, Box>), +} + +impl LinkedList { + #[requires(time_credits(bound + 1))] + #[ensures(time_receipts(result))] + #[ensures(time_credits(bound - result + 1))] + fn bounded_len(&self, bound: usize) -> usize { //~ ERROR Not enough time credits at the end of the function. + if bound == 0 { + 0 + } else { + match self { + LinkedList::Empty => 0, + LinkedList::Cons(_, tail) => tail.bounded_len(bound - 1) + 1, + } + } + } +} + +#[requires(time_credits(1))] +fn main() {} diff --git a/prusti-tests/tests/verify/fail/time-resources/functions.rs b/prusti-tests/tests/verify/fail/time-resources/functions.rs index 1b7ddd16a18..798066dce1c 100644 --- a/prusti-tests/tests/verify/fail/time-resources/functions.rs +++ b/prusti-tests/tests/verify/fail/time-resources/functions.rs @@ -5,11 +5,11 @@ use prusti_contracts::*; // This tests that function with time specification can only call function with time specification #[requires(time_credits(2))] -#[ensures(time_receipts(2))] -fn main() { - constant(); //~ ERROR +#[ensures(time_receipts(2))] +fn main() { //~ ERROR Not enough time receipts at the end of the function. + constant(); } -fn constant() -> u32 { +fn constant() -> u32 { //~ ERROR Not enough time credits 42 } diff --git a/prusti-tests/tests/verify/fail/time-resources/loop.rs b/prusti-tests/tests/verify/fail/time-resources/loop.rs index b53d292ab2d..70ed3772324 100644 --- a/prusti-tests/tests/verify/fail/time-resources/loop.rs +++ b/prusti-tests/tests/verify/fail/time-resources/loop.rs @@ -2,16 +2,56 @@ use prusti_contracts::*; -// #[requires(n > 0)] +// TODO: specify the error as: Not enough time receipts/credits + #[requires(time_credits(n as usize + 2))] -#[ensures(time_receipts(n as usize + 1))] //~ ERROR Not enough time receipts +#[ensures(time_receipts(n as usize + 1))] // This is also an error but the other is reported first. fn sum(n: u32) -> u32 { let mut i = 1; // To be correct it should be n let mut res = 0; while 0 < i { - body_invariant!(time_credits(i as usize)); - body_invariant!(time_receipts(1 + (n - i) as usize)); //~ ERROR Not enough time receipts + body_invariant!(time_credits(i as usize)); //~ ERROR Not enough time receipts for invariant in the first loop iteration. + body_invariant!(time_receipts(1 + (n - i) as usize)); + res += i; + i -= 1; + } + res +} + +#[requires(time_credits(1))] +#[ensures(time_receipts(1))] +fn foo() -> usize { + 42 +} + +#[requires(time_credits(n + 1))] // should be n + 2 to be correct +#[ensures(time_receipts(n + 2))] +fn loop_foo(n: usize) -> usize { + let mut i = 0; + let mut res = 0; + while i < n { + body_invariant!(time_receipts(i + 1)); + res += i; + i += 1; + } + res += foo(); //~ ERROR Not enough time credits to call function. + res +} + +#[requires(time_credits(2 * n + 1))] // should be 2 * n + 2 to be correct +#[ensures(time_receipts(2 * n + 2))] +fn loop_foo_loop(n: usize) -> usize { + let mut i = 0; + let mut res = 0; + while i < n { + body_invariant!(time_receipts(i + 1)); res += i; + i += 1; + } + res += foo(); // this is also an error but the other is reported first. + while 0 < i { + body_invariant!(time_receipts(2 * n - i + 2)); //~ ERROR Not enough time credits. + res += 1; i -= 1; } res diff --git a/prusti-tests/tests/verify/fail/time-resources/negative_amount.rs b/prusti-tests/tests/verify/fail/time-resources/negative_amount.rs index 35399f538a2..da3ad5303c6 100644 --- a/prusti-tests/tests/verify/fail/time-resources/negative_amount.rs +++ b/prusti-tests/tests/verify/fail/time-resources/negative_amount.rs @@ -2,6 +2,14 @@ use prusti_contracts::*; -#[requires(time_credits(1 - 2))] //~ ERROR +// TODO: fix error reporting for negative time resources. + +#[requires(time_credits(1))] #[ensures(time_receipts(2 - 4))] //~ ERROR -fn main() {} +fn main() { +} + +#[requires(time_credits(1))] +fn foo() { + assert!(time_credits(1 - 2)); //~ ERROR +} diff --git a/prusti-tests/tests/verify/fail/time-resources/only_lower_bound.rs b/prusti-tests/tests/verify/fail/time-resources/only_lower_bound.rs index 77456cf8d1d..70c070b2968 100644 --- a/prusti-tests/tests/verify/fail/time-resources/only_lower_bound.rs +++ b/prusti-tests/tests/verify/fail/time-resources/only_lower_bound.rs @@ -4,7 +4,7 @@ use prusti_contracts::*; // This tests that omitting the upper bound fails #[ensures(time_receipts(1))] -fn main() { +fn main() { //~ ERROR Not enough time credits. let mut i = 0; let mut _n = 0; while i < 42 { diff --git a/prusti-tests/tests/verify/pass/time-resources/array_2d.rs b/prusti-tests/tests/verify/pass/time-resources/array_2d.rs new file mode 100644 index 00000000000..fb15f1f989a --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/array_2d.rs @@ -0,0 +1,28 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +// ignore-test: loop place context issues and mixed dereferencing and array indexing projections are not supported + +#[requires(a.len() > 0)] +#[requires(forall(|i: usize| i < a.len() ==> a[i].len() == a[0].len()))] +#[requires(time_credits(a.len() * a[0].len() + 1))] +#[ensures(time_receipts(a.len() * a[0].len() + 1))] +fn sum_2d(a: &[&[u32]]) -> u32 { + let mut i = 0; + let mut res = 0; + while i < a.len() { + body_invariant!(time_receipts(i * a[i].len() + 1)); + let mut j = 0; + while j < a[i].len() { + body_invariant!(time_receipts(i * a[i].len() + j + 1)); + res += a[i][j]; + j += 1; + } + i += 1; + } + res +} + +#[requires(time_credits(1))] +fn main() {} diff --git a/prusti-tests/tests/verify/pass/time-resources/array_cmp.rs b/prusti-tests/tests/verify/pass/time-resources/array_cmp.rs new file mode 100644 index 00000000000..32a9b05062e --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/array_cmp.rs @@ -0,0 +1,34 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +#[requires(a.len() == b.len())] +#[requires(time_credits(a.len() + 1))] +#[ensures(time_receipts(a.len() + 1))] +fn safe_array_cmp(a: &[T], b: &[T]) -> bool { + let mut i = 0; + let mut res = true; + while i < a.len() { + body_invariant!(time_receipts(i + 1)); + res &= a[i] == b[i]; + i += 1; + } + return res; +} + +#[requires(a.len() == b.len())] +#[requires(time_credits(a.len() + 1))] +#[ensures(time_receipts(1))] +fn opt_array_cmp(a: &[T], b: &[T]) -> bool { + let mut i = 0; + while i < a.len() { + if a[i] != b[i] { + return false; + } + i += 1; + } + return true; +} + +#[requires(time_credits(1))] +fn main() {} \ No newline at end of file diff --git a/prusti-tests/tests/verify/pass/time-resources/ensures_time_credits.rs b/prusti-tests/tests/verify/pass/time-resources/ensures_time_credits.rs new file mode 100644 index 00000000000..68dc5a175ae --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/ensures_time_credits.rs @@ -0,0 +1,41 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +#[requires(time_credits(bound + 1))] +#[ensures(time_receipts(result.0 + 1))] +#[ensures(time_credits(bound - result.0))] +fn bounded_sum(a: &[usize], bound: usize) -> (usize, usize) { + let mut i = 0; + let mut sum = 0; + while i < a.len() && i < bound { + body_invariant!(time_receipts(i + 1)); + sum += a[i]; + i += 1; + } + (i, sum) +} + +enum LinkedList { + Empty, + Cons(T, Box>), +} + +impl LinkedList { + #[requires(time_credits(bound + 1))] + #[ensures(time_receipts(result))] + #[ensures(time_credits(bound - result))] + fn bounded_len(&self, bound: usize) -> usize { + if bound == 0 { + 0 + } else { + match self { + LinkedList::Empty => 0, + LinkedList::Cons(_, tail) => tail.bounded_len(bound - 1) + 1, + } + } + } +} + +#[requires(time_credits(1))] +fn main() {} diff --git a/prusti-tests/tests/verify/pass/time-resources/linked_list.rs b/prusti-tests/tests/verify/pass/time-resources/linked_list.rs index 66950eeef8a..9fc2da3a2b6 100644 --- a/prusti-tests/tests/verify/pass/time-resources/linked_list.rs +++ b/prusti-tests/tests/verify/pass/time-resources/linked_list.rs @@ -2,6 +2,7 @@ use prusti_contracts::*; +// ignore-test: time reasoning on pure functions does not work for now enum LinkedList { Empty, Cons(T, Box>), diff --git a/prusti-tests/tests/verify/pass/time-resources/loop.rs b/prusti-tests/tests/verify/pass/time-resources/loop.rs index eda534d8b6b..1249cf40d5a 100644 --- a/prusti-tests/tests/verify/pass/time-resources/loop.rs +++ b/prusti-tests/tests/verify/pass/time-resources/loop.rs @@ -19,7 +19,7 @@ fn sum(n: u32) -> u32 { #[requires(time_credits(n as usize + 1))] #[ensures(time_receipts(n as usize + 1))] fn sum2(n: u32) -> u32 { - let mut i = n; // To be correct it should be n + let mut i = n; let mut res = 0; while 0 < i { body_invariant!(time_credits(i as usize)); @@ -30,6 +30,60 @@ fn sum2(n: u32) -> u32 { res } +#[requires(time_credits((n * n) + 2 * n + 1))] +#[ensures(time_receipts((n * n) + 2 * n + 1))] +fn double_loop(n: usize) -> u32 { + let mut i = 0; + let mut res = 0; + while i < n { + body_invariant!(time_receipts(i * (n + 2) + 1)); + res += sum(n as u32); + i += 1; + } + res +} + +#[requires(time_credits(1))] +#[ensures(time_receipts(1))] +fn foo() -> usize { + 42 +} + +#[requires(time_credits(n + 2))] +#[ensures(time_receipts(n + 2))] +fn loop_foo(n: usize) -> usize { + let mut i = 0; + let mut res = 0; + while i < n { + body_invariant!(time_receipts(i + 1)); + res += i; + i += 1; + } + res += foo(); + res +} + + +#[requires(time_credits(2 * n + 3))] +#[ensures(time_receipts(2 * n + 3))] +fn loop_foo_loop_foo(n: usize) -> usize { + let mut i = 0; + let mut res = 0; + while i < n { + body_invariant!(time_receipts(i + 1)); + res += i; + i += 1; + } + res += foo(); + while 0 < i { + body_invariant!(time_receipts(2 * n - i + 2)); + res += 1; + i -= 1; + } + res += foo(); + res +} + #[requires(time_credits(12))] #[ensures(time_receipts(12))] fn main() { diff --git a/prusti-tests/tests/verify/pass/time-resources/rec.rs b/prusti-tests/tests/verify/pass/time-resources/rec.rs new file mode 100644 index 00000000000..361eb29d677 --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/rec.rs @@ -0,0 +1,25 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +#[extern_spec] +impl usize { + #[pure] + #[ensures(self.pow(n) * self == self.pow(n + 1))] + #[ensures(2_usize.pow(n) + 2_usize.pow(n) == 2_usize.pow(n + 1))] + #[ensures(2_usize.pow(n) + 2_usize.pow(n + 1) <= 2_usize.pow(n + 2))] + #[ensures(result >= 1)] + fn pow(self, n: u32) -> usize; +} + +#[requires(time_credits(2_usize.pow(n as u32)))] +fn fib(n: usize) -> usize { + if n <= 1 { + 1 + } else { + fib(n - 1) + fib(n - 2) + } +} + +#[requires(time_credits(1))] +fn main() {} diff --git a/prusti-tests/tests/verify/pass/time-resources/res.rs b/prusti-tests/tests/verify/pass/time-resources/res.rs index d7f99e27ea5..f4ee8b132c0 100644 --- a/prusti-tests/tests/verify/pass/time-resources/res.rs +++ b/prusti-tests/tests/verify/pass/time-resources/res.rs @@ -2,3 +2,38 @@ use prusti_contracts::*; +#[requires(time_credits(bound + 1))] +#[ensures(time_receipts(result.0 + 1))] +#[ensures(a.len() >= bound ==> result.0 == bound)] +#[ensures(a.len() <= bound ==> result.0 == a.len())] +fn bounded_sum(a: &[usize], bound: usize) -> (usize, usize) { + let mut i = 0; + let mut sum = 0; + while i < a.len() && i < bound { + body_invariant!(time_receipts(i + 1)); + sum += a[i]; + i += 1; + } + (i, sum) +} + +#[requires(time_credits(a.len() + 2))] +#[ensures(time_receipts(a.len() + 2))] +fn sum(a: &[usize]) -> usize { + bounded_sum(a, a.len()).1 +} + +#[requires(a.len() >= 1)] +#[requires(time_credits(3))] +#[ensures(time_receipts(3))] +fn first(a: &[usize]) -> usize { + bounded_sum(a, 1).1 +} + +#[requires(time_credits(9))] +#[ensures(time_receipts(9))] +fn main() { + let a = [1, 2, 3]; + let _s = sum(&a); + let _f = first(&a); +} From b029e943e88bc387bec104a88de23fc1ddfb70aa Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Wed, 4 Jan 2023 21:38:26 +0100 Subject: [PATCH 18/26] Fix position for error reporting --- .../vir/optimizations/functions/simplifier.rs | 51 ++++++++----------- .../src/encoder/errors/error_manager.rs | 2 +- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/prusti-common/src/vir/optimizations/functions/simplifier.rs b/prusti-common/src/vir/optimizations/functions/simplifier.rs index 199ac89ab1d..115a0d8af33 100644 --- a/prusti-common/src/vir/optimizations/functions/simplifier.rs +++ b/prusti-common/src/vir/optimizations/functions/simplifier.rs @@ -68,32 +68,13 @@ impl ExprSimplifier { position: inner_pos, }), position: pos, - }) => ast::Expr::BinOp(ast::BinOp { + }) if !matches!(left.get_type(), ast::Type::Float(_)) => ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::NeCmp, left: box left, right: box right, position: pos, }) .set_default_pos(inner_pos), - ast::Expr::BinOp(ast::BinOp { - op_kind: ast::BinaryOpKind::And, - left: - box ast::Expr::Const(ast::ConstExpr { - value: ast::Const::Bool(b1), - position: inner_pos1, - }), - right: - box ast::Expr::Const(ast::ConstExpr { - value: ast::Const::Bool(b2), - position: inner_pos2, - }), - position: pos, - }) => ast::Expr::Const(ast::ConstExpr { - value: ast::Const::Bool(b1 && b2), - position: pos, - }) - .set_default_pos(inner_pos1) - .set_default_pos(inner_pos2), ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::And, left: @@ -113,9 +94,13 @@ impl ExprSimplifier { position: inner_pos, }), position: pos, - }) => if b { conjunct } else { false.into() } - .set_pos(pos) - .set_default_pos(inner_pos), + }) => if b { + conjunct + } else { + Into::::into(false).set_pos(inner_pos) + } + .set_default_pos(pos) + .set_default_pos(inner_pos), ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::Or, left: @@ -135,9 +120,13 @@ impl ExprSimplifier { position: inner_pos, }), position: pos, - }) => if b { true.into() } else { disjunct } - .set_pos(pos) - .set_default_pos(inner_pos), + }) => if b { + Into::::into(true).set_pos(inner_pos) + } else { + disjunct + } + .set_default_pos(pos) + .set_default_pos(inner_pos), ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::Implies, left: guard, @@ -166,9 +155,13 @@ impl ExprSimplifier { }), right: box body, position: pos, - }) => if b { body } else { true.into() } - .set_pos(pos) - .set_default_pos(inner_pos), + }) => if b { + body + } else { + Into::::into(true).set_pos(inner_pos) + } + .set_default_pos(inner_pos) + .set_default_pos(pos), ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::And, left: box op1, diff --git a/prusti-viper/src/encoder/errors/error_manager.rs b/prusti-viper/src/encoder/errors/error_manager.rs index 8a1c516f561..9fed4a54151 100644 --- a/prusti-viper/src/encoder/errors/error_manager.rs +++ b/prusti-viper/src/encoder/errors/error_manager.rs @@ -426,7 +426,7 @@ impl<'tcx> ErrorManager<'tcx> { .set_help("This might be a bug in the Rust compiler.") } - ("exhale.failed:assertion.false", ErrorCtxt::ExhaleMethodPrecondition) => { + ("assert.failed:assertion.false" | "exhale.failed:assertion.false", ErrorCtxt::ExhaleMethodPrecondition) => { PrustiError::verification("precondition might not hold.", error_span) .set_failing_assertion(opt_cause_span) } From b9f212887a936e27cdd0663470b890cad68b95a4 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Fri, 6 Jan 2023 13:27:56 +0100 Subject: [PATCH 19/26] Fix rebase --- .../optimizations/predicates/delete_unused_predicates.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs b/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs index 5047df21b33..687eeb4bfaa 100644 --- a/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs +++ b/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs @@ -176,9 +176,9 @@ struct UsedPredicateCollector { impl UsedPredicateCollector { fn new() -> Self { UsedPredicateCollector { - used_predicates: FxHashSet::new(), - folded_predicates: FxHashSet::new(), - used_resource_predicates: FxHashSet::new(), + used_predicates: FxHashSet::default(), + folded_predicates: FxHashSet::default(), + used_resource_predicates: FxHashSet::default(), } } } From 4267cde633f1e05a18c1b38f1b9b6debd0b9efc5 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Fri, 6 Jan 2023 14:19:36 +0100 Subject: [PATCH 20/26] Fix clippy error --- vir/src/legacy/ast/predicate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vir/src/legacy/ast/predicate.rs b/vir/src/legacy/ast/predicate.rs index 1fb2b63c7d8..6eb44dd93a3 100644 --- a/vir/src/legacy/ast/predicate.rs +++ b/vir/src/legacy/ast/predicate.rs @@ -117,7 +117,7 @@ impl Predicate { Predicate::Struct(p) => &p.name, Predicate::Enum(p) => &p.name, Predicate::Bodyless(ref name, _) => name, - Predicate::ResourceAccess(typ) => &typ, + Predicate::ResourceAccess(typ) => typ, } } pub fn body(&self) -> Option { From b85cab5613782fb0c047a9ffa353045c7e4fe213 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Wed, 11 Jan 2023 10:46:32 +0100 Subject: [PATCH 21/26] Pull request reviews --- docs/dev-guide/src/config/flags.md | 9 +++++ .../vir/optimizations/functions/simplifier.rs | 7 ++-- .../src/vir/optimizations/methods/mod.rs | 4 +-- .../vir/optimizations/methods/simplifier.rs | 9 +++-- .../predicates/delete_unused_predicates.rs | 8 ++--- prusti-common/src/vir/to_viper.rs | 3 +- .../verify/fail/time-resources/array_cmp.rs | 4 ++- .../tests/verify/fail/time-resources/loop.rs | 26 ++++++++++---- .../fail/time-resources/only_lower_bound.rs | 13 +++---- .../verify/pass/time-resources/array_2d.rs | 6 ++-- .../verify/pass/time-resources/array_cmp.rs | 35 ++++++++++--------- .../time-resources/ensures_time_credits.rs | 3 +- .../tests/verify/pass/time-resources/flag.rs | 18 ---------- .../tests/verify/pass/time-resources/impl.rs | 4 +-- .../tests/verify/pass/time-resources/loop.rs | 16 +++++---- .../tests/verify/pass/time-resources/rec.rs | 12 ++++--- .../tests/verify/pass/time-resources/res.rs | 3 +- prusti-utils/src/config.rs | 9 +++-- .../src/encoder/errors/error_manager.rs | 14 ++++---- .../src/encoder/foldunfold/requirements.rs | 10 ++---- .../mir/pure/specifications/encoder_poly.rs | 18 ++++------ .../mir/pure/specifications/interface.rs | 11 ++++-- prusti-viper/src/encoder/procedure_encoder.rs | 3 +- vir/defs/polymorphic/ast/common.rs | 2 +- vir/defs/polymorphic/ast/expr.rs | 1 - vir/src/legacy/ast/expr.rs | 1 - 26 files changed, 134 insertions(+), 115 deletions(-) delete mode 100644 prusti-tests/tests/verify/pass/time-resources/flag.rs diff --git a/docs/dev-guide/src/config/flags.md b/docs/dev-guide/src/config/flags.md index 52202754628..ea4817fc865 100644 --- a/docs/dev-guide/src/config/flags.md +++ b/docs/dev-guide/src/config/flags.md @@ -69,6 +69,7 @@ | [`SMT_SOLVER_WRAPPER_PATH`](#smt_solver_wrapper_path) | `Option` | `None` | A | | [`SMT_UNIQUE_TRIGGERS_BOUND`](#smt_unique_triggers_bound) | `Option` | `None` | A | | [`SMT_UNIQUE_TRIGGERS_BOUND_TOTAL`](#smt_unique_triggers_bound_total) | `Option` | `None` | A | +| [`TIME_REASONING`](#time_reasoning) | `bool` | `false` | A | | [`UNSAFE_CORE_PROOF`](#unsafe_core_proof) | `bool` | `false` | A | | [`USE_MORE_COMPLETE_EXHALE`](#use_more_complete_exhale) | `bool` | `true` | A | | [`USE_SMT_WRAPPER`](#use_smt_wrapper) | `bool` | `false` | A | @@ -310,6 +311,7 @@ Comma-separated list of optimizations to enable, or `"all"` to enable all. Possi - `"purify_vars"` - `"fix_quantifiers"` - `"fix_unfoldings"` +- `"simplify_exprs"` - `"remove_unused_vars"` - `"remove_trivial_assertions"` - `"clean_cfg"` @@ -429,6 +431,13 @@ If not `None`, checks that the total number of unique triggers reported by the S > **Note:** Requires `USE_SMT_WRAPPER` to be `true`. +## `TIME_REASONING` + +When enabled, the `time_credits` and `time_receipts` predicates are usable and functions' and loops' bodies will concume time credits and produce time receipts. + +> **Note:** This is an experimental feature. +> **Note:** Requires `SIMPLIFY_ENCODING` to be `true` and the `simplify_exprs` optimization to be enabled. + ## `UNSAFE_CORE_PROOF` When enabled, the new core proof is used, suitable for unsafe code diff --git a/prusti-common/src/vir/optimizations/functions/simplifier.rs b/prusti-common/src/vir/optimizations/functions/simplifier.rs index 115a0d8af33..1d2e83e6177 100644 --- a/prusti-common/src/vir/optimizations/functions/simplifier.rs +++ b/prusti-common/src/vir/optimizations/functions/simplifier.rs @@ -99,8 +99,7 @@ impl ExprSimplifier { } else { Into::::into(false).set_pos(inner_pos) } - .set_default_pos(pos) - .set_default_pos(inner_pos), + .set_default_pos(pos), ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::Or, left: @@ -125,8 +124,7 @@ impl ExprSimplifier { } else { disjunct } - .set_default_pos(pos) - .set_default_pos(inner_pos), + .set_default_pos(pos), ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::Implies, left: guard, @@ -160,7 +158,6 @@ impl ExprSimplifier { } else { Into::::into(true).set_pos(inner_pos) } - .set_default_pos(inner_pos) .set_default_pos(pos), ast::Expr::BinOp(ast::BinOp { op_kind: ast::BinaryOpKind::And, diff --git a/prusti-common/src/vir/optimizations/methods/mod.rs b/prusti-common/src/vir/optimizations/methods/mod.rs index be75e899679..e80c6984448 100644 --- a/prusti-common/src/vir/optimizations/methods/mod.rs +++ b/prusti-common/src/vir/optimizations/methods/mod.rs @@ -21,7 +21,7 @@ use crate::{config::Optimizations, vir::polymorphic_vir::cfg::CfgMethod}; use self::{ assert_remover::remove_trivial_assertions, cfg_cleaner::clean_cfg, empty_if_remover::remove_empty_if, purifier::purify_vars, quantifier_fixer::fix_quantifiers, - simplifier::simplify_expr, unfolding_fixer::fix_unfoldings, var_remover::remove_unused_vars, + simplifier::simplify_exprs, unfolding_fixer::fix_unfoldings, var_remover::remove_unused_vars, }; #[allow(clippy::let_and_return)] @@ -58,7 +58,7 @@ pub fn optimize_method_encoding( }; let cfg = apply!(fix_unfoldings, cfg); let cfg = apply!(fix_quantifiers, cfg); - let cfg = apply!(simplify_expr, cfg); + let cfg = apply!(simplify_exprs, cfg); let cfg = apply!(remove_empty_if, cfg); let cfg = apply!(remove_unused_vars, cfg); let cfg = apply!(remove_trivial_assertions, cfg); diff --git a/prusti-common/src/vir/optimizations/methods/simplifier.rs b/prusti-common/src/vir/optimizations/methods/simplifier.rs index 1db69a7f8ab..16c3ab4cbc6 100644 --- a/prusti-common/src/vir/optimizations/methods/simplifier.rs +++ b/prusti-common/src/vir/optimizations/methods/simplifier.rs @@ -10,10 +10,13 @@ use vir::polymorphic::*; use crate::vir::optimizations::functions::Simplifier; -/// This optimization transformes ors were the RHS is impure into implications. -/// This happens in the case of an implication with a resource access predicate in the RHS. +/// This optimization simplifies all expressions in a method. +/// It is required when resource access predicates appear on the RHS of +/// implications as implications are transformed into ors which need to be +/// transformed back into implications otherwise, we would have an impure +/// expression in ors which is disallowed in Viper. -pub fn simplify_expr(mut cfg: CfgMethod) -> CfgMethod { +pub fn simplify_exprs(mut cfg: CfgMethod) -> CfgMethod { debug!("Simplifying exprs in {}", cfg.name()); let mut sentinel_stmt = Stmt::comment("moved out stmt"); for block in &mut cfg.basic_blocks { diff --git a/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs b/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs index 687eeb4bfaa..797cac11b66 100644 --- a/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs +++ b/prusti-common/src/vir/optimizations/predicates/delete_unused_predicates.rs @@ -15,12 +15,12 @@ use log::debug; fn collect_info_from_methods_and_functions( methods: &[CfgMethod], functions: &[Function], - buildin_methods: &[BodylessMethod], + builtin_methods: &[BodylessMethod], ) -> UsedPredicateCollector { let mut collector = UsedPredicateCollector::new(); walk_methods(methods, &mut collector); walk_functions(functions, &mut collector); - walk_builtin_methods(buildin_methods, &mut collector); + walk_builtin_methods(builtin_methods, &mut collector); // DeadBorrowToken$ is a used predicate but it does not appear in VIR // becaue it is only created when viper code is created from VIR @@ -110,10 +110,10 @@ fn visit_predicate( pub fn delete_unused_predicates( methods: &[CfgMethod], functions: &[Function], - buildin_methods: &[BodylessMethod], + builtin_methods: &[BodylessMethod], mut predicates: Vec, ) -> Vec { - let collector = collect_info_from_methods_and_functions(methods, functions, buildin_methods); + let collector = collect_info_from_methods_and_functions(methods, functions, builtin_methods); let used_predicates_in_functions_and_methods = collector.used_predicates; let folded_predicates = collector.folded_predicates; let mut predicates_in_predicates_map = get_used_predicates_in_predicate_map(&predicates); diff --git a/prusti-common/src/vir/to_viper.rs b/prusti-common/src/vir/to_viper.rs index 1bd0debf4e6..2ee8d37fb6b 100644 --- a/prusti-common/src/vir/to_viper.rs +++ b/prusti-common/src/vir/to_viper.rs @@ -404,10 +404,9 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for Expr { pos.to_viper(context, ast), ), Expr::ResourceAccessPredicate(ref resource_name, ref amount, ref pos) => { - // Resource are encoded as a predicate with no arguments + // resources are encoded as predicates with no arguments ast.predicate_access_predicate_with_pos( ast.predicate_access(&[], resource_name), - // ast.int_perm_mul(amount.to_viper(context, ast), ast.full_perm()), ast.fractional_perm(amount.to_viper(context, ast), ast.int_lit(1)), pos.to_viper(context, ast), ) diff --git a/prusti-tests/tests/verify/fail/time-resources/array_cmp.rs b/prusti-tests/tests/verify/fail/time-resources/array_cmp.rs index 5a4e729f8d1..c2c756552a4 100644 --- a/prusti-tests/tests/verify/fail/time-resources/array_cmp.rs +++ b/prusti-tests/tests/verify/fail/time-resources/array_cmp.rs @@ -8,6 +8,8 @@ use prusti_contracts::*; fn opt_array_cmp(a: &[T], b: &[T]) -> bool { //~ ERROR Not enough time receipts at the end of the function. let mut i = 0; while i < a.len() { + body_invariant!(time_credits(a.len() - i)); + body_invariant!(time_receipts(i)); if a[i] != b[i] { return false; } @@ -17,4 +19,4 @@ fn opt_array_cmp(a: &[T], b: &[T]) -> bool { //~ ERROR Not enough time re } #[requires(time_credits(1))] -fn main() {} \ No newline at end of file +fn main() {} diff --git a/prusti-tests/tests/verify/fail/time-resources/loop.rs b/prusti-tests/tests/verify/fail/time-resources/loop.rs index 70ed3772324..3bbefeb89c5 100644 --- a/prusti-tests/tests/verify/fail/time-resources/loop.rs +++ b/prusti-tests/tests/verify/fail/time-resources/loop.rs @@ -2,8 +2,7 @@ use prusti_contracts::*; -// TODO: specify the error as: Not enough time receipts/credits - +#[requires(n > 0)] #[requires(time_credits(n as usize + 2))] #[ensures(time_receipts(n as usize + 1))] // This is also an error but the other is reported first. fn sum(n: u32) -> u32 { @@ -11,13 +10,25 @@ fn sum(n: u32) -> u32 { let mut res = 0; while 0 < i { body_invariant!(time_credits(i as usize)); //~ ERROR Not enough time receipts for invariant in the first loop iteration. - body_invariant!(time_receipts(1 + (n - i) as usize)); + body_invariant!(time_receipts((n - i) as usize)); res += i; i -= 1; } res } +#[requires(time_credits(3))] +fn sum2(n: usize) -> usize { + let mut i = 0; + let mut res = 0; + while i < n { + body_invariant!(time_credits(1)); //~ ERROR Not enough time credits + i += 1; + res += i; + } + res +} + #[requires(time_credits(1))] #[ensures(time_receipts(1))] fn foo() -> usize { @@ -30,7 +41,8 @@ fn loop_foo(n: usize) -> usize { let mut i = 0; let mut res = 0; while i < n { - body_invariant!(time_receipts(i + 1)); + body_invariant!(time_credits(n - i)); + body_invariant!(time_receipts(i)); res += i; i += 1; } @@ -44,13 +56,15 @@ fn loop_foo_loop(n: usize) -> usize { let mut i = 0; let mut res = 0; while i < n { - body_invariant!(time_receipts(i + 1)); + body_invariant!(time_credits(n - i)); + body_invariant!(time_receipts(i)); res += i; i += 1; } res += foo(); // this is also an error but the other is reported first. while 0 < i { - body_invariant!(time_receipts(2 * n - i + 2)); //~ ERROR Not enough time credits. + body_invariant!(time_receipts(n - i)); //~ ERROR Not enough time credits + body_invariant!(time_credits(i)); res += 1; i -= 1; } diff --git a/prusti-tests/tests/verify/fail/time-resources/only_lower_bound.rs b/prusti-tests/tests/verify/fail/time-resources/only_lower_bound.rs index 70c070b2968..b070f72ddf1 100644 --- a/prusti-tests/tests/verify/fail/time-resources/only_lower_bound.rs +++ b/prusti-tests/tests/verify/fail/time-resources/only_lower_bound.rs @@ -4,11 +4,8 @@ use prusti_contracts::*; // This tests that omitting the upper bound fails #[ensures(time_receipts(1))] -fn main() { //~ ERROR Not enough time credits. - let mut i = 0; - let mut _n = 0; - while i < 42 { - _n += 1; - i += 1; - } -} \ No newline at end of file +fn main() {//~ ERROR Not enough time credits. + let mut i = 0; + let mut _n = 0; + i += 42; +} diff --git a/prusti-tests/tests/verify/pass/time-resources/array_2d.rs b/prusti-tests/tests/verify/pass/time-resources/array_2d.rs index fb15f1f989a..8d34216a5f0 100644 --- a/prusti-tests/tests/verify/pass/time-resources/array_2d.rs +++ b/prusti-tests/tests/verify/pass/time-resources/array_2d.rs @@ -12,10 +12,12 @@ fn sum_2d(a: &[&[u32]]) -> u32 { let mut i = 0; let mut res = 0; while i < a.len() { - body_invariant!(time_receipts(i * a[i].len() + 1)); + body_invariant!(time_credits((a.len() - i) * a[i].len())); + body_invariant!(time_receipts(i * a[i].len())); let mut j = 0; while j < a[i].len() { - body_invariant!(time_receipts(i * a[i].len() + j + 1)); + body_invariant!(time_credits(a[i].len() - j)); + body_invariant!(time_receipts(j)); res += a[i][j]; j += 1; } diff --git a/prusti-tests/tests/verify/pass/time-resources/array_cmp.rs b/prusti-tests/tests/verify/pass/time-resources/array_cmp.rs index 32a9b05062e..d11e475ec80 100644 --- a/prusti-tests/tests/verify/pass/time-resources/array_cmp.rs +++ b/prusti-tests/tests/verify/pass/time-resources/array_cmp.rs @@ -6,29 +6,32 @@ use prusti_contracts::*; #[requires(time_credits(a.len() + 1))] #[ensures(time_receipts(a.len() + 1))] fn safe_array_cmp(a: &[T], b: &[T]) -> bool { - let mut i = 0; - let mut res = true; - while i < a.len() { - body_invariant!(time_receipts(i + 1)); - res &= a[i] == b[i]; - i += 1; - } - return res; + let mut i = 0; + let mut res = true; + while i < a.len() { + body_invariant!(time_credits(a.len() - i)); + body_invariant!(time_receipts(i)); + res &= a[i] == b[i]; + i += 1; + } + return res; } #[requires(a.len() == b.len())] #[requires(time_credits(a.len() + 1))] #[ensures(time_receipts(1))] fn opt_array_cmp(a: &[T], b: &[T]) -> bool { - let mut i = 0; - while i < a.len() { - if a[i] != b[i] { - return false; + let mut i = 0; + while i < a.len() { + body_invariant!(time_credits(a.len() - i)); + body_invariant!(time_receipts(i)); + if a[i] != b[i] { + return false; + } + i += 1; } - i += 1; - } - return true; + return true; } #[requires(time_credits(1))] -fn main() {} \ No newline at end of file +fn main() {} diff --git a/prusti-tests/tests/verify/pass/time-resources/ensures_time_credits.rs b/prusti-tests/tests/verify/pass/time-resources/ensures_time_credits.rs index 68dc5a175ae..012c0cb5de5 100644 --- a/prusti-tests/tests/verify/pass/time-resources/ensures_time_credits.rs +++ b/prusti-tests/tests/verify/pass/time-resources/ensures_time_credits.rs @@ -9,7 +9,8 @@ fn bounded_sum(a: &[usize], bound: usize) -> (usize, usize) { let mut i = 0; let mut sum = 0; while i < a.len() && i < bound { - body_invariant!(time_receipts(i + 1)); + body_invariant!(time_credits(bound - i)); + body_invariant!(time_receipts(i)); sum += a[i]; i += 1; } diff --git a/prusti-tests/tests/verify/pass/time-resources/flag.rs b/prusti-tests/tests/verify/pass/time-resources/flag.rs deleted file mode 100644 index 0902ea8ed9f..00000000000 --- a/prusti-tests/tests/verify/pass/time-resources/flag.rs +++ /dev/null @@ -1,18 +0,0 @@ -// compile-flags: -Ptime_reasoning=false - -use prusti_contracts::*; - -// This test checks that setting the time reasoning option to false ignores timing constrains -fn sum(n: u32) -> u32 { - let mut i = 0; - let mut res = 0; - while i < n { - res += i; - i += 1; - } - res -} - -fn main() { - sum(4); -} diff --git a/prusti-tests/tests/verify/pass/time-resources/impl.rs b/prusti-tests/tests/verify/pass/time-resources/impl.rs index 59a38479062..f66dfd3715a 100644 --- a/prusti-tests/tests/verify/pass/time-resources/impl.rs +++ b/prusti-tests/tests/verify/pass/time-resources/impl.rs @@ -9,7 +9,7 @@ fn sum(n: i32) -> i32 { let mut res = 0; while i < n { body_invariant!(time_credits((n - i) as usize)); - body_invariant!(time_receipts(i as usize + 1)); + body_invariant!(time_receipts(i as usize)); res += i; i += 1; } @@ -25,7 +25,7 @@ fn sum2(n: i32) -> i32 { let mut res = 0; while i < n { body_invariant!(time_credits((n - i) as usize)); - body_invariant!(time_receipts(i as usize + 1)); + body_invariant!(time_receipts(i as usize)); res += i; i += 1; } diff --git a/prusti-tests/tests/verify/pass/time-resources/loop.rs b/prusti-tests/tests/verify/pass/time-resources/loop.rs index 1249cf40d5a..02ac150f395 100644 --- a/prusti-tests/tests/verify/pass/time-resources/loop.rs +++ b/prusti-tests/tests/verify/pass/time-resources/loop.rs @@ -9,7 +9,7 @@ fn sum(n: u32) -> u32 { let mut res = 0; while i < n { body_invariant!(time_credits((n - i) as usize)); - body_invariant!(time_receipts(i as usize + 1)); + body_invariant!(time_receipts(i as usize)); res += i; i += 1; } @@ -23,7 +23,7 @@ fn sum2(n: u32) -> u32 { let mut res = 0; while 0 < i { body_invariant!(time_credits(i as usize)); - body_invariant!(time_receipts(1 + (n - i) as usize)); + body_invariant!(time_receipts((n - i) as usize)); res += i; i -= 1; } @@ -36,7 +36,8 @@ fn double_loop(n: usize) -> u32 { let mut i = 0; let mut res = 0; while i < n { - body_invariant!(time_receipts(i * (n + 2) + 1)); + body_invariant!(time_credits((n - i) * (n + 2))); + body_invariant!(time_receipts(i * (n + 2))); res += sum(n as u32); i += 1; } @@ -55,7 +56,8 @@ fn loop_foo(n: usize) -> usize { let mut i = 0; let mut res = 0; while i < n { - body_invariant!(time_receipts(i + 1)); + body_invariant!(time_credits(n - i)); + body_invariant!(time_receipts(i)); res += i; i += 1; } @@ -70,13 +72,15 @@ fn loop_foo_loop_foo(n: usize) -> usize { let mut i = 0; let mut res = 0; while i < n { - body_invariant!(time_receipts(i + 1)); + body_invariant!(time_credits(n - i)); + body_invariant!(time_receipts(i)); res += i; i += 1; } res += foo(); while 0 < i { - body_invariant!(time_receipts(2 * n - i + 2)); + body_invariant!(time_credits(i)); + body_invariant!(time_receipts(n - i)); res += 1; i -= 1; } diff --git a/prusti-tests/tests/verify/pass/time-resources/rec.rs b/prusti-tests/tests/verify/pass/time-resources/rec.rs index 361eb29d677..2789666154f 100644 --- a/prusti-tests/tests/verify/pass/time-resources/rec.rs +++ b/prusti-tests/tests/verify/pass/time-resources/rec.rs @@ -6,9 +6,8 @@ use prusti_contracts::*; impl usize { #[pure] #[ensures(self.pow(n) * self == self.pow(n + 1))] - #[ensures(2_usize.pow(n) + 2_usize.pow(n) == 2_usize.pow(n + 1))] - #[ensures(2_usize.pow(n) + 2_usize.pow(n + 1) <= 2_usize.pow(n + 2))] - #[ensures(result >= 1)] + #[ensures(self >= 1 ==> result >= 1)] + #[ensures(self >= 1 ==> self.pow(0) == 1)] fn pow(self, n: u32) -> usize; } @@ -21,5 +20,8 @@ fn fib(n: usize) -> usize { } } -#[requires(time_credits(1))] -fn main() {} +#[requires(time_credits(50))] +fn main() { + // consumes 32 credits + fib(5); +} diff --git a/prusti-tests/tests/verify/pass/time-resources/res.rs b/prusti-tests/tests/verify/pass/time-resources/res.rs index f4ee8b132c0..317ba3ec431 100644 --- a/prusti-tests/tests/verify/pass/time-resources/res.rs +++ b/prusti-tests/tests/verify/pass/time-resources/res.rs @@ -10,7 +10,8 @@ fn bounded_sum(a: &[usize], bound: usize) -> (usize, usize) { let mut i = 0; let mut sum = 0; while i < a.len() && i < bound { - body_invariant!(time_receipts(i + 1)); + body_invariant!(time_credits(bound - i)); + body_invariant!(time_receipts(i)); sum += a[i]; i += 1; } diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index c7586fc72cc..82648266a04 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -23,7 +23,7 @@ pub struct Optimizations { pub purify_vars: bool, pub fix_quantifiers: bool, pub fix_unfoldings: bool, - pub simplify_expr: bool, + pub simplify_exprs: bool, pub remove_unused_vars: bool, pub remove_trivial_assertions: bool, pub clean_cfg: bool, @@ -39,7 +39,7 @@ impl Optimizations { purify_vars: false, fix_quantifiers: false, fix_unfoldings: false, - simplify_expr: false, + simplify_exprs: false, remove_unused_vars: false, remove_trivial_assertions: false, clean_cfg: false, @@ -56,7 +56,7 @@ impl Optimizations { fix_quantifiers: true, // Disabled because https://github.com/viperproject/prusti-dev/issues/892 has been fixed fix_unfoldings: false, - simplify_expr: true, + simplify_exprs: true, remove_unused_vars: true, remove_trivial_assertions: true, clean_cfg: true, @@ -368,6 +368,7 @@ pub fn check_panics() -> bool { } /// When enabled, the time reasoning constrains are verified. Otherwise they are ignored. +/// Note that this is an experimental feature. pub fn time_reasoning() -> bool { read_setting("time_reasoning") } @@ -704,6 +705,7 @@ pub fn verify_only_basic_block_path() -> Vec { /// - `"purify_vars"` /// - `"fix_quantifiers"` /// - `"fix_unfoldings"` +/// - `"simplify_exprs"` /// - `"remove_unused_vars"` /// - `"remove_trivial_assertions"` /// - `"clean_cfg"` @@ -723,6 +725,7 @@ pub fn optimizations() -> Optimizations { "purify_vars" => opt.purify_vars = true, "fix_quantifiers" => opt.fix_quantifiers = true, "fix_unfoldings" => opt.fix_unfoldings = true, + "simplify_exprs" => opt.simplify_exprs = true, "remove_unused_vars" => opt.remove_unused_vars = true, "remove_trivial_assertions" => opt.remove_trivial_assertions = true, "clean_cfg" => opt.clean_cfg = true, diff --git a/prusti-viper/src/encoder/errors/error_manager.rs b/prusti-viper/src/encoder/errors/error_manager.rs index 9fed4a54151..99fe04b2941 100644 --- a/prusti-viper/src/encoder/errors/error_manager.rs +++ b/prusti-viper/src/encoder/errors/error_manager.rs @@ -177,7 +177,7 @@ pub enum ErrorCtxt { /// The state that fold-unfold algorithm deduced as unreachable, is actually /// reachable. UnreachableFoldingState, - // There is not enough time credtis to execute this code. + // There is not enough time credits to execute this code. NotEnoughTimeCredits, // The code did not create enough time receipts. NotEnoughTimeReceipts, @@ -355,8 +355,8 @@ impl<'tcx> ErrorManager<'tcx> { ) .set_help( "This could be caused by too small assertion timeout. \ - Try increasing it by setting the configuration parameter \ - ASSERT_TIMEOUT to a larger value.", + Try increasing it by setting the configuration parameter \ + ASSERT_TIMEOUT to a larger value.", ), None => PrustiError::internal( format!( @@ -367,8 +367,8 @@ impl<'tcx> ErrorManager<'tcx> { ) .set_help( "This could be caused by too small assertion timeout. \ - Try increasing it by setting the configuration parameter \ - ASSERT_TIMEOUT to a larger value.", + Try increasing it by setting the configuration parameter \ + ASSERT_TIMEOUT to a larger value.", ), } } @@ -727,7 +727,9 @@ impl<'tcx> ErrorManager<'tcx> { PrustiError::verification("Not enough time receipts.".to_string(), error_span) } - // TODO: this is a temporary fix for detecting time credits/receipts errors + // TODO: this is a temporary fix for detecting time credits/receipts errors. + // Ideally time credits/receipts errors should have the `NotEnoughTimeCredits`/`NotEnoughTimeReceipts` error context and would be + // handled by the previous cases. For this we need to be able to debug the positions that are given to Viper. ("exhale.failed:insufficient.permission", ErrorCtxt::ExhaleMethodPrecondition) if ver_error.message.contains("There might be insufficient permission to access time_credits()") => { PrustiError::verification("Not enough time credits to call function.".to_string(), error_span) } diff --git a/prusti-viper/src/encoder/foldunfold/requirements.rs b/prusti-viper/src/encoder/foldunfold/requirements.rs index 9f677677624..bbb5c96d099 100644 --- a/prusti-viper/src/encoder/foldunfold/requirements.rs +++ b/prusti-viper/src/encoder/foldunfold/requirements.rs @@ -91,17 +91,13 @@ impl RequiredStmtPermissionsGetter for vir::Stmt { ref targets, .. }) => { - // Preconditions and postconditions are empty or does not require permissions - let arguments_perm = arguments - .iter() - .map(|expr| expr.get_required_stmt_permissions(predicates).into_iter()) - .flatten(); + // Preconditions and postconditions are empty + assert!(arguments.is_empty()); FxHashSet::from_iter( targets .iter() .cloned() - .map(|v| Acc(vir::Expr::local(v), PermAmount::Write)) - .chain(arguments_perm), + .map(|v| Acc(vir::Expr::local(v), PermAmount::Write)), ) } diff --git a/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs b/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs index 7ae315d8c88..79f4b08f29e 100644 --- a/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs +++ b/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs @@ -133,13 +133,6 @@ pub(super) fn inline_spec_item<'tcx>( .replace_multiple_places(&body_replacements)) } -fn error_ctxt(resource_type: &vir_crate::polymorphic::ResourceType) -> ErrorCtxt { - match resource_type { - vir_crate::polymorphic::ResourceType::TimeCredits => ErrorCtxt::NotEnoughTimeCredits, - vir_crate::polymorphic::ResourceType::TimeReceipts => ErrorCtxt::NotEnoughTimeReceipts, - } -} - pub(super) fn encode_time_specifications<'tcx>( encoder: &Encoder<'_, 'tcx>, amount: &vir_crate::polymorphic::Expr, @@ -147,10 +140,13 @@ pub(super) fn encode_time_specifications<'tcx>( parent_def_id: DefId, span: Span, ) -> vir_crate::polymorphic::Expr { - let pos = - encoder - .error_manager() - .register_error(span, error_ctxt(&resource_type), parent_def_id); + let error_ctxt = match resource_type { + vir_crate::polymorphic::ResourceType::TimeCredits => ErrorCtxt::NotEnoughTimeCredits, + vir_crate::polymorphic::ResourceType::TimeReceipts => ErrorCtxt::NotEnoughTimeReceipts, + }; + let pos = encoder + .error_manager() + .register_error(span, error_ctxt, parent_def_id); let amount_non_negative = vir_crate::polymorphic::Expr::ge_cmp(amount.clone(), 0.into()); let resource_access_predicate = vir_crate::polymorphic::Expr::resource_access_predicate(resource_type, amount.clone()) diff --git a/prusti-viper/src/encoder/mir/pure/specifications/interface.rs b/prusti-viper/src/encoder/mir/pure/specifications/interface.rs index a639687dfbc..f20935913aa 100644 --- a/prusti-viper/src/encoder/mir/pure/specifications/interface.rs +++ b/prusti-viper/src/encoder/mir/pure/specifications/interface.rs @@ -5,7 +5,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::encoder::{ - errors::{SpannedEncodingResult, WithSpan}, + errors::{SpannedEncodingError, SpannedEncodingResult, WithSpan}, mir::{ places::PlacesEncoderInterface, pure::{ @@ -268,7 +268,14 @@ impl<'v, 'tcx: 'v> SpecificationEncoderInterface<'tcx> for crate::encoder::Encod span, )) } else { - Ok(true.into()) + let mut error = SpannedEncodingError::unsupported( + "Time reasoning is disabled but found a call to a time_credits/time_receipts predicate.", + span + ); + error.set_help( + "To enable time reasoning set the TIME_REASONING option to true.", + ); + Err(error) } } _ => unimplemented!(), diff --git a/prusti-viper/src/encoder/procedure_encoder.rs b/prusti-viper/src/encoder/procedure_encoder.rs index c52c19e5f90..ae6ecb9b425 100644 --- a/prusti-viper/src/encoder/procedure_encoder.rs +++ b/prusti-viper/src/encoder/procedure_encoder.rs @@ -474,7 +474,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { self.encode_preconditions(start_cfg_block, precondition_weakening)?; if config::time_reasoning() { - // If time reasoning is enabled, add a call to tick after preconditions + // consume a time credits and produce a time receipt in the body of the function self.cfg_method.add_stmts(start_cfg_block, self.get_tick_call(self.mir.span, 1)); } @@ -936,6 +936,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let (mut stmts, fnspec_span) = self.encode_loop_invariant_inhale_fnspec_stmts(loop_head, before_invariant_block, false)?; if config::time_reasoning() { + // consume a time credit and produce a time receipt at each iteration of the loop stmts.extend(self.get_tick_call(fnspec_span.clone(), 1).into_iter()); } self.cfg_method.add_stmts(inv_post_block_fnspc, stmts); fnspec_span diff --git a/vir/defs/polymorphic/ast/common.rs b/vir/defs/polymorphic/ast/common.rs index 850a301eb4d..75c1f871108 100644 --- a/vir/defs/polymorphic/ast/common.rs +++ b/vir/defs/polymorphic/ast/common.rs @@ -525,7 +525,7 @@ impl ResourceType { "prusti_contracts::time_credits" => ResourceType::TimeCredits, "prusti_contracts::time_receipts" => ResourceType::TimeReceipts, _ => unreachable!( - "Fonction name: {} does not match any resource type.", + "Function name: {} does not match any resource type.", function_name ), } diff --git a/vir/defs/polymorphic/ast/expr.rs b/vir/defs/polymorphic/ast/expr.rs index 5e25a29c188..8ebcc4633f1 100644 --- a/vir/defs/polymorphic/ast/expr.rs +++ b/vir/defs/polymorphic/ast/expr.rs @@ -634,7 +634,6 @@ impl Expr { pub fn is_only_permissions(&self) -> bool { match self { Expr::PredicateAccessPredicate(..) - // TODO check this not sure | Expr::ResourceAccessPredicate(..) | Expr::FieldAccessPredicate(..) => true, Expr::BinOp(BinOp { diff --git a/vir/src/legacy/ast/expr.rs b/vir/src/legacy/ast/expr.rs index 20dcc54fd4f..58bb42de274 100644 --- a/vir/src/legacy/ast/expr.rs +++ b/vir/src/legacy/ast/expr.rs @@ -786,7 +786,6 @@ impl Expr { pub fn is_only_permissions(&self) -> bool { match self { Expr::PredicateAccessPredicate(..) - // TODO check this not sure | Expr::ResourceAccessPredicate(..) | Expr::FieldAccessPredicate(..) => true, Expr::BinOp(BinaryOpKind::And, box lhs, box rhs, _) => { From 3693f84fbfc07dfdd31b83aa040d0f192497daae Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Tue, 17 Jan 2023 19:43:34 +0100 Subject: [PATCH 22/26] Add scope id to resource predicate for loop body --- prusti-common/src/vir/to_viper.rs | 14 ++++++++----- .../src/encoder/errors/error_manager.rs | 14 ++++++------- .../mir/pure/specifications/encoder_poly.rs | 2 +- vir/defs/polymorphic/ast/expr.rs | 21 +++++++++++++++++-- vir/defs/polymorphic/ast/expr_transformers.rs | 18 ++++------------ vir/src/converter/polymorphic_to_legacy.rs | 1 + vir/src/legacy/ast/expr.rs | 18 +++++++++------- vir/src/legacy/ast/expr_transformers.rs | 17 ++++++++++----- 8 files changed, 63 insertions(+), 42 deletions(-) diff --git a/prusti-common/src/vir/to_viper.rs b/prusti-common/src/vir/to_viper.rs index 2ee8d37fb6b..d14d90b608b 100644 --- a/prusti-common/src/vir/to_viper.rs +++ b/prusti-common/src/vir/to_viper.rs @@ -17,7 +17,7 @@ use crate::{ use fxhash::FxHashMap; use log::{info, trace}; use viper::{self, AstFactory}; -use vir::common::identifier::WithIdentifier; +use vir::{common::identifier::WithIdentifier, legacy}; impl<'v> ToViper<'v, viper::Program<'v>> for Program { fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Program<'v> { @@ -403,10 +403,11 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for Expr { perm.to_viper(context, ast), pos.to_viper(context, ast), ), - Expr::ResourceAccessPredicate(ref resource_name, ref amount, ref pos) => { - // resources are encoded as predicates with no arguments + Expr::ResourceAccessPredicate(ref resource_name, ref amount, scope_id, ref pos) => { + let id = ast.int_lit(*scope_id as i64); + // resources are encoded as predicates with the integer scope_id as arguments ast.predicate_access_predicate_with_pos( - ast.predicate_access(&[], resource_name), + ast.predicate_access(&[id], resource_name), ast.fractional_perm(amount.to_viper(context, ast), ast.int_lit(1)), pos.to_viper(context, ast), ) @@ -818,7 +819,10 @@ impl<'v> ToViper<'v, viper::Predicate<'v>> for Predicate { Predicate::Bodyless(name, this) => { ast.predicate(name, &[this.to_viper_decl(context, ast)], None) } - Predicate::ResourceAccess(typ) => ast.predicate(typ, &[], None), + Predicate::ResourceAccess(typ) => { + let var = ast.local_var_decl("scope_id", legacy::Type::Int.to_viper(context, ast)); + ast.predicate(typ, &[var], None) + } } } } diff --git a/prusti-viper/src/encoder/errors/error_manager.rs b/prusti-viper/src/encoder/errors/error_manager.rs index 99fe04b2941..cfc57ee97c1 100644 --- a/prusti-viper/src/encoder/errors/error_manager.rs +++ b/prusti-viper/src/encoder/errors/error_manager.rs @@ -730,25 +730,25 @@ impl<'tcx> ErrorManager<'tcx> { // TODO: this is a temporary fix for detecting time credits/receipts errors. // Ideally time credits/receipts errors should have the `NotEnoughTimeCredits`/`NotEnoughTimeReceipts` error context and would be // handled by the previous cases. For this we need to be able to debug the positions that are given to Viper. - ("exhale.failed:insufficient.permission", ErrorCtxt::ExhaleMethodPrecondition) if ver_error.message.contains("There might be insufficient permission to access time_credits()") => { + ("exhale.failed:insufficient.permission", ErrorCtxt::ExhaleMethodPrecondition) if ver_error.message.contains("There might be insufficient permission to access time_credits") => { PrustiError::verification("Not enough time credits to call function.".to_string(), error_span) } - ("assert.failed:insufficient.permission", ErrorCtxt::AssertMethodPostcondition) if ver_error.message.contains("There might be insufficient permission to access time_receipts()") => { + ("assert.failed:insufficient.permission", ErrorCtxt::AssertMethodPostcondition) if ver_error.message.contains("There might be insufficient permission to access time_receipts") => { PrustiError::verification("Not enough time receipts at the end of the function.".to_string(), error_span) } - ("assert.failed:insufficient.permission", ErrorCtxt::AssertMethodPostcondition) if ver_error.message.contains("There might be insufficient permission to access time_credits()") => { + ("assert.failed:insufficient.permission", ErrorCtxt::AssertMethodPostcondition) if ver_error.message.contains("There might be insufficient permission to access time_credits") => { PrustiError::verification("Not enough time credits at the end of the function.".to_string(), error_span) } - ("exhale.failed:insufficient.permission", ErrorCtxt::AssertLoopInvariantOnEntry) if ver_error.message.contains("There might be insufficient permission to access time_credits()") => { + ("exhale.failed:insufficient.permission", ErrorCtxt::AssertLoopInvariantOnEntry) if ver_error.message.contains("There might be insufficient permission to access time_credits") => { PrustiError::verification("Not enough time credits for invariant in the first loop iteration.".to_string(), error_span) } - ("exhale.failed:insufficient.permission", ErrorCtxt::AssertLoopInvariantOnEntry) if ver_error.message.contains("There might be insufficient permission to access time_receipts()") => { + ("exhale.failed:insufficient.permission", ErrorCtxt::AssertLoopInvariantOnEntry) if ver_error.message.contains("There might be insufficient permission to access time_receipts") => { PrustiError::verification("Not enough time receipts for invariant in the first loop iteration.".to_string(), error_span) } - ("exhale.failed:insufficient.permission", ErrorCtxt::AssertLoopInvariantAfterIteration) if ver_error.message.contains("There might be insufficient permission to access time_credits()") => { + ("exhale.failed:insufficient.permission", ErrorCtxt::AssertLoopInvariantAfterIteration) if ver_error.message.contains("There might be insufficient permission to access time_credits") => { PrustiError::verification("Not enough time credits for invariant after a loop iteration that preserves the loop condition.".to_string(), error_span) } - ("exhale.failed:insufficient.permission", ErrorCtxt::AssertLoopInvariantAfterIteration) if ver_error.message.contains("There might be insufficient permission to access time_receipts()") => { + ("exhale.failed:insufficient.permission", ErrorCtxt::AssertLoopInvariantAfterIteration) if ver_error.message.contains("There might be insufficient permission to access time_receipts") => { PrustiError::verification("Not enough time receipts for invariant after a loop iteration that preserves the loop condition.".to_string(), error_span) } diff --git a/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs b/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs index 79f4b08f29e..93b345bb328 100644 --- a/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs +++ b/prusti-viper/src/encoder/mir/pure/specifications/encoder_poly.rs @@ -149,7 +149,7 @@ pub(super) fn encode_time_specifications<'tcx>( .register_error(span, error_ctxt, parent_def_id); let amount_non_negative = vir_crate::polymorphic::Expr::ge_cmp(amount.clone(), 0.into()); let resource_access_predicate = - vir_crate::polymorphic::Expr::resource_access_predicate(resource_type, amount.clone()) + vir_crate::polymorphic::Expr::resource_access_predicate(resource_type, amount.clone(), -1) .set_pos(pos); vir_crate::polymorphic::Expr::and(amount_non_negative, resource_access_predicate) } diff --git a/vir/defs/polymorphic/ast/expr.rs b/vir/defs/polymorphic/ast/expr.rs index 8ebcc4633f1..da1e32c29ad 100644 --- a/vir/defs/polymorphic/ast/expr.rs +++ b/vir/defs/polymorphic/ast/expr.rs @@ -275,11 +275,16 @@ impl Expr { }) } - pub fn resource_access_predicate(resource_type: ResourceType, amount: Expr) -> Self { + pub fn resource_access_predicate( + resource_type: ResourceType, + amount: Expr, + scope_id: isize, + ) -> Self { let pos = amount.pos(); Expr::ResourceAccessPredicate(ResourceAccessPredicate { resource_type, amount: Box::new(amount), + scope_id, position: pos, }) } @@ -2092,15 +2097,27 @@ impl Hash for PredicateAccessPredicate { pub struct ResourceAccessPredicate { pub resource_type: ResourceType, pub amount: Box, + // Global scope id is -1, loops' scope id is the index of the loop head block + pub scope_id: isize, pub position: Position, } +impl ResourceAccessPredicate { + pub fn replace_scope_id(&self, new_scope_id: isize) -> Expr { + Expr::ResourceAccessPredicate(ResourceAccessPredicate { + scope_id: new_scope_id, + ..self.clone() + }) + } +} + impl fmt::Display for ResourceAccessPredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "acc({}(), {}/1)", + "acc({}({}), {}/1)", self.resource_type.encode_as_string(), + self.scope_id, self.amount ) } diff --git a/vir/defs/polymorphic/ast/expr_transformers.rs b/vir/defs/polymorphic/ast/expr_transformers.rs index a4e42b6a2ef..baace67d3b0 100644 --- a/vir/defs/polymorphic/ast/expr_transformers.rs +++ b/vir/defs/polymorphic/ast/expr_transformers.rs @@ -162,15 +162,10 @@ pub trait ExprFolder: Sized { } fn fold_resource_access_predicate(&mut self, expr: ResourceAccessPredicate) -> Expr { - let ResourceAccessPredicate { - resource_type, - amount, - position, - } = expr; + let ResourceAccessPredicate { amount, .. } = expr; Expr::ResourceAccessPredicate(ResourceAccessPredicate { - resource_type, amount: self.fold_boxed(amount), - position, + ..expr }) } @@ -862,15 +857,10 @@ pub trait FallibleExprFolder: Sized { &mut self, expr: ResourceAccessPredicate, ) -> Result { - let ResourceAccessPredicate { - resource_type, - amount, - position, - } = expr; + let ResourceAccessPredicate { amount, .. } = expr; Ok(Expr::ResourceAccessPredicate(ResourceAccessPredicate { - resource_type, amount: self.fallible_fold_boxed(amount)?, - position, + ..expr })) } diff --git a/vir/src/converter/polymorphic_to_legacy.rs b/vir/src/converter/polymorphic_to_legacy.rs index 7274e29535b..c53a69ac175 100644 --- a/vir/src/converter/polymorphic_to_legacy.rs +++ b/vir/src/converter/polymorphic_to_legacy.rs @@ -272,6 +272,7 @@ impl From for legacy::Expr { legacy::Expr::ResourceAccessPredicate( resource_access_predicate.resource_type.encode_as_string(), Box::new((*resource_access_predicate.amount).into()), + resource_access_predicate.scope_id, resource_access_predicate.position.into(), ) } diff --git a/vir/src/legacy/ast/expr.rs b/vir/src/legacy/ast/expr.rs index 58bb42de274..87b68b2d6c5 100644 --- a/vir/src/legacy/ast/expr.rs +++ b/vir/src/legacy/ast/expr.rs @@ -31,8 +31,8 @@ pub enum Expr { MagicWand(Box, Box, Option, Position), /// PredicateAccessPredicate: predicate_name, arg, permission amount PredicateAccessPredicate(String, Box, PermAmount, Position), - /// ResourceAccessPredicate: resource name, amount - ResourceAccessPredicate(String, Box, Position), + /// ResourceAccessPredicate: resource name, amount, scope_id + ResourceAccessPredicate(String, Box, isize, Position), FieldAccessPredicate(Box, PermAmount, Position), UnaryOp(UnaryOpKind, Box, Position), BinOp(BinaryOpKind, Box, Box, Position), @@ -195,8 +195,8 @@ impl fmt::Display for Expr { Expr::PredicateAccessPredicate(ref pred_name, ref arg, perm, ref _pos) => { write!(f, "acc({pred_name}({arg}), {perm})") } - Expr::ResourceAccessPredicate(resouce_name, amount, _pos) => { - write!(f, "acc({}(), {}/1)", resouce_name, amount) + Expr::ResourceAccessPredicate(resouce_name, amount, scope_id, _) => { + write!(f, "acc({}({}), {}/1)", resouce_name, scope_id, amount) } Expr::FieldAccessPredicate(ref expr, perm, ref _pos) => { write!(f, "acc({expr}, {perm})") @@ -378,7 +378,7 @@ impl Expr { | Expr::LabelledOld(_, _, p) | Expr::MagicWand(_, _, _, p) | Expr::PredicateAccessPredicate(_, _, _, p) - | Expr::ResourceAccessPredicate(_, _, p) + | Expr::ResourceAccessPredicate(_, _, _, p) | Expr::FieldAccessPredicate(_, _, p) | Expr::UnaryOp(_, _, p) | Expr::BinOp(_, _, _, p) @@ -413,7 +413,9 @@ impl Expr { Expr::PredicateAccessPredicate(x, y, z, _) => { Expr::PredicateAccessPredicate(x, y, z, pos) } - Expr::ResourceAccessPredicate(n, a, _) => Expr::ResourceAccessPredicate(n, a, pos), + Expr::ResourceAccessPredicate(n, a, id, _) => { + Expr::ResourceAccessPredicate(n, a, id, pos) + } Expr::FieldAccessPredicate(x, y, _) => Expr::FieldAccessPredicate(x, y, pos), Expr::UnaryOp(x, y, _) => Expr::UnaryOp(x, y, pos), Expr::BinOp(x, y, z, _) => Expr::BinOp(x, y, z, pos), @@ -460,9 +462,9 @@ impl Expr { Expr::PredicateAccessPredicate(name.to_string(), box place, perm, pos) } - pub fn resource_access_predicate(name: S, amount: Expr) -> Self { + pub fn resource_access_predicate(name: S, amount: Expr, scope_id: isize) -> Self { let pos = amount.pos(); - Expr::ResourceAccessPredicate(name.to_string(), box amount, pos) + Expr::ResourceAccessPredicate(name.to_string(), box amount, scope_id, pos) } pub fn field_access_predicate(place: Expr, perm: PermAmount) -> Self { diff --git a/vir/src/legacy/ast/expr_transformers.rs b/vir/src/legacy/ast/expr_transformers.rs index 38c361335c2..f59768bce33 100644 --- a/vir/src/legacy/ast/expr_transformers.rs +++ b/vir/src/legacy/ast/expr_transformers.rs @@ -154,11 +154,13 @@ pub trait ExprFolder: Sized { &mut self, resource_name: String, amount: Box, + scope_id: isize, pos: Position, ) -> Expr { Expr::ResourceAccessPredicate( resource_name, self.fold_boxed(amount), + scope_id, self.fold_position(pos), ) } @@ -370,7 +372,9 @@ pub fn default_fold_expr(this: &mut T, e: Expr) -> Expr { Expr::PredicateAccessPredicate(x, y, z, p) => { this.fold_predicate_access_predicate(x, y, z, p) } - Expr::ResourceAccessPredicate(n, a, p) => this.fold_resource_access_predicate(n, a, p), + Expr::ResourceAccessPredicate(n, a, id, p) => { + this.fold_resource_access_predicate(n, a, id, p) + } Expr::FieldAccessPredicate(x, y, p) => this.fold_field_access_predicate(x, y, p), Expr::UnaryOp(x, y, p) => this.fold_unary_op(x, y, p), Expr::BinOp(x, y, z, p) => this.fold_bin_op(x, y, z, p), @@ -455,6 +459,7 @@ pub trait ExprWalker: Sized { &mut self, _resource_name: &str, amount: &Expr, + _scope_id: isize, pos: &Position, ) { self.walk(amount); @@ -643,8 +648,8 @@ pub fn default_walk_expr(this: &mut T, e: &Expr) { Expr::PredicateAccessPredicate(ref x, ref y, z, ref p) => { this.walk_predicate_access_predicate(x, y, z, p) } - Expr::ResourceAccessPredicate(ref n, ref a, ref p) => { - this.walk_resource_access_predicate(n, a, p) + Expr::ResourceAccessPredicate(ref n, ref a, id, ref p) => { + this.walk_resource_access_predicate(n, a, id, p) } Expr::FieldAccessPredicate(ref x, y, ref p) => this.walk_field_access_predicate(x, y, p), Expr::UnaryOp(x, ref y, ref p) => this.walk_unary_op(x, y, p), @@ -755,11 +760,13 @@ pub trait FallibleExprFolder: Sized { &mut self, resource_name: String, amount: Box, + scope_id: isize, pos: Position, ) -> Result { Ok(Expr::ResourceAccessPredicate( resource_name, self.fallible_fold_boxed(amount)?, + scope_id, pos, )) } @@ -1018,8 +1025,8 @@ pub fn default_fallible_fold_expr>( Expr::PredicateAccessPredicate(x, y, z, p) => { this.fallible_fold_predicate_access_predicate(x, y, z, p) } - Expr::ResourceAccessPredicate(n, a, p) => { - this.fallible_fold_resource_access_predicate(n, a, p) + Expr::ResourceAccessPredicate(n, a, id, p) => { + this.fallible_fold_resource_access_predicate(n, a, id, p) } Expr::FieldAccessPredicate(x, y, p) => this.fallible_fold_field_access_predicate(x, y, p), Expr::UnaryOp(x, y, p) => this.fallible_fold_unary_op(x, y, p), From 335633865c9f19fd71f0b62a4f4a126255622f11 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Tue, 17 Jan 2023 19:45:06 +0100 Subject: [PATCH 23/26] Add functions to modify resource predicate scope --- .../src/encoder/resources/interface.rs | 44 ++++-- prusti-viper/src/encoder/resources/mod.rs | 138 ++++++++++++++++++ 2 files changed, 167 insertions(+), 15 deletions(-) diff --git a/prusti-viper/src/encoder/resources/interface.rs b/prusti-viper/src/encoder/resources/interface.rs index 65c8278f98b..fe62b039062 100644 --- a/prusti-viper/src/encoder/resources/interface.rs +++ b/prusti-viper/src/encoder/resources/interface.rs @@ -9,7 +9,12 @@ use prusti_rustc_interface::errors::MultiSpan; use vir_crate::polymorphic as vir; pub trait ResourcesEncoderInterface { - fn get_tick_call>(&self, span: T, amount: usize) -> Vec; + fn get_tick_call>( + &self, + span: T, + amount: usize, + scope_ids: &[isize], + ) -> Vec; } // TODO: differentiate between loops' and functions' bodies errors @@ -17,22 +22,31 @@ pub trait ResourcesEncoderInterface { impl<'p, 'v: 'p, 'tcx: 'v> ResourcesEncoderInterface for super::super::procedure_encoder::ProcedureEncoder<'p, 'v, 'tcx> { - fn get_tick_call>(&self, span: T, amount: usize) -> Vec { + fn get_tick_call>( + &self, + span: T, + amount: usize, + scope_ids: &[isize], + ) -> Vec { let pos = self.register_error(span, ErrorCtxt::NotEnoughTimeCredits); - vec![ - vir::Stmt::Exhale(vir::Exhale { - expr: vir::Expr::resource_access_predicate( - vir::common::ResourceType::TimeCredits, - amount.into(), - ).set_pos(pos), - position: pos, - }), - vir::Stmt::Inhale(vir::Inhale { - expr: vir::Expr::resource_access_predicate( - vir::common::ResourceType::TimeReceipts, + let mut stmts = vec![vir::Stmt::comment("tick call")]; + stmts.extend(scope_ids.iter().map(|&scope_id| { + vir::Stmt::exhale( + vir::Expr::resource_access_predicate( + vir::ResourceType::TimeCredits, amount.into(), + scope_id, ), - }), - ] + pos.clone(), + ) + })); + stmts.extend(scope_ids.iter().map(|&scope_id| { + vir::Stmt::inhale(vir::Expr::resource_access_predicate( + vir::ResourceType::TimeReceipts, + amount.into(), + scope_id, + )) + })); + stmts } } diff --git a/prusti-viper/src/encoder/resources/mod.rs b/prusti-viper/src/encoder/resources/mod.rs index f3e200b468f..cb14ced13c7 100644 --- a/prusti-viper/src/encoder/resources/mod.rs +++ b/prusti-viper/src/encoder/resources/mod.rs @@ -5,3 +5,141 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. pub(super) mod interface; + +use vir_crate::polymorphic::{ + BinOp, BinaryOpKind, Cond, Expr, ExprFolder, ExprIterator, FallibleExprWalker, + ResourceAccessPredicate, UnaryOp, UnaryOpKind, +}; + +use super::errors::{EncodingError, EncodingResult}; + +struct Changer<'a> { + scope_ids: &'a [isize], +} +impl<'a> ExprFolder for Changer<'a> { + fn fold_resource_access_predicate(&mut self, expr: ResourceAccessPredicate) -> Expr { + self.scope_ids + .iter() + .map(|&scope_id| expr.replace_scope_id(scope_id)) + .conjoin() + } +} + +pub fn change_scope_id(expr: Expr, scope_ids: &[isize]) -> Expr { + let mut remover = Changer { scope_ids }; + remover.fold(expr) +} + +// Founds resource predicates and if they appear in incorrect position such as in the negative form, in the condition of a conditional, in the gard of an implication or inside other resource predicates +#[derive(Default)] +struct ResourceFinder { + // track if we have found a resource predicate + resource_found: bool, + // counts the negativity of the current expression + negativity: usize, + // counts the number of conditions of a conditional, gards of an implication, or resource predicates traversed + depth: usize, +} + +impl ResourceFinder { + fn inc_depth(&mut self) { + self.depth += 1; + } + + fn dec_depth(&mut self) { + assert!(self.depth > 0); + self.depth -= 1; + } + + fn inc_negativity(&mut self) { + self.negativity += 1; + } + + fn dec_negativity(&mut self) { + assert!(self.negativity > 0); + self.negativity -= 1; + } +} + +impl FallibleExprWalker for ResourceFinder { + type Error = EncodingError; + + fn fallible_walk_resource_access_predicate( + &mut self, + expr: &ResourceAccessPredicate, + ) -> Result<(), Self::Error> { + if self.negativity % 2 == 1 { + return Err(EncodingError::incorrect( + "Found a resource predicate in a negative position!", + )); + } + if self.depth > 0 { + return Err(EncodingError::incorrect( + "Found a resource predicate in the condition of a conditional, \ + or in the guard of an implication, or a resource predicate inside an other!", + )); + } + self.resource_found = true; + self.inc_depth(); + self.fallible_walk(&expr.amount)?; + self.dec_depth(); + Ok(()) + } + + fn fallible_walk_cond(&mut self, expr: &Cond) -> Result<(), Self::Error> { + let Cond { + guard, + then_expr, + else_expr, + .. + } = expr; + self.inc_depth(); + self.fallible_walk(guard)?; + self.dec_depth(); + self.fallible_walk(then_expr)?; + self.fallible_walk(else_expr)?; + Ok(()) + } + + fn fallible_walk_unary_op(&mut self, expr: &UnaryOp) -> Result<(), Self::Error> { + let UnaryOp { + op_kind, argument, .. + } = expr; + let is_not = matches!(op_kind, UnaryOpKind::Not); + if is_not { + self.inc_negativity(); + } + self.fallible_walk(argument)?; + if is_not { + self.dec_negativity(); + } + Ok(()) + } + + fn fallible_walk_bin_op(&mut self, expr: &BinOp) -> Result<(), Self::Error> { + let BinOp { + op_kind, + left, + right, + .. + } = expr; + let is_implies = matches!(op_kind, BinaryOpKind::Implies); + if is_implies { + self.inc_depth(); + } + self.fallible_walk(left)?; + if is_implies { + self.dec_depth(); + } + self.fallible_walk(right)?; + Ok(()) + } +} + +pub fn contains_resource_access_predicate(expr: &Expr) -> EncodingResult { + let mut finder: ResourceFinder = Default::default(); + finder.fallible_walk(expr)?; + assert!(finder.negativity == 0); + assert!(finder.depth == 0); + Ok(finder.resource_found) +} From 4b46a5d3d83ea8c224863e0f2143e69fbd1b3b30 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Wed, 18 Jan 2023 20:49:54 +0100 Subject: [PATCH 24/26] Add loop cost --- prusti-viper/src/encoder/procedure_encoder.rs | 3144 ++++++++++------- 1 file changed, 1787 insertions(+), 1357 deletions(-) diff --git a/prusti-viper/src/encoder/procedure_encoder.rs b/prusti-viper/src/encoder/procedure_encoder.rs index ae6ecb9b425..3a45032ffc8 100644 --- a/prusti-viper/src/encoder/procedure_encoder.rs +++ b/prusti-viper/src/encoder/procedure_encoder.rs @@ -4,85 +4,84 @@ // 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::encoder::mir::spans::interface::SpanInterface; -use crate::encoder::builtin_encoder::{BuiltinMethodKind}; -use crate::encoder::errors::{ - SpannedEncodingError, ErrorCtxt, EncodingError, WithSpan, - EncodingResult, SpannedEncodingResult +use super::{ + counterexamples::DiscriminantsStateInterface, high::generics::HighGenericsEncoderInterface, }; -use crate::encoder::errors::error_manager::PanicCause; -use crate::encoder::foldunfold; -use crate::encoder::high::types::HighTypeEncoderInterface; -use crate::encoder::initialisation::InitInfo; -use crate::encoder::loop_encoder::{LoopEncoder, LoopEncoderError}; -use crate::encoder::mir_encoder::{MirEncoder, FakeMirEncoder, PlaceEncoder, PlaceEncoding, ExprOrArrayBase}; -use crate::encoder::mir_encoder::PRECONDITION_LABEL; -use crate::encoder::mir_successor::MirSuccessor; -use crate::encoder::places::{Local, LocalVariableManager, Place}; -use crate::encoder::Encoder; -use crate::encoder::resources::interface::ResourcesEncoderInterface; -use crate::encoder::snapshot::interface::SnapshotEncoderInterface; -use crate::encoder::mir::procedures::encoder::specification_blocks::SpecificationBlocks; -use crate::error_unsupported; +use crate::{ + encoder::{ + builtin_encoder::BuiltinMethodKind, + errors::{ + error_manager::PanicCause, EncodingError, EncodingErrorKind, EncodingResult, ErrorCtxt, + SpannedEncodingError, SpannedEncodingResult, WithSpan, + }, + foldunfold, + high::types::HighTypeEncoderInterface, + initialisation::InitInfo, + loop_encoder::{LoopEncoder, LoopEncoderError}, + mir::{ + contracts::{ContractsEncoderInterface, ProcedureContract}, + procedures::encoder::specification_blocks::SpecificationBlocks, + pure::{PureFunctionEncoderInterface, SpecificationEncoderInterface}, + sequences::MirSequencesEncoderInterface, + spans::interface::SpanInterface, + specifications::SpecificationsInterface, + type_invariants::TypeInvariantEncoderInterface, + types::MirTypeEncoderInterface, + }, + mir_encoder::{ + ExprOrArrayBase, FakeMirEncoder, MirEncoder, PlaceEncoder, PlaceEncoding, + PRECONDITION_LABEL, + }, + mir_successor::MirSuccessor, + places::{Local, LocalVariableManager, Place}, + resources, + resources::interface::ResourcesEncoderInterface, + snapshot::interface::SnapshotEncoderInterface, + Encoder, + }, + error_unsupported, + utils::is_reference, +}; +use ::log::{debug, trace}; use prusti_common::{ config, utils::to_string::ToString, - vir::{ToGraphViz, fixes::fix_ghost_vars}, - vir_local, vir_expr, vir_stmt -}; -use vir_crate::{ - polymorphic::{ - self as vir, - compute_identifier, - borrows::Borrow, - collect_assigned_vars, - CfgBlockIndex, ExprIterator, Successor, Type}, + vir::{fixes::fix_ghost_vars, ToGraphViz}, + vir_expr, vir_local, vir_stmt, }; use prusti_interface::{ data::ProcedureDefId, environment::{ - borrowck::facts, + borrowck::{facts, regions::PlaceRegionsError}, + mir_utils::SliceOrArrayRef, polonius_info::{ LoanPlaces, PoloniusInfo, PoloniusInfoError, ReborrowingDAG, ReborrowingDAGNode, ReborrowingKind, ReborrowingZombity, }, BasicBlockIndex, LoopAnalysisError, PermissionKind, Procedure, }, - PrustiError, + specs::{ + typed, + typed::{Pledge, SpecificationItem}, + }, + utils, PrustiError, }; -use std::collections::{BTreeMap}; -use prusti_interface::utils; -use prusti_rustc_interface::middle::mir::Mutability; -use prusti_rustc_interface::middle::mir; -use prusti_rustc_interface::middle::mir::{TerminatorKind}; -use prusti_rustc_interface::middle::ty::{self, subst::SubstsRef}; -use prusti_rustc_interface::target::abi::Integer; -use rustc_hash::{FxHashMap, FxHashSet}; -use prusti_rustc_interface::span::Span; -use prusti_rustc_interface::errors::MultiSpan; -use prusti_interface::specs::typed; -use ::log::{trace, debug}; -use prusti_interface::environment::borrowck::regions::PlaceRegionsError; -use crate::encoder::errors::EncodingErrorKind; -use std::convert::TryInto; -use prusti_interface::specs::typed::{Pledge, SpecificationItem}; -use vir_crate::polymorphic::Float; -use crate::utils::is_reference; -use crate::encoder::mir::{ - sequences::MirSequencesEncoderInterface, - contracts::{ - ContractsEncoderInterface, - ProcedureContract, +use prusti_rustc_interface::{ + errors::MultiSpan, + middle::{ + mir, + mir::{Mutability, TerminatorKind}, + ty::{self, subst::SubstsRef}, }, - pure::PureFunctionEncoderInterface, - types::MirTypeEncoderInterface, - pure::SpecificationEncoderInterface, - specifications::SpecificationsInterface, - type_invariants::TypeInvariantEncoderInterface, + span::Span, + target::abi::Integer, +}; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::{collections::BTreeMap, convert::TryInto, iter::once}; +use vir_crate::polymorphic::{ + self as vir, borrows::Borrow, collect_assigned_vars, compute_identifier, CfgBlockIndex, + ExprIterator, Float, Successor, Type, }; -use super::high::generics::HighGenericsEncoderInterface; -use super::counterexamples::DiscriminantsStateInterface; -use prusti_interface::environment::mir_utils::SliceOrArrayRef; pub struct ProcedureEncoder<'p, 'v: 'p, 'tcx: 'v> { encoder: &'p Encoder<'v, 'tcx>, @@ -121,7 +120,8 @@ pub struct ProcedureEncoder<'p, 'v: 'p, 'tcx: 'v> { FxHashMap, FxHashMap)>, /// A map that stores local variables used to preserve the value of a place accross the loop /// when we cannot do that by using permissions. - pure_var_for_preserving_value_map: FxHashMap>, + pure_var_for_preserving_value_map: + FxHashMap>, /// Information about which places are definitely initialised. init_info: InitInfo, /// Mapping from old expressions to ghost variables with which they were replaced. @@ -138,7 +138,7 @@ pub struct ProcedureEncoder<'p, 'v: 'p, 'tcx: 'v> { impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { pub fn new( encoder: &'p Encoder<'v, 'tcx>, - procedure: &'p Procedure<'tcx> + procedure: &'p Procedure<'tcx>, ) -> SpannedEncodingResult { debug!("ProcedureEncoder constructor"); @@ -150,7 +150,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let init_info = InitInfo::new(mir, tcx, proc_def_id, &mir_encoder) .with_default_span(procedure.get_span())?; - let specification_blocks = SpecificationBlocks::build(encoder.env().query, mir, procedure, false); + let specification_blocks = + SpecificationBlocks::build(encoder.env().query, mir, procedure, false); let cfg_method = vir::CfgMethod::new( // method name @@ -222,7 +223,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) -> SpannedEncodingResult<()> { let block = &self.mir[bb]; let _ = self.try_encode_assert(bb, block, encoded_statements)? - || self.try_encode_assume(bb, block, encoded_statements)?; + || self.try_encode_assume(bb, block, encoded_statements)?; Ok(()) } @@ -238,16 +239,18 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { mir::Rvalue::Aggregate(box mir::AggregateKind::Closure(cl_def_id, cl_substs), _), )) = stmt.kind { - if self.encoder.get_prusti_assumption(cl_def_id.to_def_id()).is_none() { + if self + .encoder + .get_prusti_assumption(cl_def_id.to_def_id()) + .is_none() + { return Ok(false); } - let assume_expr = self.encoder.encode_invariant(self.mir, bb, self.proc_def_id, cl_substs)?; + let assume_expr = + self.encoder + .encode_invariant(self.mir, bb, self.proc_def_id, cl_substs)?; - let assume_stmt = vir::Stmt::Inhale( - vir::Inhale { - expr: assume_expr - } - ); + let assume_stmt = vir::Stmt::Inhale(vir::Inhale { expr: assume_expr }); encoded_statements.push(assume_stmt); @@ -278,14 +281,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .encoder .get_definition_span(assertion.assertion.to_def_id()); - let assert_expr = self.encoder.encode_invariant(self.mir, bb, self.proc_def_id, cl_substs)?; + let assert_expr = + self.encoder + .encode_invariant(self.mir, bb, self.proc_def_id, cl_substs)?; - let assert_stmt = vir::Stmt::Assert( - vir::Assert { - expr: assert_expr, - position: self.register_error(span, ErrorCtxt::Panic(PanicCause::Assert)) - } - ); + let assert_stmt = vir::Stmt::Assert(vir::Assert { + expr: assert_expr, + position: self.register_error(span, ErrorCtxt::Panic(PanicCause::Assert)), + }); encoded_statements.push(assert_stmt); @@ -302,11 +305,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { variable, } => { let msg = if self.mir.local_decls[variable].is_user_variable() { - "creation of loan 'FIXME: extract variable name' in loop is unsupported".to_string() + "creation of loan 'FIXME: extract variable name' in loop is unsupported" + .to_string() } else { "creation of temporary loan in loop is unsupported".to_string() }; - SpannedEncodingError::unsupported(msg, self.mir_encoder.get_span_of_basic_block(loop_head)) + SpannedEncodingError::unsupported( + msg, + self.mir_encoder.get_span_of_basic_block(loop_head), + ) } PoloniusInfoError::LoansInNestedLoops(location1, _loop1, _location2, _loop2) => { @@ -324,11 +331,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) } - PoloniusInfoError::MultipleMagicWandsPerLoop(location) => SpannedEncodingError::unsupported( - "the creation of loans in this loop is not supported \ + PoloniusInfoError::MultipleMagicWandsPerLoop(location) => { + SpannedEncodingError::unsupported( + "the creation of loans in this loop is not supported \ (MultipleMagicWandsPerLoop)", - self.mir.source_info(location).span, - ), + self.mir.source_info(location).span, + ) + } PoloniusInfoError::MagicWandHasNoRepresentativeLoan(location) => { SpannedEncodingError::unsupported( @@ -338,10 +347,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) } - PoloniusInfoError::PlaceRegionsError( - PlaceRegionsError::Unsupported(msg), - span, - ) => { + PoloniusInfoError::PlaceRegionsError(PlaceRegionsError::Unsupported(msg), span) => { SpannedEncodingError::unsupported(msg, span) } @@ -364,7 +370,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let mir_span = self.mir.span; // Retrieve the contract - let procedure_contract = self.encoder + let procedure_contract = self + .encoder .get_procedure_contract_for_def(self.proc_def_id, self.substs) .with_span(mir_span)?; assert_one_magic_wand(procedure_contract.borrow_infos.len()).with_span(mir_span)?; @@ -375,9 +382,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let name = self.mir_encoder.encode_local_var_name(local); let typ = self .encoder - .encode_type(self.mir_encoder.get_local_ty(local)).unwrap(); // will panic if attempting to encode unsupported type - self.cfg_method - .add_formal_return(&name, typ) + .encode_type(self.mir_encoder.get_local_ty(local)) + .unwrap(); // will panic if attempting to encode unsupported type + self.cfg_method.add_formal_return(&name, typ) } // Preprocess loops @@ -399,8 +406,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // Load Polonius info self.polonius_info = Some( - PoloniusInfo::new(self.encoder.env(), self.procedure, &self.cached_loop_invariant_block) - .map_err(|err| self.translate_polonius_error(err))?, + PoloniusInfo::new( + self.encoder.env(), + self.procedure, + &self.cached_loop_invariant_block, + ) + .map_err(|err| self.translate_polonius_error(err))?, ); // Initialize CFG blocks @@ -432,7 +443,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .register_span(self.mir_encoder.get_span_of_basic_block(bbi)); self.cfg_method.add_stmt( start_cfg_block, - vir::Stmt::Assign( vir::Assign { + vir::Stmt::Assign(vir::Assign { target: vir::Expr::local(executed_flag_var.clone()).set_pos(bb_pos), source: false.into(), kind: vir::AssignKind::Copy, @@ -450,12 +461,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { &self.procedure.get_reachable_nonspec_cfg_blocks(), 0, return_cfg_block, + &[-1], )?; if !unresolved_edges.is_empty() { return Err(SpannedEncodingError::internal( - format!( - "there are unresolved CFG edges in the encoding: {unresolved_edges:?}" - ), + format!("there are unresolved CFG edges in the encoding: {unresolved_edges:?}"), mir_span, )); } @@ -467,15 +477,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ); // Prepare assertions to check specification refinement - let (precondition_weakening, postcondition_strengthening) - = self.encode_spec_refinement(PRECONDITION_LABEL)?; + let (precondition_weakening, postcondition_strengthening) = + self.encode_spec_refinement(PRECONDITION_LABEL)?; // Encode preconditions self.encode_preconditions(start_cfg_block, precondition_weakening)?; if config::time_reasoning() { // consume a time credits and produce a time receipt in the body of the function - self.cfg_method.add_stmts(start_cfg_block, self.get_tick_call(self.mir.span, 1)); + self.cfg_method + .add_stmts(start_cfg_block, self.get_tick_call(self.mir.span, 1, &[-1])); } // Encode postcondition @@ -490,8 +501,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let local_ty = self.locals.get_type(*local); let typ = self.encoder.encode_type(local_ty).unwrap(); // will panic if attempting to encode unsupported type let var_name = self.locals.get_name(*local); - self.cfg_method - .add_local_var(&var_name, typ); + self.cfg_method.add_local_var(&var_name, typ); } self.check_vir()?; @@ -511,7 +521,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } // Patch snapshots - self.cfg_method = self.encoder.patch_snapshots_method(self.cfg_method) + self.cfg_method = self + .encoder + .patch_snapshots_method(self.cfg_method) .with_span(mir_span)?; // Add fold/unfold @@ -521,10 +533,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .iter() .map(|(loan, location)| (loan.index().into(), *location)) .collect(); - let method_pos = self.register_error( - self.mir.span, - ErrorCtxt::Unexpected - ); + let method_pos = self.register_error(self.mir.span, ErrorCtxt::Unexpected); let method_with_fold_unfold = foldunfold::add_fold_unfold( self.encoder, self.cfg_method, @@ -532,19 +541,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { &self.cfg_blocks_map, method_pos, ) - .map_err(|foldunfold_error| { - match foldunfold_error { - foldunfold::FoldUnfoldError::Unsupported(msg) => { - SpannedEncodingError::unsupported(msg, mir_span) - } - - _ => SpannedEncodingError::internal( - format!( - "cannot generate fold-unfold Viper statements. {foldunfold_error}", - ), - mir_span, - ), + .map_err(|foldunfold_error| match foldunfold_error { + foldunfold::FoldUnfoldError::Unsupported(msg) => { + SpannedEncodingError::unsupported(msg, mir_span) } + _ => SpannedEncodingError::internal( + format!("cannot generate fold-unfold Viper statements. {foldunfold_error}",), + mir_span, + ), })?; // Fix variable declarations. @@ -574,6 +578,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ordered_group_blocks: &[BasicBlockIndex], group_loop_depth: usize, return_block: CfgBlockIndex, + scope_ids: &[isize], ) -> SpannedEncodingResult<(Option, Vec<(CfgBlockIndex, BasicBlockIndex)>)> { // Encode the CFG blocks let mut bb_map: FxHashMap<_, _> = FxHashMap::default(); @@ -583,13 +588,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let curr_loop_depth = loop_info.get_loop_depth(curr_bb); let (curr_block, curr_edges) = if curr_loop_depth == group_loop_depth { // This block is not in a nested loop - self.encode_block(label_prefix, curr_bb, return_block)? + self.encode_block(label_prefix, curr_bb, return_block, scope_ids)? } else { debug_assert!(curr_loop_depth > group_loop_depth); let is_loop_head = loop_info.is_loop_head(curr_bb); if curr_loop_depth == group_loop_depth + 1 && is_loop_head { // Encode a nested loop - self.encode_loop(label_prefix, curr_bb, return_block)? + self.encode_loop(label_prefix, curr_bb, return_block, scope_ids)? } else { debug_assert!(curr_loop_depth > group_loop_depth + 1 || !is_loop_head); // Skip the inner block of a nested loop @@ -647,7 +652,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { matches!( expr, vir::Expr::Const(vir::ConstExpr { - value: vir::Const::Bool(true), .. + value: vir::Const::Bool(true), + .. }) ) } @@ -669,14 +675,28 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } impl<'a, 'b, 'c> ExprFolder for FindFnApps<'a, 'b, 'c> { fn fold_func_app(&mut self, expr: vir::FuncApp) -> vir::Expr { - let vir::FuncApp { function_name, type_arguments, arguments, formal_arguments, return_type, .. } = expr; + let vir::FuncApp { + function_name, + type_arguments, + arguments, + formal_arguments, + return_type, + .. + } = expr; if self.recurse { - let identifier: vir::FunctionIdentifier = - compute_identifier(&function_name, &type_arguments, &formal_arguments, &return_type).into(); + let identifier: vir::FunctionIdentifier = compute_identifier( + &function_name, + &type_arguments, + &formal_arguments, + &return_type, + ) + .into(); let pres = self.encoder.get_function(&identifier).unwrap().pres.clone(); // Avoid recursively collecting preconditions: they should be self-framing anyway self.recurse = false; - let pres: vir::Expr = pres.into_iter().fold(true.into(), |acc, expr| vir_expr! { [acc] && [expr] }); + let pres: vir::Expr = pres + .into_iter() + .fold(true.into(), |acc, expr| vir_expr! { [acc] && [expr] }); self.push_precond(pres, Some(function_name)); self.recurse = true; @@ -690,16 +710,27 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // We only care about the exhale part (e.g. for `walk_exhale`) self.fold(*expr.exhale_expr) } - fn fold_predicate_access_predicate(&mut self, expr: vir::PredicateAccessPredicate) -> vir::Expr { + fn fold_predicate_access_predicate( + &mut self, + expr: vir::PredicateAccessPredicate, + ) -> vir::Expr { self.fold(*expr.argument); true.into() } - fn fold_field_access_predicate(&mut self, expr: vir::FieldAccessPredicate) -> vir::Expr { + fn fold_field_access_predicate( + &mut self, + expr: vir::FieldAccessPredicate, + ) -> vir::Expr { self.fold(*expr.base); true.into() } fn fold_bin_op(&mut self, expr: vir::BinOp) -> vir::Expr { - let vir::BinOp { op_kind, left, right, position } = expr; + let vir::BinOp { + op_kind, + left, + right, + position, + } = expr; let left = self.fold_boxed(left); let right = self.fold_boxed(right); match op_kind { @@ -708,27 +739,38 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { vir::BinaryOpKind::Or if is_const_true(&left) => *left, vir::BinaryOpKind::Or if is_const_true(&right) => *right, _ => vir::Expr::BinOp(vir::BinOp { - op_kind, left, right, position, + op_kind, + left, + right, + position, }), } } } let mut walker = FindFnApps { - recurse: true, preconds: Vec::new(), encoder: self.encoder + recurse: true, + preconds: Vec::new(), + encoder: self.encoder, }; walker.walk(stmt); walker.preconds } fn block_preconditions(&self, block: &CfgBlockIndex) -> Vec<(Option, vir::Expr)> { let bb = &self.cfg_method.basic_blocks[block.block_index]; - let mut preconds: Vec<_> = bb.stmts.iter().flat_map(|stmt| self.stmt_preconditions(stmt)).collect(); + let mut preconds: Vec<_> = bb + .stmts + .iter() + .flat_map(|stmt| self.stmt_preconditions(stmt)) + .collect(); match &bb.successor { Successor::Undefined => (), Successor::Return => (), Successor::Goto(cbi) => preconds.extend(self.block_preconditions(cbi)), Successor::GotoSwitch(succs, def) => { preconds.extend( - succs.iter().flat_map(|cond_bb| self.block_preconditions(&cond_bb.1)) + succs + .iter() + .flat_map(|cond_bb| self.block_preconditions(&cond_bb.1)), ); preconds.extend(self.block_preconditions(def)); } @@ -789,6 +831,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { label_prefix: &str, loop_head: BasicBlockIndex, return_block: CfgBlockIndex, + scope_ids: &[isize], ) -> SpannedEncodingResult<(CfgBlockIndex, Vec<(CfgBlockIndex, BasicBlockIndex)>)> { let loop_info = self.loop_encoder.loops(); debug_assert!(loop_info.is_loop_head(loop_head)); @@ -801,9 +844,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .get_loop_body(loop_head) .iter() .copied() - .filter( - |&bb| self.procedure.is_reachable_block(bb) && !self.procedure.is_spec_block(bb) - ) + .filter(|&bb| { + self.procedure.is_reachable_block(bb) && !self.procedure.is_spec_block(bb) + }) .collect(); // Identify important blocks @@ -811,13 +854,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let loop_exit_blocks_set: FxHashSet<_> = loop_exit_blocks.iter().cloned().collect(); let before_invariant_block: BasicBlockIndex = self.cached_loop_invariant_block[&loop_head]; let before_inv_block_pos = loop_body - .iter().copied() + .iter() + .copied() .position(|bb| bb == before_invariant_block) .unwrap(); let after_inv_block_pos = 1 + before_inv_block_pos; // Find boolean switch exit blocks before the invariant let boolean_exit_blocks_before_inv: Vec<_> = loop_body[0..after_inv_block_pos] - .iter().copied() + .iter() + .copied() .filter(|bb| loop_exit_blocks_set.contains(bb)) .filter(|&bb| self.procedure.successors(bb).len() == 2) .collect(); @@ -886,6 +931,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { loop_guard_evaluation, loop_depth, return_block, + scope_ids, )?; heads.push(first_g_head); @@ -895,12 +941,19 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { loop_body_before_inv, loop_depth, return_block, + scope_ids, )?; heads.push(first_b1_head); // Calculate if `G` and `B1` are safe to include as the first `body_invariant!(...)` - let mut preconds = first_g_head.map(|cbi| self.block_preconditions(&cbi)).unwrap_or_default(); - preconds.extend(first_b1_head.map(|cbi| self.block_preconditions(&cbi)).unwrap_or_default()); + let mut preconds = first_g_head + .map(|cbi| self.block_preconditions(&cbi)) + .unwrap_or_default(); + preconds.extend( + first_b1_head + .map(|cbi| self.block_preconditions(&cbi)) + .unwrap_or_default(), + ); // Build the "invariant" CFG block (start - G - B1 - *invariant* - B2 - G - B1 - end) // (1) checks the loop invariant on entry @@ -925,25 +978,43 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ); heads.push(Some(inv_pre_block)); self.cfg_method - .set_successor(inv_pre_block, vir::Successor::Goto(inv_post_block_perms)); + .set_successor(inv_pre_block, vir::Successor::Goto(inv_post_block_perms)); { - let stmts = - self.encode_loop_invariant_exhale_stmts(loop_head, before_invariant_block, false)?; + let stmts = self.encode_loop_invariant_exhale_stmts( + loop_head, + before_invariant_block, + false, + scope_ids, + )?; self.cfg_method.add_stmts(inv_pre_block, stmts); } + let scope_ids: Vec = scope_ids + .iter() + .copied() + .chain(once(loop_head.index() as isize)) + .collect(); // We'll add later more statements at the end of inv_pre_block, to havoc local variables let fnspec_span = { - let (mut stmts, fnspec_span) = - self.encode_loop_invariant_inhale_fnspec_stmts(loop_head, before_invariant_block, false)?; + let (mut stmts, fnspec_span) = self.encode_loop_invariant_inhale_fnspec_stmts( + loop_head, + before_invariant_block, + false, + &scope_ids, + )?; if config::time_reasoning() { // consume a time credit and produce a time receipt at each iteration of the loop - stmts.extend(self.get_tick_call(fnspec_span.clone(), 1).into_iter()); + stmts.extend( + self.get_tick_call(fnspec_span.clone(), 1, &scope_ids) + .into_iter(), + ); } - self.cfg_method.add_stmts(inv_post_block_fnspc, stmts); fnspec_span + self.cfg_method.add_stmts(inv_post_block_fnspc, stmts); + fnspec_span }; { - let stmts = - self.encode_loop_invariant_inhale_perm_stmts(loop_head, before_invariant_block, false).with_span(fnspec_span)?; + let stmts = self + .encode_loop_invariant_inhale_perm_stmts(loop_head, before_invariant_block, false) + .with_span(fnspec_span)?; self.cfg_method.add_stmts(inv_post_block_perms, stmts); } @@ -954,6 +1025,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { loop_guard_evaluation, loop_depth, return_block, + &scope_ids, )?; if let Some(mid_g_head) = &mid_g.0 { self.block_assert_to_assume(mid_g_head); @@ -965,6 +1037,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { loop_body_before_inv, loop_depth, return_block, + &scope_ids, )?; if let Some(mid_b1_head) = &mid_b1.0 { self.block_assert_to_assume(mid_b1_head); @@ -973,24 +1046,45 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { Some((mid_g, mid_b1)) } else { // Cannot add loop guard to loop invariant - let fn_names: Vec<_> = preconds.iter().filter_map(|(name, _)| name.as_ref()).map(|name| { - name.strip_prefix("m_").unwrap_or(name) - }).collect(); + let fn_names: Vec<_> = preconds + .iter() + .filter_map(|(name, _)| name.as_ref()) + .map(|name| name.strip_prefix("m_").unwrap_or(name)) + .collect(); let warning_msg = if fn_names.is_empty() { "the loop guard was not automatically added as a `body_invariant!(...)`, consider doing this manually".to_string() } else { "the loop guard was not automatically added as a `body_invariant!(...)`, \ - due to the following pure functions with preconditions: [".to_string() + &fn_names.join(", ") + "]" + due to the following pure functions with preconditions: [" + .to_string() + + &fn_names.join(", ") + + "]" }; // Span of loop guard - let span_lg = loop_guard_evaluation.last().map(|bb| self.mir_encoder.get_span_of_basic_block(*bb)); + let span_lg = loop_guard_evaluation + .last() + .map(|bb| self.mir_encoder.get_span_of_basic_block(*bb)); // Span of entire loop body before inv; the last elem (if any) will be the `body_invariant!(...)` bb - let span_lb = loop_body_before_inv.iter().rev().skip(1).map(|bb| self.mir_encoder.get_span_of_basic_block(*bb)) - .fold(None, |acc: Option, span| Some(acc.map(|other| - if span.ctxt() == other.ctxt() { span.to(other) } else { other } - ).unwrap_or(span)) ); + let span_lb = loop_body_before_inv + .iter() + .rev() + .skip(1) + .map(|bb| self.mir_encoder.get_span_of_basic_block(*bb)) + .fold(None, |acc: Option, span| { + Some( + acc.map(|other| { + if span.ctxt() == other.ctxt() { + span.to(other) + } else { + other + } + }) + .unwrap_or(span), + ) + }); // Multispan to highlight both - let span = MultiSpan::from_spans(vec![span_lg, span_lb].into_iter().flatten().collect()); + let span = + MultiSpan::from_spans(vec![span_lg, span_lb].into_iter().flatten().collect()); // It's only a warning so we might as well emit it straight away PrustiError::warning(warning_msg, span).emit(&self.encoder.env().diagnostic); None @@ -1002,6 +1096,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { loop_body_after_inv, loop_depth, return_block, + &scope_ids, )?; heads.push(last_b2_head); @@ -1011,6 +1106,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { loop_guard_evaluation, loop_depth, return_block, + &scope_ids, )?; heads.push(last_g_head); @@ -1020,6 +1116,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { loop_body_before_inv, loop_depth, return_block, + &scope_ids, )?; heads.push(last_b1_head); @@ -1036,13 +1133,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let stmts = self.encode_loop_invariant_exhale_stmts( loop_head, before_invariant_block, - true + true, + &scope_ids, )?; self.cfg_method.add_stmts(end_body_block, stmts); } self.cfg_method.add_stmt( end_body_block, - vir::Stmt::Inhale( vir::Inhale {expr: false.into()} ), + vir::Stmt::Inhale(vir::Inhale { expr: false.into() }), ); heads.push(Some(end_body_block)); @@ -1077,20 +1175,24 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { if let Some(((mid_g_head, mid_g_edges), (mid_b1_head, mid_b1_edges))) = mid_groups { let mid_b1_block = mid_b1_head.unwrap_or(inv_post_block_fnspc); // Link edges of "invariant_perm" (start - G - B1 - *invariant_perm* - G - B1 - invariant_fnspec - B2 - G - B1 - end) - self.cfg_method - .set_successor(inv_post_block_perms, vir::Successor::Goto(mid_g_head.unwrap_or(mid_b1_block))); + self.cfg_method.set_successor( + inv_post_block_perms, + vir::Successor::Goto(mid_g_head.unwrap_or(mid_b1_block)), + ); // We know that there is at least one loop iteration of B2, thus it is safe to assume // that anything beforehand couldn't have exited the loop. Here we use // `self.cfg_method.set_successor(..., Successor::Return)` as the same as `assume false` for (curr_block, target) in &mid_g_edges { if self.loop_encoder.get_loop_depth(*target) < loop_depth { - self.cfg_method.set_successor(*curr_block, Successor::Return); + self.cfg_method + .set_successor(*curr_block, Successor::Return); } } - let mid_g_edges = mid_g_edges.into_iter().filter(|(_, target)| - self.loop_encoder.get_loop_depth(*target) >= loop_depth - ).collect::>(); + let mid_g_edges = mid_g_edges + .into_iter() + .filter(|(_, target)| self.loop_encoder.get_loop_depth(*target) >= loop_depth) + .collect::>(); // Link edges from the mid G group (start - G - B1 - invariant_perm - *G* - B1 - invariant_fnspec - B2 - G - B1 - end) still_unresolved_edges.extend(self.encode_unresolved_edges(mid_g_edges, |bb| { if bb == after_guard_block { @@ -1104,12 +1206,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // that anything beforehand couldn't have exited the loop. for (curr_block, target) in &mid_b1_edges { if self.loop_encoder.get_loop_depth(*target) < loop_depth { - self.cfg_method.set_successor(*curr_block, Successor::Return); + self.cfg_method + .set_successor(*curr_block, Successor::Return); } } - let mid_b1_edges = mid_b1_edges.into_iter().filter(|(_, target)| - self.loop_encoder.get_loop_depth(*target) >= loop_depth - ).collect::>(); + let mid_b1_edges = mid_b1_edges + .into_iter() + .filter(|(_, target)| self.loop_encoder.get_loop_depth(*target) >= loop_depth) + .collect::>(); // Link edges from the mid B1 group (start - G - B1 - invariant_perm - G - *B1* - invariant_fnspec - B2 - G - B1 - end) still_unresolved_edges.extend(self.encode_unresolved_edges(mid_b1_edges, |bb| { if bb == after_inv_block { @@ -1120,8 +1224,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { })?); } else { // Link edges of "invariant_perm" (start - G - B1 - *invariant_perm* - invariant_fnspec - B2 - G - B1 - end) - self.cfg_method - .set_successor(inv_post_block_perms, vir::Successor::Goto(inv_post_block_fnspc)); + self.cfg_method.set_successor( + inv_post_block_perms, + vir::Successor::Goto(inv_post_block_fnspc), + ); } // Link edges of "invariant" (start - G - B1 - *invariant* - B2 - G - B1 - end) @@ -1178,13 +1284,18 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { vir::Type::Snapshot(_) => BuiltinMethodKind::HavocRef, vir::Type::Seq(_) => BuiltinMethodKind::HavocRef, vir::Type::Map(_) => BuiltinMethodKind::HavocRef, - vir::Type::Ref => return Err(SpannedEncodingError::internal( - format!("unexpected type of local variable {var:?}"), - self.mir.span, - )), + vir::Type::Ref => { + return Err(SpannedEncodingError::internal( + format!("unexpected type of local variable {var:?}"), + self.mir.span, + )) + } }; - let stmt = vir::Stmt::MethodCall( vir::MethodCall { - method_name: self.encoder.encode_builtin_method_use(builtin_method).with_span(loop_head_span)?, + let stmt = vir::Stmt::MethodCall(vir::MethodCall { + method_name: self + .encoder + .encode_builtin_method_use(builtin_method) + .with_span(loop_head_span)?, arguments: vec![], targets: vec![var], }); @@ -1205,6 +1316,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { label_prefix: &str, bbi: BasicBlockIndex, return_block: CfgBlockIndex, + scope_ids: &[isize], ) -> SpannedEncodingResult<(CfgBlockIndex, Vec<(CfgBlockIndex, BasicBlockIndex)>)> { debug_assert!(!self.procedure.is_spec_block(bbi)); @@ -1220,19 +1332,17 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .insert(curr_block); if self.loop_encoder.is_loop_head(bbi) { - self.cfg_method.add_stmt( - curr_block, - vir::Stmt::comment("This is a loop head"), - ); + self.cfg_method + .add_stmt(curr_block, vir::Stmt::comment("This is a loop head")); } self.encode_execution_flag(bbi, curr_block)?; - let opt_successor = self.encode_block_statements(bbi, curr_block)?; + let opt_successor = self.encode_block_statements(bbi, curr_block, scope_ids)?; let mir_successor: MirSuccessor = if let Some(successor) = opt_successor { // In case of unsupported statements, we do not encode the terminator successor } else { - self.encode_block_terminator(bbi, curr_block)? + self.encode_block_terminator(bbi, curr_block, scope_ids)? }; // Make sure that the @@ -1286,7 +1396,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let executed_flag_var = self.cfg_block_has_been_executed[&bbi].clone(); self.cfg_method.add_stmt( cfg_block, - vir::Stmt::Assign( vir::Assign { + vir::Stmt::Assign(vir::Assign { target: vir::Expr::local(executed_flag_var).set_pos(pos), source: true.into(), kind: vir::AssignKind::Copy, @@ -1301,6 +1411,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { &mut self, bbi: BasicBlockIndex, cfg_block: CfgBlockIndex, + scope_ids: &[isize], ) -> SpannedEncodingResult> { debug_assert!(!self.procedure.is_spec_block(bbi)); let bb_data = &self.mir.basic_blocks[bbi]; @@ -1312,7 +1423,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { statement_index: stmt_index, }; { - let (stmts, opt_succ) = self.encode_statement_at(location)?; + let (stmts, opt_succ) = self.encode_statement_at(location, scope_ids)?; debug_assert!(matches!(opt_succ, None | Some(MirSuccessor::Kill))); self.cfg_method.add_stmts(cfg_block, stmts); if opt_succ.is_some() { @@ -1335,6 +1446,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { &mut self, bbi: BasicBlockIndex, curr_block: CfgBlockIndex, + scope_ids: &[isize], ) -> SpannedEncodingResult { trace!("Encode terminator of {:?}", bbi); let bb_data = &self.mir.basic_blocks[bbi]; @@ -1342,7 +1454,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { block: bbi, statement_index: bb_data.statements.len(), }; - let (stmts, opt_mir_successor) = self.encode_statement_at(location)?; + let (stmts, opt_mir_successor) = self.encode_statement_at(location, scope_ids)?; self.cfg_method.add_stmts(curr_block, stmts); Ok(opt_mir_successor.unwrap()) } @@ -1352,6 +1464,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { fn encode_statement_at( &mut self, location: mir::Location, + scope_ids: &[isize], ) -> SpannedEncodingResult<(Vec, Option)> { debug!("Encode location {:?}", location); @@ -1364,7 +1477,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .map(|stmts| (stmts, None)) } else { let mir_term = bb_data.terminator(); - self.encode_terminator(mir_term, location) + self.encode_terminator(mir_term, location, scope_ids) .map(|(stmts, succ)| (stmts, Some(succ))) }; @@ -1374,9 +1487,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { Err(err) => { let unsupported_msg = match err.kind() { EncodingErrorKind::Unsupported(msg) - if config::allow_unreachable_unsupported_code() => { + if config::allow_unreachable_unsupported_code() => + { msg.to_string() - }, + } _ => { // Propagate the error return Err(err); @@ -1392,13 +1506,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { }; let stmts = vec![ vir::Stmt::comment(head_stmt), - vir::Stmt::comment( - format!("Unsupported feature: {unsupported_msg}") - ), - vir::Stmt::Assert( vir::Assert { + vir::Stmt::comment(format!("Unsupported feature: {unsupported_msg}")), + vir::Stmt::Assert(vir::Assert { expr: false.into(), position: pos, - }) + }), ]; (stmts, Some(MirSuccessor::Kill)) } @@ -1431,48 +1543,54 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { mir::StatementKind::Assign(box (lhs, ref rhs)) => { // Array access on the LHS should always be mutable (idx is always calculated // before, and just a separate local variable here) - let (lhs_place_encoding, ty, _) = self.mir_encoder.encode_place(lhs).with_span(span)?; + let (lhs_place_encoding, ty, _) = + self.mir_encoder.encode_place(lhs).with_span(span)?; match lhs_place_encoding { - PlaceEncoding::SliceAccess { box base, index, rust_slice_ty: rust_ty, .. } | - PlaceEncoding::ArrayAccess { box base, index, rust_array_ty: rust_ty, .. } => { + PlaceEncoding::SliceAccess { + box base, + index, + rust_slice_ty: rust_ty, + .. + } + | PlaceEncoding::ArrayAccess { + box base, + index, + rust_array_ty: rust_ty, + .. + } => { // Current stmt is of the form `arr[idx] = val`. This does not have an expiring // temporary variable, so we encode it differently from indexing into an array. - self.encode_array_direct_assign( - base, - index, - rust_ty, - rhs, - location, - )? + self.encode_array_direct_assign(base, index, rust_ty, rhs, location)? } _ => { - let (encoded_lhs, pre_stmts) = self.postprocess_place_encoding(lhs_place_encoding, ArrayAccessKind::Mutable(None, location)) + let (encoded_lhs, pre_stmts) = self + .postprocess_place_encoding( + lhs_place_encoding, + ArrayAccessKind::Mutable(None, location), + ) .with_span(span)?; stmts.extend(pre_stmts); - self.encode_assign( - encoded_lhs, - rhs, - ty, - location, - )? + self.encode_assign(encoded_lhs, rhs, ty, location)? } } } - ref x => return Err(SpannedEncodingError::unsupported( - format!("unsupported statement kind: {x:?}"), - span, - )), + ref x => { + return Err(SpannedEncodingError::unsupported( + format!("unsupported statement kind: {x:?}"), + span, + )) + } }; stmts.extend(encoding_stmts); Ok(self.set_stmts_default_pos(stmts, stmt.source_info.span)) } fn set_stmts_default_pos(&self, stmts: Vec, default_span: Span) -> Vec { - let pos = self.encoder.error_manager().register_span(self.proc_def_id, default_span); - stmts - .into_iter() - .map(|s| s.set_default_pos(pos)) - .collect() + let pos = self + .encoder + .error_manager() + .register_span(self.proc_def_id, default_span); + stmts.into_iter().map(|s| s.set_default_pos(pos)).collect() } /// Encode assignment of RHS to LHS, depending on what kind of thing the RHS is @@ -1490,106 +1608,51 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { self.encode_assign_operand(&encoded_lhs, operand, location)? } mir::Rvalue::Aggregate(ref aggregate, ref operands) => { - self.encode_assign_aggregate( - &encoded_lhs, - ty, - aggregate, - operands, - location - )? - }, + self.encode_assign_aggregate(&encoded_lhs, ty, aggregate, operands, location)? + } mir::Rvalue::BinaryOp(op, box (ref left, ref right)) => { - self.encode_assign_binary_op( - op, - left, - right, - encoded_lhs, - ty, - location - )? + self.encode_assign_binary_op(op, left, right, encoded_lhs, ty, location)? + } + mir::Rvalue::CheckedBinaryOp(op, box (ref left, ref right)) => { + self.encode_assign_checked_binary_op(op, left, right, encoded_lhs, ty, location)? } - mir::Rvalue::CheckedBinaryOp(op, box (ref left, ref right)) => self - .encode_assign_checked_binary_op( - op, - left, - right, - encoded_lhs, - ty, - location, - )?, mir::Rvalue::UnaryOp(op, ref operand) => { - self.encode_assign_unary_op( - op, - operand, - encoded_lhs, - ty, - location - )? + self.encode_assign_unary_op(op, operand, encoded_lhs, ty, location)? } mir::Rvalue::NullaryOp(op, op_ty) => { - self.encode_assign_nullary_op( - op, - op_ty, - encoded_lhs, - ty, - location - )? + self.encode_assign_nullary_op(op, op_ty, encoded_lhs, ty, location)? } mir::Rvalue::Discriminant(src) => { - self.encode_assign_discriminant( - src, - location, - encoded_lhs, - ty - )? + self.encode_assign_discriminant(src, location, encoded_lhs, ty)? } mir::Rvalue::Ref(_region, mir_borrow_kind, place) => { - self.encode_assign_ref( - mir_borrow_kind, - place, - location, - encoded_lhs, - ty - )? + self.encode_assign_ref(mir_borrow_kind, place, location, encoded_lhs, ty)? } - mir::Rvalue::Cast(mir::CastKind::PointerExposeAddress, ref operand, dst_ty) | - mir::Rvalue::Cast(mir::CastKind::PointerFromExposedAddress, ref operand, dst_ty) | - mir::Rvalue::Cast(mir::CastKind::IntToInt, ref operand, dst_ty) => { - self.encode_cast( - operand, - dst_ty, - encoded_lhs, - ty, - location, - )? + mir::Rvalue::Cast(mir::CastKind::PointerExposeAddress, ref operand, dst_ty) + | mir::Rvalue::Cast(mir::CastKind::PointerFromExposedAddress, ref operand, dst_ty) + | mir::Rvalue::Cast(mir::CastKind::IntToInt, ref operand, dst_ty) => { + self.encode_cast(operand, dst_ty, encoded_lhs, ty, location)? } mir::Rvalue::Len(place) => { - self.encode_assign_sequence_len( - encoded_lhs, - place, - ty, - location, - )? + self.encode_assign_sequence_len(encoded_lhs, place, ty, location)? } - mir::Rvalue::Repeat(ref operand, times) => { - self.encode_assign_array_repeat_initializer( + mir::Rvalue::Repeat(ref operand, times) => self + .encode_assign_array_repeat_initializer( encoded_lhs, operand, times, ty, location, - )? - } - mir::Rvalue::Cast(mir::CastKind::Pointer(ty::adjustment::PointerCast::Unsize), ref operand, cast_ty) => { + )?, + mir::Rvalue::Cast( + mir::CastKind::Pointer(ty::adjustment::PointerCast::Unsize), + ref operand, + cast_ty, + ) => { let rhs_ty = self.mir_encoder.get_operand_ty(operand); if rhs_ty.is_array_ref() && cast_ty.is_slice_ref() { trace!("slice: operand={:?}, ty={:?}", operand, cast_ty); - self.encode_assign_slice( - encoded_lhs, - operand, - cast_ty, - location, - )? + self.encode_assign_slice(encoded_lhs, operand, cast_ty, location)? } else { return Err(SpannedEncodingError::unsupported( format!("unsizing a {rhs_ty} into a {cast_ty} is not supported"), @@ -1597,15 +1660,17 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { )); } } - mir::Rvalue::Cast(mir::CastKind::Pointer(_), _, _) | - mir::Rvalue::Cast(mir::CastKind::DynStar, _, _) => { + mir::Rvalue::Cast(mir::CastKind::Pointer(_), _, _) + | mir::Rvalue::Cast(mir::CastKind::DynStar, _, _) => { return Err(SpannedEncodingError::unsupported( - "raw pointers are not supported", span + "raw pointers are not supported", + span, )); } mir::Rvalue::Cast(cast_kind, _, _) => { return Err(SpannedEncodingError::unsupported( - format!("casts {cast_kind:?} are not supported"), span + format!("casts {cast_kind:?} are not supported"), + span, )); } mir::Rvalue::AddressOf(_, _) => { @@ -1615,16 +1680,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } mir::Rvalue::ThreadLocalRef(_) => { return Err(SpannedEncodingError::unsupported( - "references to thread-local storage are not supported", span + "references to thread-local storage are not supported", + span, )); } mir::Rvalue::ShallowInitBox(_, op_ty) => { - self.encode_assign_box( - op_ty, - encoded_lhs, - ty, - location, - )? + self.encode_assign_box(op_ty, encoded_lhs, ty, location)? } mir::Rvalue::CopyForDeref(ref place) => { self.encode_assign_operand(&encoded_lhs, &mir::Operand::Copy(*place), location)? @@ -1650,13 +1711,23 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } /// Encode the lhs and the rhs of the assignment that create the loan - fn encode_loan_places(&mut self, loan_places: &LoanPlaces<'tcx>) -> SpannedEncodingResult<(vir::Expr, Option, bool, Vec)> { + fn encode_loan_places( + &mut self, + loan_places: &LoanPlaces<'tcx>, + ) -> SpannedEncodingResult<(vir::Expr, Option, bool, Vec)> { debug!("encode_loan_places '{:?}'", loan_places); let location = loan_places.location; let span = self.mir_encoder.get_span_of_location(location); - let (expiring_base, mut stmts, expiring_ty, _) = self.encode_place(loan_places.dest, ArrayAccessKind::Mutable(None, location), location)?; - trace!("expiring_base: {:?}", (&expiring_base, &stmts, &expiring_ty)); + let (expiring_base, mut stmts, expiring_ty, _) = self.encode_place( + loan_places.dest, + ArrayAccessKind::Mutable(None, location), + location, + )?; + trace!( + "expiring_base: {:?}", + (&expiring_base, &stmts, &expiring_ty) + ); // the original encoding of arrays is with a sort-of magic temporary variable, so // `postprocess_place_encoding` will return `i32` here instead of Array$3$i32. so here @@ -1666,12 +1737,19 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let (rhs_place_encoding, ..) = self.mir_encoder.encode_place(rhs_place).unwrap(); if let PlaceEncoding::ArrayAccess { .. } = rhs_place_encoding { // encode expiry of the array borrow - let (expired_expr, regained_array, wand_rhs) = self.array_magic_wand_at[&location].clone(); + let (expired_expr, regained_array, wand_rhs) = + self.array_magic_wand_at[&location].clone(); // expiring base is something like ref$i32, so we need .val_ref.val_int - let deref = self.encoder.encode_value_expr(expiring_base.clone(), expiring_ty).with_span(span)?; + let deref = self + .encoder + .encode_value_expr(expiring_base.clone(), expiring_ty) + .with_span(span)?; let target_ty = expiring_ty.peel_refs(); - let expiring_base_value = self.encoder.encode_value_expr(deref, target_ty).with_span(span)?; + let expiring_base_value = self + .encoder + .encode_value_expr(deref, target_ty) + .with_span(span)?; // the original magic wand refered to the temporary variable that we created for array // encoding. the expiry here refers to the non-temporary rust variable, so we need @@ -1682,25 +1760,31 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // so there's no LHS. instead, we add a label just before the inhale, and refer to // that let new_lhs_label = self.cfg_method.get_fresh_label_name(); - stmts.push( - vir::Stmt::label(new_lhs_label.clone()) - ); + stmts.push(vir::Stmt::label(new_lhs_label.clone())); - let wand_rhs_patched_lhs = wand_rhs - .map_old_expr_label(|label| if label == "lhs" { + let wand_rhs_patched_lhs = wand_rhs.map_old_expr_label(|label| { + if label == "lhs" { trace!("{} -> {}", label, new_lhs_label); new_lhs_label.clone() } else { label } - ); + }); - let expiring_base_pred = self.mir_encoder.encode_place_predicate_permission(expiring_base.clone(), vir::PermAmount::Write).unwrap(); - stmts.push(vir_stmt!{ exhale [expiring_base_pred] }); + let expiring_base_pred = self + .mir_encoder + .encode_place_predicate_permission( + expiring_base.clone(), + vir::PermAmount::Write, + ) + .unwrap(); + stmts.push(vir_stmt! { exhale [expiring_base_pred] }); - let arr_pred = vir::Expr::pred_permission(regained_array.clone(), vir::PermAmount::Write).unwrap(); - stmts.push(vir_stmt!{ inhale [arr_pred] }); - stmts.push(vir_stmt!{ inhale [wand_rhs_patched_lhs] }); + let arr_pred = + vir::Expr::pred_permission(regained_array.clone(), vir::PermAmount::Write) + .unwrap(); + stmts.push(vir_stmt! { inhale [arr_pred] }); + stmts.push(vir_stmt! { inhale [wand_rhs_patched_lhs] }); // NOTE: the third tuple elem is called is_mut in // `construct_vir_reborrowing_node_for_assignment`, and is currently only used to @@ -1712,10 +1796,17 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } } - let mut encode = |rhs_place, stmts: &mut Vec, array_encode_kind| -> SpannedEncodingResult<_> { - let (restored, pre_stmts, _, _) = self.encode_place(rhs_place, array_encode_kind, location)?; + let mut encode = |rhs_place, + stmts: &mut Vec, + array_encode_kind| + -> SpannedEncodingResult<_> { + let (restored, pre_stmts, _, _) = + self.encode_place(rhs_place, array_encode_kind, location)?; stmts.extend(pre_stmts); - let ref_field = self.encoder.encode_value_field(expiring_ty).with_span(span)?; + let ref_field = self + .encoder + .encode_value_field(expiring_ty) + .with_span(span)?; let expiring = expiring_base.clone().field(ref_field.clone()); Ok((expiring, restored, ref_field)) }; @@ -1726,32 +1817,43 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { mir::BorrowKind::Mut { .. } => true, _ => return Err(Self::unsupported_borrow_kind(mir_borrow_kind).with_span(span)), }; - let array_encode_kind = if is_mut { ArrayAccessKind::Mutable(None, location) } else { ArrayAccessKind::Shared }; + let array_encode_kind = if is_mut { + ArrayAccessKind::Mutable(None, location) + } else { + ArrayAccessKind::Shared + }; let (expiring, restored, _) = encode(rhs_place, &mut stmts, array_encode_kind)?; assert_eq!(expiring.get_type(), restored.get_type()); (expiring, Some(restored), is_mut, stmts) } mir::Rvalue::Use(mir::Operand::Move(rhs_place)) => { - let (expiring, restored_base, ref_field) = encode(rhs_place, &mut stmts, ArrayAccessKind::Shared)?; + let (expiring, restored_base, ref_field) = + encode(rhs_place, &mut stmts, ArrayAccessKind::Shared)?; let restored = restored_base.field(ref_field); assert_eq!(expiring.get_type(), restored.get_type()); (expiring, Some(restored), true, stmts) } mir::Rvalue::Use(mir::Operand::Copy(rhs_place)) => { - let (expiring, restored_base, ref_field) = encode(rhs_place, &mut stmts, ArrayAccessKind::Shared)?; + let (expiring, restored_base, ref_field) = + encode(rhs_place, &mut stmts, ArrayAccessKind::Shared)?; let restored = restored_base.field(ref_field); assert_eq!(expiring.get_type(), restored.get_type()); (expiring, Some(restored), false, stmts) } - mir::Rvalue::Cast(mir::CastKind::Pointer(ty::adjustment::PointerCast::Unsize), ref operand, ty) => { + mir::Rvalue::Cast( + mir::CastKind::Pointer(ty::adjustment::PointerCast::Unsize), + ref operand, + ty, + ) => { trace!("cast: operand={:?}, ty={:?}", operand, ty); let place = match *operand { mir::Operand::Move(place) => place, mir::Operand::Copy(place) => place, _ => unreachable!("operand: {:?}", operand), }; - let (restored, r_stmts, ..) = self.encode_place(place, ArrayAccessKind::Shared, location)?; + let (restored, r_stmts, ..) = + self.encode_place(place, ArrayAccessKind::Shared, location)?; stmts.extend(r_stmts); (expiring_base, Some(restored), false, stmts) @@ -1760,12 +1862,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { mir::Rvalue::Use(mir::Operand::Constant(ref expr)) => { // TODO: Encoding of string literals is not yet supported, so // do not return an expression in restored here. - let restored: Option = - if is_str(expr.ty()) { - None - } else { - Some(self.encoder.encode_const_expr(expr.ty(), expr.literal).with_span(span)?) - }; + let restored: Option = if is_str(expr.ty()) { + None + } else { + Some( + self.encoder + .encode_const_expr(expr.ty(), expr.literal) + .with_span(span)?, + ) + }; (expiring_base, restored, false, stmts) } @@ -1782,13 +1887,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { is_in_package_stmt: bool, ) -> Vec { let mut stmts = if let Some(var) = self.old_to_ghost_var.get(&rhs) { - vec![vir::Stmt::Assign( vir::Assign { + vec![vir::Stmt::Assign(vir::Assign { target: var.clone(), source: lhs.clone(), kind: vir::AssignKind::Move, })] } else { - vec![vir::Stmt::TransferPerm( vir::TransferPerm { + vec![vir::Stmt::TransferPerm(vir::TransferPerm { left: lhs.clone(), right: rhs.clone(), unchecked: false, @@ -1796,8 +1901,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { }; if self.check_foldunfold_state && !is_in_package_stmt { - let pos = self.register_error(self.mir.source_info(location).span, ErrorCtxt::Unexpected); - stmts.push(vir::Stmt::Assert( vir::Assert { + let pos = + self.register_error(self.mir.source_info(location).span, ErrorCtxt::Unexpected); + stmts.push(vir::Stmt::Assert(vir::Assert { expr: vir::Expr::eq_cmp(lhs, rhs), position: pos, })); @@ -1807,17 +1913,17 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } fn encode_obtain(&mut self, expr: vir::Expr, pos: vir::Position) -> Vec { - let mut stmts = vec![ - vir::Stmt::Obtain( vir::Obtain { - expr: expr.clone(), - position: pos, - }) - ]; + let mut stmts = vec![vir::Stmt::Obtain(vir::Obtain { + expr: expr.clone(), + position: pos, + })]; if self.check_foldunfold_state { let new_pos = self.encoder.error_manager().duplicate_position(pos); - self.encoder.error_manager().set_error(new_pos, ErrorCtxt::Unexpected); - stmts.push(vir::Stmt::Assert( vir::Assert { + self.encoder + .error_manager() + .set_error(new_pos, ErrorCtxt::Unexpected); + stmts.push(vir::Stmt::Assert(vir::Assert { expr, position: new_pos, })); @@ -1828,17 +1934,19 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { /// A borrow is mutable if it was a MIR unique borrow, a move of /// a borrow, or a argument of a function. - fn is_mutable_borrow(&self, loan: facts::Loan) - -> EncodingResult - { + fn is_mutable_borrow(&self, loan: facts::Loan) -> EncodingResult { if let Some(stmt) = self.polonius_info().get_assignment_for_loan(loan)? { Ok(match stmt.kind { mir::StatementKind::Assign(box (_, ref rhs)) => match rhs { - &mir::Rvalue::Ref(_, mir::BorrowKind::Shared, _) | - &mir::Rvalue::Use(mir::Operand::Copy(_)) => false, - &mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, _) | - &mir::Rvalue::Use(mir::Operand::Move(_)) => true, - &mir::Rvalue::Cast(mir::CastKind::Pointer(ty::adjustment::PointerCast::Unsize), _, _ty) => false, + &mir::Rvalue::Ref(_, mir::BorrowKind::Shared, _) + | &mir::Rvalue::Use(mir::Operand::Copy(_)) => false, + &mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, _) + | &mir::Rvalue::Use(mir::Operand::Move(_)) => true, + &mir::Rvalue::Cast( + mir::CastKind::Pointer(ty::adjustment::PointerCast::Unsize), + _, + _ty, + ) => false, &mir::Rvalue::Use(mir::Operand::Constant(_)) => false, x => unreachable!("{:?}", x), }, @@ -1879,11 +1987,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { is_in_package_stmt, )?, ReborrowingKind::Call { loan, .. } => { - if let Some(slice_expiry_node) = self.construct_vir_reborrowing_node_for_slice( - loan, - node, - location, - )? { + if let Some(slice_expiry_node) = + self.construct_vir_reborrowing_node_for_slice(loan, node, location)? + { slice_expiry_node } else { self.construct_vir_reborrowing_node_for_call( @@ -1891,7 +1997,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { loan, node, location, - is_in_package_stmt + is_in_package_stmt, )? } } @@ -1941,34 +2047,34 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let loan_location = self.polonius_info().get_loan_location(&loan); trace!("loan_location: {:?}", loan_location); - let loan_places = self.polonius_info().get_loan_places(&loan) + let loan_places = self + .polonius_info() + .get_loan_places(&loan) .map_err(EncodingError::from) .with_span(span)?; trace!("loan_places: {:?}", loan_places); trace!("node: {:?}", node); - Ok(if let Some(regained) = self.slice_created_at.get(&loan_location) { - let guard = self.construct_location_guard(loan_location); - trace!("guard: {:?}", guard); - trace!("regained: {:?}", regained); - Some( - vir::borrows::Node::new( + Ok( + if let Some(regained) = self.slice_created_at.get(&loan_location) { + let guard = self.construct_location_guard(loan_location); + trace!("guard: {:?}", guard); + trace!("regained: {:?}", regained); + Some(vir::borrows::Node::new( guard, node.loan.index().into(), convert_loans_to_borrows(&node.reborrowing_loans), convert_loans_to_borrows(&node.reborrowed_loans), - vec![ - vir::Stmt::comment("hi"), - ], + vec![vir::Stmt::comment("hi")], vec![regained.clone()], Vec::new(), Vec::new(), None, - ) - ) - } else { - None - }) + )) + } else { + None + }, + ) } fn construct_vir_reborrowing_node_for_assignment( @@ -1984,11 +2090,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let node_is_leaf = node.reborrowed_loans.is_empty(); let loan_location = self.polonius_info().get_loan_location(&loan); - let loan_places = self.polonius_info().get_loan_places(&loan) + let loan_places = self + .polonius_info() + .get_loan_places(&loan) .map_err(EncodingError::from) - .with_span(span)?.unwrap(); - let (expiring, restored, is_mut, mut stmts) = self.encode_loan_places(&loan_places) - .with_span(span)?; + .with_span(span)? + .unwrap(); + let (expiring, restored, is_mut, mut stmts) = + self.encode_loan_places(&loan_places).with_span(span)?; let borrowed_places = restored.clone().into_iter().collect(); trace!("construct_vir_reborrowing_node_for_assignment(loan={:?}, loan_places={:?}, expiring={:?}, restored={:?}, stmts={:?}", loan, loan_places, expiring, restored, stmts); @@ -1996,12 +2105,18 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // Move the permissions from the "in loans" ("reborrowing loans") to the current loan if node.incoming_zombies && restored.is_some() { - let lhs_label = self.get_label_after_location(loan_location)?.unwrap().to_string(); + let lhs_label = self + .get_label_after_location(loan_location)? + .unwrap() + .to_string(); for &in_loan in node.reborrowing_loans.iter() { // TODO: Is this the correct span? if self.is_mutable_borrow(in_loan).with_span(span)? { let in_location = self.polonius_info().get_loan_location(&in_loan); - let in_label = self.get_label_after_location(in_location)?.unwrap().to_string(); + let in_label = self + .get_label_after_location(in_location)? + .unwrap() + .to_string(); used_lhs_label = true; stmts.extend(self.encode_transfer_permissions( expiring.clone().old(in_label), @@ -2078,9 +2193,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // Get the borrow information. if !self.procedure_contracts.contains_key(&loan_location) { return Err(SpannedEncodingError::internal( - format!("There is no procedure contract for loan {loan:?}. This could happen if you \ - are chaining pure functions, which is not fully supported."), - span + format!( + "There is no procedure contract for loan {loan:?}. This could happen if you \ + are chaining pure functions, which is not fully supported." + ), + span, )); } let (contract, fake_exprs) = self.procedure_contracts[&loan_location].clone(); @@ -2093,10 +2210,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let borrow_infos = &contract.borrow_infos; match borrow_infos.len().cmp(&1) { std::cmp::Ordering::Less => (), - std::cmp::Ordering::Greater => return Err(SpannedEncodingError::internal( - format!("We require at most one magic wand in the postcondition. But we have {:?}", borrow_infos.len()), - span, - )), + std::cmp::Ordering::Greater => { + return Err(SpannedEncodingError::internal( + format!( + "We require at most one magic wand in the postcondition. But we have {:?}", + borrow_infos.len() + ), + span, + )) + } std::cmp::Ordering::Equal => { let borrow_info = &borrow_infos[0]; @@ -2114,9 +2236,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // Obtain the LHS permission. for (path, _) in &borrow_info.blocking_paths { - let (encoded_place, _, _) = self.encode_generic_place( - contract.def_id, Some(loan_location), *path - ).with_span(span)?; + let (encoded_place, _, _) = self + .encode_generic_place(contract.def_id, Some(loan_location), *path) + .with_span(span)?; let encoded_place = replace_fake_exprs(encoded_place); // Move the permissions from the "in loans" ("reborrowing loans") to the current loan @@ -2151,15 +2273,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ErrorCtxt::ApplyMagicWandOnExpiry, ); // Inhale the magic wand. - let magic_wand = vir::Expr::MagicWand( vir::MagicWand { + let magic_wand = vir::Expr::MagicWand(vir::MagicWand { left: box lhs.clone(), right: box rhs.clone(), borrow: Some(loan.index().into()), position: pos, }); - stmts.push(vir::Stmt::Inhale( vir::Inhale { - expr: magic_wand - })); + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: magic_wand })); // Emit the apply statement. let statement = vir::Stmt::apply_magic_wand(lhs, rhs, loan.index().into(), pos); debug!("{:?} at {:?}", statement, loan_location); @@ -2196,9 +2316,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ); let mut stmts: Vec = vec![]; if !loans.is_empty() { - let vir_reborrowing_dag = - self.construct_vir_reborrowing_dag(&loans, zombie_loans, location, end_location, is_in_package_stmt)?; - stmts.push(vir::Stmt::ExpireBorrows( vir::ExpireBorrows { + let vir_reborrowing_dag = self.construct_vir_reborrowing_dag( + &loans, + zombie_loans, + location, + end_location, + is_in_package_stmt, + )?; + stmts.push(vir::Stmt::ExpireBorrows(vir::ExpireBorrows { dag: vir_reborrowing_dag, })); } @@ -2218,15 +2343,23 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .polonius_info() .get_all_loans_dying_between(begin_loc, end_loc); // FIXME: is 'end_loc' correct here? What about 'begin_loc'? - self.encode_expiration_of_loans(all_dying_loans, &zombie_loans, begin_loc, Some(end_loc), false) + self.encode_expiration_of_loans( + all_dying_loans, + &zombie_loans, + begin_loc, + Some(end_loc), + false, + ) } - fn encode_expiring_borrows_at(&mut self, location: mir::Location) - -> SpannedEncodingResult> - { + fn encode_expiring_borrows_at( + &mut self, + location: mir::Location, + ) -> SpannedEncodingResult> { debug!("encode_expiring_borrows_at '{:?}'", location); let (all_dying_loans, zombie_loans) = self.polonius_info().get_all_loans_dying_at(location); - let stmts = self.encode_expiration_of_loans(all_dying_loans, &zombie_loans, location, None, false)?; + let stmts = + self.encode_expiration_of_loans(all_dying_loans, &zombie_loans, location, None, false)?; Ok(self.set_stmts_default_pos(stmts, self.mir_encoder.get_span_of_location(location))) } @@ -2235,6 +2368,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { &mut self, term: &mir::Terminator<'tcx>, location: mir::Location, + scope_ids: &[isize], ) -> SpannedEncodingResult<(Vec, MirSuccessor)> { debug!( "Encode terminator '{:?}', span: {:?}", @@ -2301,29 +2435,27 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // Use a local variable for the discriminant. // See Silicon issue https://github.com/viperproject/silicon/issues/356 let discr_var = match switch_ty.kind() { - ty::TyKind::Bool => { - self.cfg_method.add_fresh_local_var(vir::Type::Bool) - } + ty::TyKind::Bool => self.cfg_method.add_fresh_local_var(vir::Type::Bool), - ty::TyKind::Int(_) - | ty::TyKind::Uint(_) - | ty::TyKind::Char => { + ty::TyKind::Int(_) | ty::TyKind::Uint(_) | ty::TyKind::Char => { self.cfg_method.add_fresh_local_var(vir::Type::Int) } - ty::TyKind::Float(ty::FloatTy::F32) => { - self.cfg_method.add_fresh_local_var(vir::Type::Float(Float::F32)) - } + ty::TyKind::Float(ty::FloatTy::F32) => self + .cfg_method + .add_fresh_local_var(vir::Type::Float(Float::F32)), - ty::TyKind::Float(ty::FloatTy::F64) => { - self.cfg_method.add_fresh_local_var(vir::Type::Float(Float::F64)) - } + ty::TyKind::Float(ty::FloatTy::F64) => self + .cfg_method + .add_fresh_local_var(vir::Type::Float(Float::F64)), ref x => unreachable!("{:?}", x), }; - let encoded_discr = self.mir_encoder.encode_operand_expr(discr) + let encoded_discr = self + .mir_encoder + .encode_operand_expr(discr) .with_span(span)?; - stmts.push(vir::Stmt::Assign( vir::Assign { + stmts.push(vir::Stmt::Assign(vir::Assign { target: discr_var.clone().into(), source: encoded_discr, kind: vir::AssignKind::Copy, @@ -2344,12 +2476,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } } - ty::TyKind::Int(_) - | ty::TyKind::Uint(_) - | ty::TyKind::Char => vir::Expr::eq_cmp( - discr_var.clone().into(), - self.encoder.encode_int_cast(value, switch_ty), - ), + ty::TyKind::Int(_) | ty::TyKind::Uint(_) | ty::TyKind::Char => { + vir::Expr::eq_cmp( + discr_var.clone().into(), + self.encoder.encode_int_cast(value, switch_ty), + ) + } ref x => unreachable!("{:?}", x), }; @@ -2386,13 +2518,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { if guard_is_bool && cfg_targets.len() == 1 { let (target_guard, target) = cfg_targets.pop().unwrap(); let target_span = self.mir_encoder.get_span_of_basic_block(target); - let default_target_span = self.mir_encoder.get_span_of_basic_block(default_target); + let default_target_span = + self.mir_encoder.get_span_of_basic_block(default_target); if target_span > default_target_span { let guard_pos = target_guard.pos(); - cfg_targets = vec![( - target_guard.negate().set_pos(guard_pos), - default_target, - )]; + cfg_targets = + vec![(target_guard.negate().set_pos(guard_pos), default_target)]; default_target = target; } else { // Undo the pop @@ -2417,7 +2548,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { TerminatorKind::Abort => { let pos = self.register_error(term.source_info.span, ErrorCtxt::AbortTerminator); - stmts.push(vir::Stmt::Assert( vir::Assert { + stmts.push(vir::Stmt::Assert(vir::Assert { expr: false.into(), position: pos, })); @@ -2440,11 +2571,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ref value, .. } => { - let (encoded_lhs, pre_stmts, _, _) = self.encode_place(lhs, ArrayAccessKind::Mutable(None, location), location)?; + let (encoded_lhs, pre_stmts, _, _) = + self.encode_place(lhs, ArrayAccessKind::Mutable(None, location), location)?; stmts.extend(pre_stmts); - stmts.extend( - self.encode_assign_operand(&encoded_lhs, value, location)? - ); + stmts.extend(self.encode_assign_operand(&encoded_lhs, value, location)?); (stmts, MirSuccessor::Goto(target)) } @@ -2452,21 +2582,23 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ref args, destination, target, - func: - mir::Operand::Constant(box mir::Constant { - literal, - .. - }), + func: mir::Operand::Constant(box mir::Constant { literal, .. }), .. } => { let ty = literal.ty(); let func_const_val = literal.try_to_value(self.encoder.env().tcx()); if let ty::TyKind::FnDef(called_def_id, call_substs) = ty.kind() { let called_def_id = *called_def_id; - debug!("Encode function call {:?} with substs {:?}", called_def_id, call_substs); + debug!( + "Encode function call {:?} with substs {:?}", + called_def_id, call_substs + ); - let full_func_proc_name: &str = - &self.encoder.env().name.get_absolute_item_name(called_def_id); + let full_func_proc_name: &str = &self + .encoder + .env() + .name + .get_absolute_item_name(called_def_id); match full_func_proc_name { "std::rt::begin_panic" @@ -2479,19 +2611,18 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // Example of args[0]: 'const "internal error: entered unreachable code"' let panic_message = format!("{:?}", args[0]); - let panic_cause = self.mir_encoder.encode_panic_cause( - term.source_info.span - ); + let panic_cause = + self.mir_encoder.encode_panic_cause(term.source_info.span); let pos = self.register_error( - term.source_info.span, - ErrorCtxt::Panic(panic_cause), - ); + term.source_info.span, + ErrorCtxt::Panic(panic_cause), + ); if self.check_panics { stmts.push(vir::Stmt::comment(format!( "Rust panic - {panic_message}" ))); - stmts.push(vir::Stmt::Assert( vir::Assert { + stmts.push(vir::Stmt::Assert(vir::Assert { expr: false.into(), position: pos, })); @@ -2500,94 +2631,93 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } } - "std::boxed::Box::::new" - | "alloc::boxed::Box::::new" => { + "std::boxed::Box::::new" | "alloc::boxed::Box::::new" => { // This is the initialization of a box // args[0]: value to put in the box assert_eq!(args.len(), 1); - let (dst, pre_stmts, dest_ty, _) = self.encode_place(destination, ArrayAccessKind::Shared, location)?; + let (dst, pre_stmts, dest_ty, _) = + self.encode_place(destination, ArrayAccessKind::Shared, location)?; stmts.extend(pre_stmts); let boxed_ty = dest_ty.boxed_ty(); - let ref_field = self.encoder.encode_dereference_field(boxed_ty) + let ref_field = self + .encoder + .encode_dereference_field(boxed_ty) .with_span(span)?; let box_content = dst.clone().field(ref_field.clone()); - stmts.extend( - self.prepare_assign_target( - dst, - ref_field, - location, - vir::AssignKind::Move, - false - )? - ); + stmts.extend(self.prepare_assign_target( + dst, + ref_field, + location, + vir::AssignKind::Move, + false, + )?); // Allocate `box_content` - stmts.extend(self.encode_havoc_and_initialization(&box_content).with_span(span)?); - - // Initialize `box_content` stmts.extend( - self.encode_assign_operand( - &box_content, - &args[0], - location - )? + self.encode_havoc_and_initialization(&box_content) + .with_span(span)?, ); + + // Initialize `box_content` + stmts.extend(self.encode_assign_operand( + &box_content, + &args[0], + location, + )?); } - "std::cmp::PartialEq::eq" | - "core::cmp::PartialEq::eq" - if args.len() == 2 && - self.encoder.has_structural_eq_impl( - self.mir_encoder.get_operand_ty(&args[0]) - ) - => { + "std::cmp::PartialEq::eq" | "core::cmp::PartialEq::eq" + if args.len() == 2 + && self.encoder.has_structural_eq_impl( + self.mir_encoder.get_operand_ty(&args[0]), + ) => + { debug!("Encoding call of PartialEq::eq"); - stmts.extend( - self.encode_cmp_function_call( - called_def_id, - location, - term.source_info.span, - args, - destination, - target, - vir::BinaryOpKind::EqCmp, - call_substs, - )? - ); + stmts.extend(self.encode_cmp_function_call( + called_def_id, + location, + term.source_info.span, + args, + destination, + target, + vir::BinaryOpKind::EqCmp, + call_substs, + scope_ids, + )?); } - "std::cmp::PartialEq::ne" | - "core::cmp::PartialEq::ne" - if args.len() == 2 && - self.encoder.has_structural_eq_impl( - self.mir_encoder.get_operand_ty(&args[0]) - ) - => { + "std::cmp::PartialEq::ne" | "core::cmp::PartialEq::ne" + if args.len() == 2 + && self.encoder.has_structural_eq_impl( + self.mir_encoder.get_operand_ty(&args[0]), + ) => + { debug!("Encoding call of PartialEq::ne"); - stmts.extend( - self.encode_cmp_function_call( - called_def_id, - location, - term.source_info.span, - args, - destination, - target, - vir::BinaryOpKind::NeCmp, - call_substs, - )? - ); + stmts.extend(self.encode_cmp_function_call( + called_def_id, + location, + term.source_info.span, + args, + destination, + target, + vir::BinaryOpKind::NeCmp, + call_substs, + scope_ids, + )?); } - "std::ops::Fn::call" - | "core::ops::Fn::call" => { + "std::ops::Fn::call" | "core::ops::Fn::call" => { let cl_type: ty::Ty = call_substs[0].expect_ty(); match cl_type.kind() { ty::TyKind::Closure(cl_def_id, _) => { - debug!("Encoding call to closure {:?} with func {:?}", cl_def_id, func_const_val); + debug!( + "Encoding call to closure {:?} with func {:?}", + cl_def_id, func_const_val + ); stmts.extend(self.encode_impure_function_call( location, term.source_info.span, @@ -2596,6 +2726,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { target, *cl_def_id, call_substs, + scope_ids, )?); } @@ -2610,18 +2741,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { "core::slice::::len" => { debug!("Encoding call of slice::len"); - stmts.extend( - self.encode_slice_len_call( - destination, - args, - location, - span, - )? - ); + stmts.extend(self.encode_slice_len_call( + destination, + args, + location, + span, + )?); } - "std::iter::Iterator::next" | - "core::iter::Iterator::next" => { + "std::iter::Iterator::next" | "core::iter::Iterator::next" => { return Err(SpannedEncodingError::unsupported( "iterators are not fully supported yet", term.source_info.span, @@ -2629,16 +2757,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } // TODO: use extern_spec - "core::ops::IndexMut::index_mut" | - "std::ops::IndexMut::index_mut" => { + "core::ops::IndexMut::index_mut" | "std::ops::IndexMut::index_mut" => { return Err(SpannedEncodingError::unsupported( "mutably slicing is not fully supported yet", term.source_info.span, )); } - "core::ops::Index::index" | - "std::ops::Index::index" => { + "core::ops::Index::index" | "std::ops::Index::index" => { debug!("Encoding call of array/slice index call"); stmts.extend( self.encode_sequence_index_call( @@ -2646,7 +2772,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { args, location, term.source_info.span, - ).with_span(span)? + ) + .with_span(span)?, ); } @@ -2654,7 +2781,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // The called method might be a trait method. // We try to resolve it to the concrete implementation // and type substitutions. - let (called_def_id, call_substs) = self.encoder.env().query + let (called_def_id, call_substs) = self + .encoder + .env() + .query .resolve_method_call(self.proc_def_id, called_def_id, call_substs); let is_pure_function = self.encoder.is_pure(called_def_id, Some(call_substs)) && @@ -2664,7 +2794,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { self.proc_def_id != called_def_id; if is_pure_function { let def_id = called_def_id; - let (function_name, _) = self.encoder + let (function_name, _) = self + .encoder .encode_pure_function_use(def_id, self.proc_def_id, call_substs) .with_default_span(term.source_info.span)?; debug!("Encoding pure function call '{}'", function_name); @@ -2674,29 +2805,26 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let _ = self.mir_encoder.encode_operand_expr(operand); } - stmts.extend( - self.encode_pure_function_call( - location, - term.source_info.span, - args, - destination, - target, - def_id, - call_substs, - )? - ); + stmts.extend(self.encode_pure_function_call( + location, + term.source_info.span, + args, + destination, + target, + def_id, + call_substs, + )?); } else { - stmts.extend( - self.encode_impure_function_call( - location, - term.source_info.span, - args, - destination, - target, - called_def_id, - call_substs, - )? - ); + stmts.extend(self.encode_impure_function_call( + location, + term.source_info.span, + args, + destination, + target, + called_def_id, + call_substs, + scope_ids, + )?); } } } @@ -2733,12 +2861,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // Use local variables in the switch/if. // See Silicon issue https://github.com/viperproject/silicon/issues/356 let cond_var = self.cfg_method.add_fresh_local_var(vir::Type::Bool); - stmts.push(vir::Stmt::Assign( vir::Assign { + stmts.push(vir::Stmt::Assign(vir::Assign { target: cond_var.clone().into(), - source: self.mir_encoder.encode_operand_expr(cond) - .with_span( - self.mir_encoder.get_span_of_location(location) - )?, + source: self + .mir_encoder + .encode_operand_expr(cond) + .with_span(self.mir_encoder.get_span_of_location(location))?, kind: vir::AssignKind::Copy, })); @@ -2760,18 +2888,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { stmts.push(vir::Stmt::comment(format!("Rust assertion: {assert_msg}"))); if self.check_panics { - stmts.push(vir::Stmt::Assert( vir::Assert { + stmts.push(vir::Stmt::Assert(vir::Assert { expr: viper_guard, - position: self.register_error( - term.source_info.span, - error_ctxt, - ), + position: self.register_error(term.source_info.span, error_ctxt), })); } else { stmts.push(vir::Stmt::comment("This assertion will not be checked")); - stmts.push(vir::Stmt::Inhale( vir::Inhale { - expr: viper_guard, - })); + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: viper_guard })); }; (stmts, MirSuccessor::Goto(target)) @@ -2793,7 +2916,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { span: Span, ) -> SpannedEncodingResult> { assert!(args.len() == 1, "unexpected args to slice::len(): {args:?}"); - let slice_operand = self.mir_encoder.encode_operand_expr(&args[0]) + let slice_operand = self + .mir_encoder + .encode_operand_expr(&args[0]) .with_span(span)?; let mut stmts = vec![]; @@ -2803,28 +2928,30 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { stmts.push(vir::Stmt::label(label.clone())); let slice_ty_ref = self.mir_encoder.get_operand_ty(&args[0]); - let slice_ty = if let ty::TyKind::Ref(_, slice_ty, _) = slice_ty_ref.kind() { slice_ty } else { unreachable!() }; - let slice_types = self.encoder.encode_sequence_types(*slice_ty).with_span(span)?; + let slice_ty = if let ty::TyKind::Ref(_, slice_ty, _) = slice_ty_ref.kind() { + slice_ty + } else { + unreachable!() + }; + let slice_types = self + .encoder + .encode_sequence_types(*slice_ty) + .with_span(span)?; let rhs = slice_types.len(self.encoder, slice_operand); - let (encoded_lhs, encode_stmts, ty, _) = self.encode_place( - destination, - ArrayAccessKind::Mutable(None, location), - location - ).with_span(span)?; + let (encoded_lhs, encode_stmts, ty, _) = self + .encode_place( + destination, + ArrayAccessKind::Mutable(None, location), + location, + ) + .with_span(span)?; stmts.extend(encode_stmts); - stmts.extend( - self.encode_copy_value_assign( - encoded_lhs, - rhs, - ty, - location, - )? - ); + stmts.extend(self.encode_copy_value_assign(encoded_lhs, rhs, ty, location)?); - self.encode_transfer_args_permissions(location, args, &mut stmts, &label, false)?; + self.encode_transfer_args_permissions(location, args, &mut stmts, &label, false)?; // Store a label for permissions got back from the call debug!( @@ -2841,13 +2968,20 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { destination: mir::Place<'tcx>, args: &[mir::Operand<'tcx>], location: mir::Location, - error_span: Span + error_span: Span, ) -> EncodingResult> { - trace!("encode_sequence_index_call(destination={:?}, args={:?})", destination, args); + trace!( + "encode_sequence_index_call(destination={:?}, args={:?})", + destination, + args + ); // args[0] is the base array/slice, args[1] is the index // index is not specified exactly, as std::ops::Index::index is a trait method, so all we // know is there needs to be an impl Index for T - assert!(args.len() == 2, "unexpected args to sequence index call: {args:?}"); + assert!( + args.len() == 2, + "unexpected args to sequence index call: {args:?}" + ); let mut stmts = vec![]; @@ -2865,7 +2999,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { if !lhs_ty.is_slice_or_ref() && !lhs_ty.is_array_or_ref() { error_unsupported!("Non-slice LHS type '{:?}' not supported yet", lhs_ty); } - let mutability = if let ty::TyKind::Ref(_, _, mutability) = lhs_ty.kind() { mutability } else { unreachable!() }; + let mutability = if let ty::TyKind::Ref(_, _, mutability) = lhs_ty.kind() { + mutability + } else { + unreachable!() + }; let perm_amount = match mutability { Mutability::Mut => vir::PermAmount::Write, Mutability::Not => vir::PermAmount::Read, @@ -2875,23 +3013,32 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { stmts.push(vir_stmt!{ inhale [vir::Expr::pred_permission(encoded_lhs.clone(), perm_amount).unwrap()] }); let lhs_slice_ty = lhs_ty.peel_refs(); - let lhs_slice_expr = self.encoder.encode_value_expr(encoded_lhs.clone(), lhs_ty)?; + let lhs_slice_expr = self + .encoder + .encode_value_expr(encoded_lhs.clone(), lhs_ty)?; let base_seq = self.mir_encoder.encode_operand_place(&args[0])?.unwrap(); let base_seq_ty = self.mir_encoder.get_operand_ty(&args[0]); if !base_seq_ty.is_slice_or_ref() && !base_seq_ty.is_array_or_ref() { - error_unsupported!("Slicing is only supported for arrays/slices currently, not '{:?}'", base_seq_ty); + error_unsupported!( + "Slicing is only supported for arrays/slices currently, not '{:?}'", + base_seq_ty + ); } // base_seq is expected to be ref$Array$.. or ref$Slice$.., but lookup_pure wants the // contained Array$../Slice$.. let base_seq_expr = self.encoder.encode_value_expr(base_seq, base_seq_ty)?; - let enc_sequence_types = self.encoder.encode_sequence_types(base_seq_ty.peel_refs())?; + let enc_sequence_types = self + .encoder + .encode_sequence_types(base_seq_ty.peel_refs())?; - let j = vir_local!{ j: Int }; - let elem_snap_ty = self.encoder.encode_snapshot_type(enc_sequence_types.elem_ty_rs)?; + let j = vir_local! { j: Int }; + let elem_snap_ty = self + .encoder + .encode_snapshot_type(enc_sequence_types.elem_ty_rs)?; let rhs_lookup_j = enc_sequence_types.encode_lookup_pure_call( self.encoder, base_seq_expr.clone(), @@ -2902,7 +3049,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let encoded_idx = self.mir_encoder.encode_operand_place(&args[1])?.unwrap(); trace!("idx: {:?}", encoded_idx); let idx_ty = self.mir_encoder.get_operand_ty(&args[1]); - let idx_ident = self.encoder.env().name.get_absolute_item_name(idx_ty.ty_adt_def().unwrap().did()); + let idx_ident = self + .encoder + .env() + .name + .get_absolute_item_name(idx_ty.ty_adt_def().unwrap().did()); trace!("ident: {}", idx_ident); self.slice_created_at.insert(location, encoded_lhs); @@ -2912,16 +3063,32 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // TODO: there's fields like _5.f$start.val_int on `encoded_idx`, it just feels hacky to // manually re-do and hardcode them here when we probably just encoded the type // and the construction of the fields. - let usize_ty = self.encoder.env().tcx().mk_ty(ty::TyKind::Uint(ty::UintTy::Usize)); + let usize_ty = self + .encoder + .env() + .tcx() + .mk_ty(ty::TyKind::Uint(ty::UintTy::Usize)); let start = match &*idx_ident { - "std::ops::Range" | "core::ops::Range" | - "std::ops::RangeFrom" | "core::ops::RangeFrom" => { - let start_expr = self.encoder.encode_struct_field_value(encoded_idx.clone(), "start", usize_ty)?; + "std::ops::Range" + | "core::ops::Range" + | "std::ops::RangeFrom" + | "core::ops::RangeFrom" => { + let start_expr = self.encoder.encode_struct_field_value( + encoded_idx.clone(), + "start", + usize_ty, + )?; if self.check_panics { // Check indexing in bounds - stmts.push(vir::Stmt::Assert( vir::Assert { - expr: vir_expr!{ [start_expr] >= [vir::Expr::from(0usize)] }, - position: self.register_error(error_span, ErrorCtxt::SliceRangeBoundsCheckAssert("the range start value may be smaller than 0 when slicing".to_string())), + stmts.push(vir::Stmt::Assert(vir::Assert { + expr: vir_expr! { [start_expr] >= [vir::Expr::from(0usize)] }, + position: self.register_error( + error_span, + ErrorCtxt::SliceRangeBoundsCheckAssert( + "the range start value may be smaller than 0 when slicing" + .to_string(), + ), + ), })); } start_expr @@ -2929,71 +3096,102 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // RangeInclusive is wierdly differnet to all of the other Range*s in that the struct fields are private // and it is created with a new() fn and start/end are accessed with getter fns // See https://github.com/rust-lang/rust/issues/67371 for why this is the case... - "std::ops::RangeInclusive" | "core::ops::RangeInclusive" => return Err( - EncodingError::unsupported("slicing with RangeInclusive (e.g. [x..=y]) currently not supported".to_string()) - ), - "std::ops::RangeTo" | "core::ops::RangeTo" | - "std::ops::RangeFull" | "core::ops::RangeFull" | - "std::ops::RangeToInclusive" | "core::ops::RangeToInclusive" => vir::Expr::from(0usize), - _ => unreachable!("{}", idx_ident) + "std::ops::RangeInclusive" | "core::ops::RangeInclusive" => { + return Err(EncodingError::unsupported( + "slicing with RangeInclusive (e.g. [x..=y]) currently not supported" + .to_string(), + )) + } + "std::ops::RangeTo" + | "core::ops::RangeTo" + | "std::ops::RangeFull" + | "core::ops::RangeFull" + | "std::ops::RangeToInclusive" + | "core::ops::RangeToInclusive" => vir::Expr::from(0usize), + _ => unreachable!("{}", idx_ident), }; let end = match &*idx_ident { - "std::ops::Range" | "core::ops::Range" | - "std::ops::RangeTo" | "core::ops::RangeTo" => { - let end_expr = self.encoder.encode_struct_field_value(encoded_idx, "end", usize_ty)?; + "std::ops::Range" | "core::ops::Range" | "std::ops::RangeTo" | "core::ops::RangeTo" => { + let end_expr = + self.encoder + .encode_struct_field_value(encoded_idx, "end", usize_ty)?; if self.check_panics { // Check indexing in bounds - stmts.push(vir::Stmt::Assert( vir::Assert { - expr: vir_expr!{ [end_expr] <= [original_len] }, - position: self.register_error(error_span, ErrorCtxt::SliceRangeBoundsCheckAssert("the range end value may be out of bounds when slicing".to_string())), + stmts.push(vir::Stmt::Assert(vir::Assert { + expr: vir_expr! { [end_expr] <= [original_len] }, + position: self.register_error( + error_span, + ErrorCtxt::SliceRangeBoundsCheckAssert( + "the range end value may be out of bounds when slicing".to_string(), + ), + ), })); } end_expr } - "std::ops::RangeInclusive" | "core::ops::RangeInclusive" => return Err( - EncodingError::unsupported("slicing with RangeInclusive (e.g. [x..=y]) currently not supported".to_string()) - ), + "std::ops::RangeInclusive" | "core::ops::RangeInclusive" => { + return Err(EncodingError::unsupported( + "slicing with RangeInclusive (e.g. [x..=y]) currently not supported" + .to_string(), + )) + } "std::ops::RangeToInclusive" | "core::ops::RangeToInclusive" => { - let end_expr = self.encoder.encode_struct_field_value(encoded_idx, "end", usize_ty)?; - let end_expr = vir_expr!{ [end_expr] + [vir::Expr::from(1usize)] }; + let end_expr = + self.encoder + .encode_struct_field_value(encoded_idx, "end", usize_ty)?; + let end_expr = vir_expr! { [end_expr] + [vir::Expr::from(1usize)] }; if self.check_panics { // Check indexing in bounds - stmts.push(vir::Stmt::Assert( vir::Assert { - expr: vir_expr!{ [end_expr] <= [original_len] }, - position: self.register_error(error_span, ErrorCtxt::SliceRangeBoundsCheckAssert("the range end value may be out of bounds when slicing".to_string())), + stmts.push(vir::Stmt::Assert(vir::Assert { + expr: vir_expr! { [end_expr] <= [original_len] }, + position: self.register_error( + error_span, + ErrorCtxt::SliceRangeBoundsCheckAssert( + "the range end value may be out of bounds when slicing".to_string(), + ), + ), })); } end_expr } - "std::ops::RangeFrom" | "core::ops::RangeFrom" | - "std::ops::RangeFull" | "core::ops::RangeFull" => original_len, - _ => unreachable!("{}", idx_ident) + "std::ops::RangeFrom" + | "core::ops::RangeFrom" + | "std::ops::RangeFull" + | "core::ops::RangeFull" => original_len, + _ => unreachable!("{}", idx_ident), }; trace!("start: {}, end: {}", start, end); let slice_types_lhs = self.encoder.encode_sequence_types(lhs_slice_ty)?; - let elem_snap_ty = self.encoder.encode_snapshot_type(slice_types_lhs.elem_ty_rs)?; + let elem_snap_ty = self + .encoder + .encode_snapshot_type(slice_types_lhs.elem_ty_rs)?; // length - let length = vir_expr!{ [end] - [start] }; + let length = vir_expr! { [end] - [start] }; if self.check_panics { // start must be leq than end if idx_ident != "std::ops::RangeFull" && idx_ident != "core::ops::RangeFull" { - stmts.push(vir::Stmt::Assert( vir::Assert { - expr: vir_expr!{ [start] <= [end] }, - position: self.register_error(error_span, ErrorCtxt::SliceRangeBoundsCheckAssert("the range end may be smaller than the start when slicing".to_string())), + stmts.push(vir::Stmt::Assert(vir::Assert { + expr: vir_expr! { [start] <= [end] }, + position: self.register_error( + error_span, + ErrorCtxt::SliceRangeBoundsCheckAssert( + "the range end may be smaller than the start when slicing".to_string(), + ), + ), })); } } let slice_len_call = slice_types_lhs.len(self.encoder, lhs_slice_expr.clone()); - stmts.push(vir_stmt!{ + stmts.push(vir_stmt! { inhale [vir_expr!{ [slice_len_call] == [length] }] }); // lookup_pure: contents - let i = vir_local!{ i: Int }; + let i = vir_local! { i: Int }; let i_var: vir::Expr = i.clone().into(); let j_var: vir::Expr = j.clone().into(); @@ -3009,8 +3207,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // NOTE: the lhs_ and rhs_ here refer to the moment the slice is created, so lhs_lookup is // lookup on the slice currently being created, and rhs_lookup is on the array or slice // being sliced - let lookup_eq = vir_expr!{ [lhs_lookup_i] == [rhs_lookup_j] }; - let indices = vir_expr!{ + let lookup_eq = vir_expr! { [lhs_lookup_i] == [rhs_lookup_j] }; + let indices = vir_expr! { [vir_expr!{ [vir::Expr::from(0usize)] <= [i_var] }] && ( [vir_expr!{ [i_var] < [slice_len_call] }] && @@ -3025,7 +3223,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { }; // forall i: Int, j: Int :: { lhs_lookup(i), rhs_lookup(j) } 0 <= i && i < slice$len && j == i + start && start <= j && j < end ==> lhs_lookup(i) == rhs_lookup(j) - stmts.push(vir_stmt!{ + stmts.push(vir_stmt! { inhale [ vir::Expr::forall( vec![i, j], @@ -3035,7 +3233,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ] }); - self.encode_transfer_args_permissions(location, args, &mut stmts, &label, false)?; + self.encode_transfer_args_permissions(location, args, &mut stmts, &label, false)?; // Store a label for permissions got back from the call debug!( "Pure function call location {:?} has label {}", @@ -3063,40 +3261,44 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { target: Option, bin_op: vir::BinaryOpKind, substs: ty::subst::SubstsRef<'tcx>, + scope_ids: &[isize], ) -> SpannedEncodingResult> { let arg_ty = self.mir_encoder.get_operand_ty(&args[0]); - if self.encoder.supports_snapshot_equality(arg_ty).with_span(call_site_span)? { - let lhs = self.mir_encoder.encode_operand_expr(&args[0]) + if self + .encoder + .supports_snapshot_equality(arg_ty) + .with_span(call_site_span)? + { + let lhs = self + .mir_encoder + .encode_operand_expr(&args[0]) .with_span(call_site_span)?; - let rhs = self.mir_encoder.encode_operand_expr(&args[1]) + let rhs = self + .mir_encoder + .encode_operand_expr(&args[1]) .with_span(call_site_span)?; let expr = match bin_op { - vir::BinaryOpKind::EqCmp => vir::Expr::eq_cmp( - vir::Expr::snap_app(lhs), - vir::Expr::snap_app(rhs), - ), - vir::BinaryOpKind::NeCmp => vir::Expr::ne_cmp( - vir::Expr::snap_app(lhs), - vir::Expr::snap_app(rhs), - ), - _ => unreachable!() + vir::BinaryOpKind::EqCmp => { + vir::Expr::eq_cmp(vir::Expr::snap_app(lhs), vir::Expr::snap_app(rhs)) + } + vir::BinaryOpKind::NeCmp => { + vir::Expr::ne_cmp(vir::Expr::snap_app(lhs), vir::Expr::snap_app(rhs)) + } + _ => unreachable!(), }; - let (target_value, mut stmts) = self.encode_pure_function_call_lhs_value(destination, target, location) + let (target_value, mut stmts) = self + .encode_pure_function_call_lhs_value(destination, target, location) .with_span(call_site_span)?; let inhaled_expr = vir::Expr::eq_cmp(target_value, expr); - let (call_stmts, label) = self.encode_pure_function_call_site( - location, - destination, - target, - inhaled_expr, - )?; + let (call_stmts, label) = + self.encode_pure_function_call_site(location, destination, target, inhaled_expr)?; stmts.extend(call_stmts); - self.encode_transfer_args_permissions(location, args, &mut stmts, &label, false)?; + self.encode_transfer_args_permissions(location, args, &mut stmts, &label, false)?; Ok(stmts) } else { @@ -3110,6 +3312,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { target, called_def_id, substs, + scope_ids, ) } } @@ -3161,13 +3364,17 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { target: Option, called_def_id: ProcedureDefId, mut substs: ty::subst::SubstsRef<'tcx>, + scope_ids: &[isize], ) -> SpannedEncodingResult> { let full_func_proc_name = &self .encoder .env() .name .get_absolute_item_name(called_def_id); - debug!("Encoding non-pure function call '{}' with args {:?} and substs {:?}", full_func_proc_name, mir_args, substs); + debug!( + "Encoding non-pure function call '{}' with args {:?} and substs {:?}", + full_func_proc_name, mir_args, substs + ); // Spans for fake exprs that cannot be encoded in viper let mut fake_expr_spans: FxHashMap = FxHashMap::default(); @@ -3179,8 +3386,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // - the VIR Local that will hold hold the argument before the call // - the type of the argument // - if not constant, the VIR expression for the argument - let mut operands: Vec<(&mir::Operand<'tcx>, Local, ty::Ty<'tcx>, Option)> = vec![]; - let mut encoded_operands = mir_args.iter() + let mut operands: Vec<(&mir::Operand<'tcx>, Local, ty::Ty<'tcx>, Option)> = + vec![]; + let mut encoded_operands = mir_args + .iter() .map(|arg| self.mir_encoder.encode_operand_place(arg)) .collect::>, _>>() .with_span(call_site_span)?; @@ -3192,12 +3401,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let cl_ty = self.mir_encoder.get_operand_ty(&mir_args[0]); operands.push(( &mir_args[0], - mir_args[0].place() + mir_args[0] + .place() .and_then(|place| place.as_local()) - .map_or_else( - || self.locals.get_fresh(cl_ty), - |local| local.into() - ), + .map_or_else(|| self.locals.get_fresh(cl_ty), |local| local.into()), cl_ty, encoded_operands[0].take(), )); @@ -3224,7 +3431,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // TODO: weird fix for closure call substitutions, we need to // prepend the identity substs of the containing method ... - substs = self.encoder.env().tcx().mk_substs(self.substs.iter().chain(substs)); + substs = self + .encoder + .env() + .tcx() + .mk_substs(self.substs.iter().chain(substs)); } else { for (arg, encoded_operand) in mir_args.iter().zip(encoded_operands.iter_mut()) { let arg_ty = self.mir_encoder.get_operand_ty(arg); @@ -3232,10 +3443,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { arg, arg.place() .and_then(|place| place.as_local()) - .map_or_else( - || self.locals.get_fresh(arg_ty), - |local| local.into() - ), + .map_or_else(|| self.locals.get_fresh(arg_ty), |local| local.into()), arg_ty, encoded_operand.take(), )); @@ -3271,10 +3479,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { debug!("arg: {} {}", arg_place, place); if !self.encoder.is_pure(called_def_id, Some(substs)) { type_invs.push( - self.encoder.encode_invariant_func_app( - arg_ty, - vir::Expr::snap_app(place.clone()), - ).with_span(call_site_span)?, + self.encoder + .encode_invariant_func_app( + arg_ty, + vir::Expr::snap_app(place.clone()), + ) + .with_span(call_site_span)?, ); } fake_exprs.insert(arg_place, place); @@ -3284,28 +3494,28 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // We have a constant. constant_args.push(arg_place.clone()); - let val_field = self.encoder.encode_value_field(arg_ty).with_span(call_site_span)?; + let val_field = self + .encoder + .encode_value_field(arg_ty) + .with_span(call_site_span)?; // TODO: String constants are not being encoded currently, // instead just inhale the permission to the value. if !is_str(arg_ty) { - let arg_val_expr = self.mir_encoder.encode_operand_expr(mir_arg) + let arg_val_expr = self + .mir_encoder + .encode_operand_expr(mir_arg) .with_span(call_site_span)?; debug!("arg_val_expr: {} {}", arg_place, arg_val_expr); fake_exprs.insert(arg_place.clone().field(val_field), arg_val_expr); } else { - fake_expr_spans.insert( - arg, - call_site_span - ); - stmts.push(vir::Stmt::Inhale ( - vir::Inhale { - expr: vir::Expr::acc_permission( - arg_place.clone().field(val_field), - vir::PermAmount::Read - ) - } - )); + fake_expr_spans.insert(arg, call_site_span); + stmts.push(vir::Stmt::Inhale(vir::Inhale { + expr: vir::Expr::acc_permission( + arg_place.clone().field(val_field), + vir::PermAmount::Read, + ), + })); } let in_loop = self.loop_encoder.get_loop_depth(location.block) > 0; if in_loop { @@ -3324,7 +3534,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let (target_local, encoded_target) = { if target.is_some() { - let (encoded_target, pre_stmts, ty, _) = self.encode_place(destination, ArrayAccessKind::Shared, location)?; + let (encoded_target, pre_stmts, ty, _) = + self.encode_place(destination, ArrayAccessKind::Shared, location)?; stmts.extend(pre_stmts); let target_local = if let Some(target_local) = destination.as_local() { @@ -3341,9 +3552,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // The return type is Never // This means that the function call never returns // So, we `assume false` after the function call - stmts_after.push(vir::Stmt::Inhale( vir::Inhale { - expr: false.into() - })); + stmts_after.push(vir::Stmt::Inhale(vir::Inhale { expr: false.into() })); // Return a dummy local variable let never_ty = self.encoder.env().tcx().mk_ty(ty::TyKind::Never); (self.locals.get_fresh(never_ty), None) @@ -3371,7 +3580,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } } */ - vir::Expr::PredicateAccessPredicate( vir::PredicateAccessPredicate {ref argument, ..} ) => { + vir::Expr::PredicateAccessPredicate( + vir::PredicateAccessPredicate { ref argument, .. }, + ) => { if argument.is_local() && const_arg_vars.contains(argument) { // Skip predicate permission true.into() @@ -3389,13 +3600,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { }; let procedure_contract = { - self.encoder.get_procedure_contract_for_call( - self.proc_def_id, - called_def_id, - &arguments, - target_local, - substs, - ).with_span(call_site_span)? + self.encoder + .get_procedure_contract_for_call( + self.proc_def_id, + called_def_id, + &arguments, + target_local, + substs, + ) + .with_span(call_site_span)? }; assert_one_magic_wand(procedure_contract.borrow_infos.len()).with_span(call_site_span)?; @@ -3405,28 +3618,36 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // Havoc and inhale variables that store constants for constant_arg in &constant_args { - stmts.extend(self.encode_havoc_and_initialization(constant_arg).with_span(call_site_span)?); + stmts.extend( + self.encode_havoc_and_initialization(constant_arg) + .with_span(call_site_span)?, + ); } // Encode precondition. - let ( - pre_type_spec, - pre_mandatory_type_spec, - pre_invs_spec, - pre_func_spec, - ) = self.encode_precondition_expr(&procedure_contract, substs, fake_expr_spans)?; + let (pre_type_spec, pre_mandatory_type_spec, pre_invs_spec, pre_func_spec) = + self.encode_precondition_expr(&procedure_contract, substs, fake_expr_spans)?; + + let pre_func_spec = if resources::contains_resource_access_predicate(&pre_func_spec) + .with_span(call_site_span)? + { + resources::change_scope_id(pre_func_spec, scope_ids) + } else { + pre_func_spec + }; + let pos = self.register_error(call_site_span, ErrorCtxt::ExhaleMethodPrecondition); - stmts.push(vir::Stmt::Exhale( vir::Exhale { + stmts.push(vir::Stmt::Exhale(vir::Exhale { expr: replace_fake_exprs(pre_func_spec), position: pos, })); - stmts.push(vir::Stmt::Assert( vir::Assert { + stmts.push(vir::Stmt::Assert(vir::Assert { expr: replace_fake_exprs(pre_invs_spec), position: pos, })); let pre_perm_spec = replace_fake_exprs(pre_type_spec); assert!(!pos.is_default()); - stmts.push(vir::Stmt::Exhale( vir::Exhale { + stmts.push(vir::Stmt::Exhale(vir::Exhale { expr: pre_perm_spec.remove_read_permissions(), position: pos, })); @@ -3444,10 +3665,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let from_place = perm.get_place().unwrap().clone(); let to_place = from_place.clone().old(pre_label.clone()); let old_perm = perm.replace_place(&from_place, &to_place); - stmts.push(vir::Stmt::TransferPerm( vir::TransferPerm { + stmts.push(vir::Stmt::TransferPerm(vir::TransferPerm { left: from_place, right: to_place, - unchecked: true + unchecked: true, })); pre_mandatory_perms_old.push(old_perm); } @@ -3498,31 +3719,39 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .collect(); let post_perm_spec = replace_fake_exprs(post_type_spec); - stmts.push(vir::Stmt::Inhale( vir::Inhale { + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: post_perm_spec.remove_read_permissions(), })); if let Some(access) = return_type_spec { - stmts.push(vir::Stmt::Inhale( vir::Inhale { + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: replace_fake_exprs(access), })); } for (from_place, to_place) in read_transfer { - stmts.push(vir::Stmt::TransferPerm( vir::TransferPerm { + stmts.push(vir::Stmt::TransferPerm(vir::TransferPerm { left: replace_fake_exprs(from_place), right: replace_fake_exprs(to_place), unchecked: true, })); } - stmts.push(vir::Stmt::Inhale( vir::Inhale { + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: replace_fake_exprs(post_invs_spec), })); - stmts.push(vir::Stmt::Inhale( vir::Inhale { + + let post_func_spec = if resources::contains_resource_access_predicate(&post_func_spec) + .with_span(call_site_span)? + { + resources::change_scope_id(post_func_spec, scope_ids) + } else { + post_func_spec + }; + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: replace_fake_exprs(post_func_spec), })); // Exhale the permissions that were moved into magic wands. assert!(!pos.is_default()); - stmts.push(vir::Stmt::Exhale( vir::Exhale { + stmts.push(vir::Stmt::Exhale(vir::Exhale { expr: pre_mandatory_perm_spec, position: pos, })); @@ -3549,14 +3778,18 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { called_def_id: ProcedureDefId, call_substs: SubstsRef<'tcx>, ) -> SpannedEncodingResult> { - let (function_name, return_type) = self.encoder.encode_pure_function_use(called_def_id, self.proc_def_id, call_substs) + let (function_name, return_type) = self + .encoder + .encode_pure_function_use(called_def_id, self.proc_def_id, call_substs) .with_span(call_site_span)?; debug!("Encoding pure function call '{}'", function_name); assert!(target.is_some()); let mut arg_exprs = vec![]; for operand in args.iter() { - let arg_expr = self.mir_encoder.encode_operand_expr(operand) + let arg_expr = self + .mir_encoder + .encode_operand_expr(operand) .with_span(call_site_span)?; arg_exprs.push(arg_expr); } @@ -3593,7 +3826,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .iter() .enumerate() .map(|(i, arg)| { - self.mir_encoder.encode_operand_expr_type(arg) + self.mir_encoder + .encode_operand_expr_type(arg) .map(|ty| vir::LocalVar::new(format!("x{i}"), ty)) }) .collect::>() @@ -3601,7 +3835,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let pos = self.register_error(call_site_span, ErrorCtxt::PureFunctionCall); - let type_arguments = self.encoder.encode_generic_arguments(called_def_id, call_substs).with_span(call_site_span)?; + let type_arguments = self + .encoder + .encode_generic_arguments(called_def_id, call_substs) + .with_span(call_site_span)?; let func_call = vir::Expr::func_app( function_name, @@ -3609,32 +3846,27 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { arg_exprs, formal_args, return_type.clone(), - pos + pos, ); - let (target_value, mut stmts) = self.encode_pure_function_call_lhs_value(destination, target, location) + let (target_value, mut stmts) = self + .encode_pure_function_call_lhs_value(destination, target, location) .with_span(call_site_span)?; let inhaled_expr = if return_type.is_domain() || return_type.is_snapshot() { - let (target_place, pre_stmts) = self.encode_pure_function_call_lhs_place(destination, target, location)?; + let (target_place, pre_stmts) = + self.encode_pure_function_call_lhs_place(destination, target, location)?; stmts.extend(pre_stmts); - vir::Expr::eq_cmp( - vir::Expr::snap_app(target_place), - func_call, - ) + vir::Expr::eq_cmp(vir::Expr::snap_app(target_place), func_call) } else { vir::Expr::eq_cmp(target_value, func_call) }; - let (call_stmts, label) = self.encode_pure_function_call_site( - location, - destination, - target, - inhaled_expr - )?; + let (call_stmts, label) = + self.encode_pure_function_call_site(location, destination, target, inhaled_expr)?; stmts.extend(call_stmts); - self.encode_transfer_args_permissions(location, args, &mut stmts, &label, false)?; + self.encode_transfer_args_permissions(location, args, &mut stmts, &label, false)?; Ok(stmts) } @@ -3646,8 +3878,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) -> SpannedEncodingResult<(vir::Expr, Vec)> { let span = self.mir_encoder.get_span_of_location(location); assert!(target.is_some()); - let (encoded_place, pre_stmts, ty, _) = self.encode_place(destination, ArrayAccessKind::Shared, location)?; - let encoded_lhs_value = self.encoder.encode_value_expr(encoded_place, ty).with_span(span)?; + let (encoded_place, pre_stmts, ty, _) = + self.encode_place(destination, ArrayAccessKind::Shared, location)?; + let encoded_lhs_value = self + .encoder + .encode_value_expr(encoded_place, ty) + .with_span(span)?; Ok((encoded_lhs_value, pre_stmts)) } @@ -3658,7 +3894,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { location: mir::Location, ) -> SpannedEncodingResult<(vir::Expr, Vec)> { assert!(target.is_some()); - let (encoded, pre_stmts, _, _) = self.encode_place(destination, ArrayAccessKind::Shared, location)?; + let (encoded, pre_stmts, _, _) = + self.encode_place(destination, ArrayAccessKind::Shared, location)?; Ok((encoded, pre_stmts)) } @@ -3676,7 +3913,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { stmts.push(vir::Stmt::label(label.clone())); // Havoc the content of the lhs - let (target_place, pre_stmts) = self.encode_pure_function_call_lhs_place(destination, target, location)?; + let (target_place, pre_stmts) = + self.encode_pure_function_call_lhs_place(destination, target, location)?; stmts.extend(pre_stmts); stmts.extend(self.encode_havoc(&target_place).with_span(span)?); let type_predicate = self @@ -3684,16 +3922,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .encode_place_predicate_permission(target_place.clone(), vir::PermAmount::Write) .unwrap(); - stmts.push(vir::Stmt::Inhale( vir::Inhale { + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: type_predicate, })); // Initialize the lhs - stmts.push( - vir::Stmt::Inhale( vir::Inhale { - expr: call_result, - }) - ); + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: call_result })); // Store a label for permissions got back from the call debug!( @@ -3717,17 +3951,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let span = self.mir_encoder.get_span_of_location(location); for operand in args.iter() { let operand_ty = self.mir_encoder.get_operand_ty(operand); - let operand_place = self.mir_encoder.encode_operand_place(operand) + let operand_place = self + .mir_encoder + .encode_operand_place(operand) .with_span(span)?; match (operand_place, &operand_ty.kind()) { - ( - Some(ref place), - ty::TyKind::RawPtr(ty::TypeAndMut { - ty: inner_ty, .. - }), - ) + (Some(ref place), ty::TyKind::RawPtr(ty::TypeAndMut { ty: inner_ty, .. })) | (Some(ref place), ty::TyKind::Ref(_, inner_ty, _)) => { - let ref_field = self.encoder + let ref_field = self + .encoder .encode_dereference_field(*inner_ty) .with_span(span)?; let ref_place = place.clone().field(ref_field); @@ -3773,9 +4005,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { /// Encode permissions that are implicitly carried by the given local variable. /// `override_span` is used for local vars for fake expressions - fn encode_local_variable_permission(&self, local: Local, override_span: Option) - -> SpannedEncodingResult - { + fn encode_local_variable_permission( + &self, + local: Local, + override_span: Option, + ) -> SpannedEncodingResult { Ok(match self.locals.get_type(local).kind() { ty::TyKind::Ref(_, ty, mutability) => { // Use unfolded references. @@ -3789,13 +4023,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { "In ProcedureEncoder::encode_local_variable_permission the local \ {local:?} is fake but override_span is None", ), - self.mir.span + self.mir.span, )); } self.mir_encoder.get_local_span(local.into()) }; - let field = self.encoder.encode_dereference_field(*ty) - .with_span(span)?; + let field = self.encoder.encode_dereference_field(*ty).with_span(span)?; let place = vir::Expr::from(encoded_local).field(field); let perm_amount = match mutability { Mutability::Mut => vir::PermAmount::Write, @@ -3825,13 +4058,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { &self, contract: &ProcedureContract<'tcx>, substs: SubstsRef<'tcx>, - override_spans: FxHashMap // spans for fake locals - ) -> SpannedEncodingResult<( - vir::Expr, - Vec, - vir::Expr, - vir::Expr, - )> { + override_spans: FxHashMap, // spans for fake locals + ) -> SpannedEncodingResult<(vir::Expr, Vec, vir::Expr, vir::Expr)> { let borrow_infos = &contract.borrow_infos; let maybe_blocked_paths = if !borrow_infos.is_empty() { assert_eq!( @@ -3869,12 +4097,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { type_spec.push(access); } }; - let access = self.encode_local_variable_permission( - *local, - override_spans.get(local).copied() - )?; + let access = + self.encode_local_variable_permission(*local, override_spans.get(local).copied())?; match access { - vir::Expr::BinOp( vir::BinOp {op_kind: vir::BinaryOpKind::And, left: box access1, right: box access2, ..} ) => { + vir::Expr::BinOp(vir::BinOp { + op_kind: vir::BinaryOpKind::And, + left: box access1, + right: box access2, + .. + }) => { add(access1); add(access2); } @@ -3889,24 +4120,26 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .map(|local| self.encode_prusti_local(*local).into()) .collect(); - let func_spec: Vec = contract.functional_precondition( - self.encoder.env(), - substs, - ).iter() - .map(|(assertion, assertion_substs)| self.encoder.encode_assertion( - assertion, - None, - &encoded_args, - None, - false, - self.proc_def_id, - assertion_substs, - )) + let func_spec: Vec = contract + .functional_precondition(self.encoder.env(), substs) + .iter() + .map(|(assertion, assertion_substs)| { + self.encoder.encode_assertion( + assertion, + None, + &encoded_args, + None, + false, + self.proc_def_id, + assertion_substs, + ) + }) .collect::, _>>()?; // TODO(tymap): do this with the previous step ... let precondition_spans = MultiSpan::from_spans( - contract.functional_precondition(self.encoder.env(), substs) + contract + .functional_precondition(self.encoder.env(), substs) .iter() .map(|(ts, _)| self.encoder.env().query.get_def_span(ts)) .collect(), @@ -3919,10 +4152,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let ty = self.locals.get_type(*arg); if !ty.is_unsafe_ptr() && !self.encoder.is_pure(contract.def_id, Some(substs)) { invs_spec.push( - self.encoder.encode_invariant_func_app( - ty, - self.encode_prusti_local(*arg).into(), - ).with_span(precondition_spans.clone())? + self.encoder + .encode_invariant_func_app(ty, self.encode_prusti_local(*arg).into()) + .with_span(precondition_spans.clone())?, ); } } @@ -3942,13 +4174,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { Option, )> { // Encode arguments and return - let encoded_args = self.procedure_contract() + let encoded_args = self + .procedure_contract() .args .iter() .map(|local| self.encode_prusti_local(*local).into()) .collect::>(); let encoded_return = self - .encode_prusti_local(self.procedure_contract().returned_value).into(); + .encode_prusti_local(self.procedure_contract().returned_value) + .into(); debug!("procedure_contract: {:?}", self.procedure_contract()); @@ -3959,41 +4193,53 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { if let SpecificationItem::Refined(from, to) = &procedure_spec.pres { // See comment in `ProcedureContractGeneric::functional_precondition`. - let trait_substs = self.encoder.env().query.find_trait_method_substs( - self.proc_def_id, - self.substs, - ).unwrap().1; + let trait_substs = self + .encoder + .env() + .query + .find_trait_method_substs(self.proc_def_id, self.substs) + .unwrap() + .1; - let from_pre = from.iter() - .map(|spec| self.encoder.encode_assertion( - spec, - None, - &encoded_args, - None, - false, - self.proc_def_id, - trait_substs, - )) + let from_pre = from + .iter() + .map(|spec| { + self.encoder.encode_assertion( + spec, + None, + &encoded_args, + None, + false, + self.proc_def_id, + trait_substs, + ) + }) .collect::, _>>()? .into_iter() .conjoin(); - let to_pre = to.iter() - .map(|spec| self.encoder.encode_assertion( - spec, - None, - &encoded_args, - None, - false, - self.proc_def_id, - self.substs, - )) + let to_pre = to + .iter() + .map(|spec| { + self.encoder.encode_assertion( + spec, + None, + &encoded_args, + None, + false, + self.proc_def_id, + self.substs, + ) + }) .collect::, _>>()? .into_iter() .conjoin(); // The spans are used for error reporting - let spec_functions_span = MultiSpan::from_spans(from.iter().chain(to.iter()) - .map(|spec_def_id| self.encoder.env().query.get_def_span(spec_def_id)).collect() + let spec_functions_span = MultiSpan::from_spans( + from.iter() + .chain(to.iter()) + .map(|spec_def_id| self.encoder.env().query.get_def_span(spec_def_id)) + .collect(), ); weakening = Some(RefinementCheckExpr { @@ -4004,43 +4250,53 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { if let SpecificationItem::Refined(from, to) = &procedure_spec.posts { // See comment in `ProcedureContractGeneric::functional_precondition`. - let trait_substs = self.encoder.env().query.find_trait_method_substs( - self.proc_def_id, - self.substs, - ).unwrap().1; + let trait_substs = self + .encoder + .env() + .query + .find_trait_method_substs(self.proc_def_id, self.substs) + .unwrap() + .1; let from_post = from .iter() - .map(|spec| self.encoder.encode_assertion( - spec, - Some(pre_label), - &encoded_args, - Some(&encoded_return), - false, - self.proc_def_id, - trait_substs, - )) + .map(|spec| { + self.encoder.encode_assertion( + spec, + Some(pre_label), + &encoded_args, + Some(&encoded_return), + false, + self.proc_def_id, + trait_substs, + ) + }) .collect::, _>>()? .into_iter() .conjoin(); let to_post = to .iter() - .map(|spec| self.encoder.encode_assertion( - spec, - Some(pre_label), - &encoded_args, - Some(&encoded_return), - false, - self.proc_def_id, - self.substs, - )) + .map(|spec| { + self.encoder.encode_assertion( + spec, + Some(pre_label), + &encoded_args, + Some(&encoded_return), + false, + self.proc_def_id, + self.substs, + ) + }) .collect::, _>>()? .into_iter() .conjoin(); // The spans are used for error reporting - let spec_functions_span = MultiSpan::from_spans(from.iter().chain(to.iter()) - .map(|spec_def_id| self.encoder.env().query.get_def_span(spec_def_id)).collect() + let spec_functions_span = MultiSpan::from_spans( + from.iter() + .chain(to.iter()) + .map(|spec_def_id| self.encoder.env().query.get_def_span(spec_def_id)) + .collect(), ); let strengthening_expr = self.wrap_arguments_into_old( @@ -4077,51 +4333,47 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) -> SpannedEncodingResult<()> { self.cfg_method .add_stmt(start_cfg_block, vir::Stmt::comment("Preconditions:")); - let (type_spec, mandatory_type_spec, invs_spec, func_spec) = - self.encode_precondition_expr( + let (type_spec, mandatory_type_spec, invs_spec, func_spec) = self + .encode_precondition_expr( self.procedure_contract(), self.substs, - FxHashMap::default() + FxHashMap::default(), )?; self.cfg_method.add_stmt( start_cfg_block, - vir::Stmt::Inhale( vir::Inhale { - expr: type_spec}), + vir::Stmt::Inhale(vir::Inhale { expr: type_spec }), ); self.cfg_method.add_stmt( start_cfg_block, - vir::Stmt::Inhale( vir::Inhale { + vir::Stmt::Inhale(vir::Inhale { expr: mandatory_type_spec.into_iter().conjoin(), }), ); self.cfg_method.add_stmt( start_cfg_block, - vir::Stmt::Inhale( vir::Inhale { - expr: invs_spec, - }), + vir::Stmt::Inhale(vir::Inhale { expr: invs_spec }), ); // Weakening assertion must be put before inhaling the precondition, otherwise the weakening // soundness check becomes trivially satisfied. if let Some(weakening_spec) = weakening_spec { - let pos = self.register_error(weakening_spec.spec_functions_span, ErrorCtxt::AssertMethodPreconditionWeakening); + let pos = self.register_error( + weakening_spec.spec_functions_span, + ErrorCtxt::AssertMethodPreconditionWeakening, + ); self.cfg_method.add_stmt( start_cfg_block, - vir::Stmt::Assert( vir::Assert { + vir::Stmt::Assert(vir::Assert { expr: weakening_spec.refinement_check_expr, - position: pos + position: pos, }), ); } self.cfg_method.add_stmt( start_cfg_block, - vir::Stmt::Inhale( vir::Inhale { - expr: func_spec - }), - ); - self.cfg_method.add_stmt( - start_cfg_block, - vir::Stmt::label(PRECONDITION_LABEL), + vir::Stmt::Inhale(vir::Inhale { expr: func_spec }), ); + self.cfg_method + .add_stmt(start_cfg_block, vir::Stmt::label(PRECONDITION_LABEL)); Ok(()) } @@ -4169,9 +4421,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { Mutability::Not => vir::PermAmount::Read, Mutability::Mut => vir::PermAmount::Write, }; - let (place_expr, place_ty, _) = self.encode_generic_place( - contract.def_id, location, place - ).with_span(span)?; + let (place_expr, place_ty, _) = self + .encode_generic_place(contract.def_id, location, place) + .with_span(span)?; let vir_access = vir::Expr::pred_permission(place_expr.clone().old(label), perm_amount).unwrap(); if !self.encoder.is_pure(contract.def_id, Some(substs)) { @@ -4194,7 +4446,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .iter() .map(|(place, mutability)| encode_place_perm(*place, *mutability, pre_label)) .collect::>()?; - if let Some(typed::Pledge { reference, lhs: body_lhs, rhs: body_rhs}) = pledges.first() { + if let Some(typed::Pledge { + reference, + lhs: body_lhs, + rhs: body_rhs, + }) = pledges.first() + { debug!( "pledge reference={:?} lhs={:?} rhs={:?}", reference, body_lhs, body_rhs @@ -4238,9 +4495,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { &encoded_args, )?; let ty = self.locals.get_type(contract.returned_value); - let return_span = self.mir_encoder.get_local_span( - contract.returned_value.into() - ); + let return_span = self + .mir_encoder + .get_local_span(contract.returned_value.into()); let (encoded_deref, ..) = self .mir_encoder .encode_deref(encoded_return, ty) @@ -4256,12 +4513,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { lhs.push(assertion_lhs); rhs.push(assertion_rhs); } - let lhs = lhs - .into_iter() - .conjoin(); - let rhs = rhs - .into_iter() - .conjoin(); + let lhs = lhs.into_iter().conjoin(); + let rhs = rhs.into_iter().conjoin(); Ok(Some((lhs, rhs))) } else { Ok(None) @@ -4294,7 +4547,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } else { // If the argument is not a reference, we wrap entire path into old. assertion = assertion.fold_expr(|e| { - if let vir::Expr::FuncApp(vir::FuncApp { function_name, arguments, .. }) = &e { + if let vir::Expr::FuncApp(vir::FuncApp { + function_name, + arguments, + .. + }) = &e + { // If `assertion` is e.g. a `foo(snap(_1))` with an `_1: T` and `fn foo(x: &T)` we cannot // wrap `foo(snap(old[pre](_1)))`, but should instead wrap as `foo(old[pre](snap(_1)))` // TODO: this could all be fixed by making all arguments into fields (e.g. `_1.local`). @@ -4357,9 +4615,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { place, mutability ); // TODO: Use a better span - let (place_expr, place_ty, _) = self.encode_generic_place( - contract.def_id, location, *place - ).with_span(self.mir.span)?; + let (place_expr, place_ty, _) = self + .encode_generic_place(contract.def_id, location, *place) + .with_span(self.mir.span)?; let old_place_expr = place_expr.clone().old(pre_label); let mut add_type_spec = |perm_amount| { let permissions = @@ -4393,9 +4651,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .iter() .map(|local| self.encode_prusti_local(*local).into()) .collect(); - trace!("encode_postcondition_expr: encoded_args {:?} ({:?}) as {:?}", contract.args, - contract.args.iter().map(|a| self.locals.get_type(*a)).collect::>(), - encoded_args); + trace!( + "encode_postcondition_expr: encoded_args {:?} ({:?}) as {:?}", + contract.args, + contract + .args + .iter() + .map(|a| self.locals.get_type(*a)) + .collect::>(), + encoded_args + ); let encoded_return: vir::Expr = self.encode_prusti_local(contract.returned_value).into(); @@ -4405,13 +4670,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { self.mir.span }; let mut magic_wands = Vec::new(); - if let Some((mut lhs, mut rhs)) = self.encode_postcondition_magic_wand( - location, - contract, - pre_label, - post_label, - substs, - ).with_span(span)? { + if let Some((mut lhs, mut rhs)) = self + .encode_postcondition_magic_wand(location, contract, pre_label, post_label, substs) + .with_span(span)? + { if let Some((location, fake_exprs)) = magic_wand_store_info { let replace_fake_exprs = |mut expr: vir::Expr| -> vir::Expr { for (fake_arg, arg_expr) in fake_exprs.iter() { @@ -4421,25 +4683,23 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { }; lhs = replace_fake_exprs(lhs); rhs = replace_fake_exprs(rhs); - lhs = self.encoder.patch_snapshots(lhs) - .with_span(self.mir.span)?; - rhs = self.encoder.patch_snapshots(rhs) - .with_span(self.mir.span)?; + lhs = self.encoder.patch_snapshots(lhs).with_span(self.mir.span)?; + rhs = self.encoder.patch_snapshots(rhs).with_span(self.mir.span)?; debug!("Insert ({:?} {:?}) at {:?}", lhs, rhs, location); self.magic_wand_at_location .insert(location, (post_label.to_string(), lhs.clone(), rhs.clone())); } - magic_wands.push(vir::Expr::magic_wand(lhs, rhs, loan.map(|l| l.index().into()))); + magic_wands.push(vir::Expr::magic_wand( + lhs, + rhs, + loan.map(|l| l.index().into()), + )); } // Encode permissions for return type // TODO: Clean-up: remove unnecessary Option. - let return_perm = Some( - self.encode_local_variable_permission( - contract.returned_value, - None - )? - ); + let return_perm = + Some(self.encode_local_variable_permission(contract.returned_value, None)?); // Encode functional specification let mut func_spec = vec![]; @@ -4458,12 +4718,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let assertion_span = self.encoder.env().query.get_def_span(typed_assertion); func_spec_spans.push(assertion_span); let assertion_pos = self.mir_encoder.register_span(assertion_span); - assertion = self.wrap_arguments_into_old( - assertion, - pre_label, - contract, - &encoded_args, - )?; + assertion = + self.wrap_arguments_into_old(assertion, pre_label, contract, &encoded_args)?; func_spec.push(assertion.set_default_pos(assertion_pos)); } let postcondition_span = MultiSpan::from_spans(func_spec_spans); @@ -4472,14 +4728,18 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // Encode invariant for return value if !self.encoder.is_pure(contract.def_id, Some(substs)) { invs_spec.push( - self.encoder.encode_invariant_func_app( - self.locals.get_type(contract.returned_value), - encoded_return, - ).with_span(postcondition_span)? + self.encoder + .encode_invariant_func_app( + self.locals.get_type(contract.returned_value), + encoded_return, + ) + .with_span(postcondition_span)?, ); } - let full_func_spec = func_spec.into_iter().conjoin() + let full_func_spec = func_spec + .into_iter() + .conjoin() .set_default_pos(func_spec_pos); Ok(( @@ -4513,9 +4773,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } impl<'a> vir::ExprFolder for OldReplacer<'a> { #[allow(clippy::map_entry)] - fn fold_labelled_old(&mut self, vir::LabelledOld {label, base, position}: vir::LabelledOld) -> vir::Expr { + fn fold_labelled_old( + &mut self, + vir::LabelledOld { + label, + base, + position, + }: vir::LabelledOld, + ) -> vir::Expr { let base = self.fold_boxed(base); - let expr = vir::Expr::LabelledOld( vir::LabelledOld { + let expr = vir::Expr::LabelledOld(vir::LabelledOld { label: label.clone(), base, position, @@ -4567,14 +4834,18 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let span = self.mir.source_info(location).span; // Package magic wand(s) - if let Some((lhs, rhs)) = self.encode_postcondition_magic_wand( - None, - self.procedure_contract(), - pre_label, - post_label, - self.substs, - ).with_span(span)? { - let pos = self.register_error(self.mir.span, ErrorCtxt::PackageMagicWandForPostcondition); + if let Some((lhs, rhs)) = self + .encode_postcondition_magic_wand( + None, + self.procedure_contract(), + pre_label, + post_label, + self.substs, + ) + .with_span(span)? + { + let pos = + self.register_error(self.mir.span, ErrorCtxt::PackageMagicWandForPostcondition); let blocker = mir::RETURN_PLACE; // TODO: Check if it really is always start and not the mid point. @@ -4582,28 +4853,26 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .polonius_info() .get_point(location, facts::PointType::Start); - let opt_region = self.polonius_info() - .place_regions - .for_local(blocker); + let opt_region = self.polonius_info().place_regions.for_local(blocker); let mut package_stmts = if let Some(region) = opt_region { - let (all_loans, zombie_loans) = self - .polonius_info() - .get_all_loans_kept_alive_by(start_point, region); - self.encode_expiration_of_loans(all_loans, &zombie_loans, location, None, true)? - } else { - // This happens when encoding the following function - // ``` - // struct MyStruct<'tcx>(TyCtxt<'tcx>); - // fn foo(tcx: TyCtxt) -> MyStruct { - // MyStruct(tcx) - // } - // ``` - return Err(SpannedEncodingError::unsupported( - "the encoding of pledges does not supporte this \ + let (all_loans, zombie_loans) = self + .polonius_info() + .get_all_loans_kept_alive_by(start_point, region); + self.encode_expiration_of_loans(all_loans, &zombie_loans, location, None, true)? + } else { + // This happens when encoding the following function + // ``` + // struct MyStruct<'tcx>(TyCtxt<'tcx>); + // fn foo(tcx: TyCtxt) -> MyStruct { + // MyStruct(tcx) + // } + // ``` + return Err(SpannedEncodingError::unsupported( + "the encoding of pledges does not supporte this \ kind of reborrowing", - self.mir_encoder.get_span_of_location(location), - )); - }; + self.mir_encoder.get_span_of_location(location), + )); + }; // We need to make sure that the lhs of the magic wand is // fully folded before the label. @@ -4631,10 +4900,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let arg_span = self.mir_encoder.get_local_span(arg_index); if is_reference(arg_ty) { let encoded_arg = self.mir_encoder.encode_local(arg_index)?; - let (deref_place, ..) = - self.mir_encoder - .encode_deref(encoded_arg.into(), arg_ty) - .with_span(arg_span)?; + let (deref_place, ..) = self + .mir_encoder + .encode_deref(encoded_arg.into(), arg_ty) + .with_span(arg_span)?; let old_deref_place = deref_place.clone().old(pre_label); package_stmts.extend(self.encode_transfer_permissions( deref_place, @@ -4672,11 +4941,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { "We can have at most one magic wand in the postcondition." ); for (path, _) in borrow_infos[0].blocking_paths.clone().iter() { - let (encoded_place, _, _) = self.encode_generic_place( - self.procedure_contract().def_id, None, *path - ).with_span(span)?; + let (encoded_place, _, _) = self + .encode_generic_place(self.procedure_contract().def_id, None, *path) + .with_span(span)?; let old_place = encoded_place.clone().old(post_label.clone()); - stmts.extend(self.encode_transfer_permissions(old_place, encoded_place, location, true)); + stmts.extend(self.encode_transfer_permissions( + old_place, + encoded_place, + location, + true, + )); } } @@ -4692,29 +4966,27 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // This clone is only due to borrow checker restrictions let contract = self.procedure_contract().clone(); - self.cfg_method.add_stmt(return_cfg_block, vir::Stmt::comment("Exhale postcondition")); + self.cfg_method + .add_stmt(return_cfg_block, vir::Stmt::comment("Exhale postcondition")); let postcondition_label = self.cfg_method.get_fresh_label_name(); - self.cfg_method.add_stmt(return_cfg_block, vir::Stmt::label(postcondition_label.clone())); + self.cfg_method.add_stmt( + return_cfg_block, + vir::Stmt::label(postcondition_label.clone()), + ); - let ( - type_spec, - return_type_spec, - invs_spec, - func_spec, - magic_wands, - _, - ) = self.encode_postcondition_expr( - None, - &contract, - PRECONDITION_LABEL, - &postcondition_label, - None, - false, - None, - true, - self.substs, - )?; + let (type_spec, return_type_spec, invs_spec, func_spec, magic_wands, _) = self + .encode_postcondition_expr( + None, + &contract, + PRECONDITION_LABEL, + &postcondition_label, + None, + false, + None, + true, + self.substs, + )?; let type_inv_pos = self.register_error( self.mir.span, @@ -4775,10 +5047,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { vir::PermAmount::Write, ) .unwrap(); - for stmt in self - .encode_obtain(deref_pred, type_inv_pos) - .drain(..) - { + for stmt in self.encode_obtain(deref_pred, type_inv_pos).drain(..) { self.cfg_method.add_stmt(return_cfg_block, stmt); } @@ -4806,25 +5075,26 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { self.cfg_method.add_stmt( return_cfg_block, - vir::Stmt::Assign( vir::Assign { + vir::Stmt::Assign(vir::Assign { target: var, source: encoded_deref, - kind: vir::AssignKind::Move + kind: vir::AssignKind::Move, }), ); } } // Fold the result. - self.cfg_method.add_stmt( - return_cfg_block, - vir::Stmt::comment("Fold the result"), - ); + self.cfg_method + .add_stmt(return_cfg_block, vir::Stmt::comment("Fold the result")); let ty = self.locals.get_type(contract.returned_value); let encoded_return: vir::Expr = self.encode_prusti_local(contract.returned_value).into(); - let return_span = self.mir_encoder.get_local_span(contract.returned_value.into()); + let return_span = self + .mir_encoder + .get_local_span(contract.returned_value.into()); let encoded_return_expr = if is_reference(ty) { - let (encoded_deref, ..) = self.mir_encoder + let (encoded_deref, ..) = self + .mir_encoder .encode_deref(encoded_return, ty) .with_span(return_span)?; encoded_deref @@ -4835,7 +5105,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .mir_encoder .encode_place_predicate_permission(encoded_return_expr, vir::PermAmount::Write) .unwrap(); - let obtain_return_stmt = vir::Stmt::Obtain( vir::Obtain { + let obtain_return_stmt = vir::Stmt::Obtain(vir::Obtain { expr: return_pred, position: type_inv_pos, }); @@ -4848,12 +5118,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { vir::Stmt::comment("Assert possible strengthening"), ); if let Some(strengthening_spec) = strengthening_spec { - let patched_strengthening_spec = - self.replace_old_places_with_ghost_vars(None, strengthening_spec.refinement_check_expr); - let pos = self.register_error(strengthening_spec.spec_functions_span, ErrorCtxt::AssertMethodPostconditionStrengthening); + let patched_strengthening_spec = self + .replace_old_places_with_ghost_vars(None, strengthening_spec.refinement_check_expr); + let pos = self.register_error( + strengthening_spec.spec_functions_span, + ErrorCtxt::AssertMethodPostconditionStrengthening, + ); self.cfg_method.add_stmt( return_cfg_block, - vir::Stmt::Assert( vir::Assert { + vir::Stmt::Assert(vir::Assert { expr: patched_strengthening_spec, position: pos, }), @@ -4869,7 +5142,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let patched_func_spec = self.replace_old_places_with_ghost_vars(None, func_spec); self.cfg_method.add_stmt( return_cfg_block, - vir::Stmt::Assert( vir::Assert { + vir::Stmt::Assert(vir::Assert { expr: patched_func_spec, position: func_pos, }), @@ -4883,7 +5156,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let patched_invs_spec = self.replace_old_places_with_ghost_vars(None, invs_spec); self.cfg_method.add_stmt( return_cfg_block, - vir::Stmt::Assert( vir::Assert { + vir::Stmt::Assert(vir::Assert { expr: patched_invs_spec, position: type_inv_pos, }), @@ -4899,7 +5172,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { debug_assert!(!perm_pos.is_default()); self.cfg_method.add_stmt( return_cfg_block, - vir::Stmt::Exhale( vir::Exhale { + vir::Stmt::Exhale(vir::Exhale { expr: patched_type_spec, position: perm_pos, }), @@ -4912,7 +5185,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { if let Some(access) = return_type_spec { self.cfg_method.add_stmt( return_cfg_block, - vir::Stmt::Exhale( vir::Exhale { + vir::Stmt::Exhale(vir::Exhale { expr: access, position: perm_pos, }), @@ -4926,7 +5199,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { for magic_wand in magic_wands { self.cfg_method.add_stmt( return_cfg_block, - vir::Stmt::Exhale( vir::Exhale { + vir::Stmt::Exhale(vir::Exhale { expr: magic_wand, position: perm_pos, }), @@ -4972,7 +5245,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { place: &vir::Expr, ) -> vir::Expr { let tmp_var = self.get_pure_var_for_preserving_value(loop_head, place); - vir::Expr::BinOp ( vir::BinOp { + vir::Expr::BinOp(vir::BinOp { op_kind: vir::BinaryOpKind::EqCmp, left: box tmp_var.into(), right: box place.clone(), @@ -4995,7 +5268,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let snap_array = vir::Expr::snap_app(array_base); let old_snap_array = vir::Expr::old(snap_array.clone(), old_label); - vir_expr!{ [snap_array] == [old_snap_array] } + vir_expr! { [snap_array] == [old_snap_array] } } /// Arguments: @@ -5037,17 +5310,21 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let enclosing_permission_forest = if loops.len() > 1 { let next_to_last = loops.len() - 2; let enclosing_loop_head = loops[next_to_last]; - Some(self.loop_encoder.compute_loop_invariant( - enclosing_loop_head, - self.cached_loop_invariant_block[&enclosing_loop_head], - ).map_err(|err| match err { - LoopAnalysisError::UnsupportedPlaceContext(place_ctxt, loc) => { - SpannedEncodingError::internal( - format!("loop uses the unexpected PlaceContext '{place_ctxt:?}'"), - self.mir_encoder.get_span_of_location(loc), + Some( + self.loop_encoder + .compute_loop_invariant( + enclosing_loop_head, + self.cached_loop_invariant_block[&enclosing_loop_head], ) - } - })?) + .map_err(|err| match err { + LoopAnalysisError::UnsupportedPlaceContext(place_ctxt, loc) => { + SpannedEncodingError::internal( + format!("loop uses the unexpected PlaceContext '{place_ctxt:?}'"), + self.mir_encoder.get_span_of_location(loc), + ) + } + })?, + ) } else { None }; @@ -5073,16 +5350,21 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ExprOrArrayBase::ArrayBase(b) | ExprOrArrayBase::SliceBase(b) => (b, true), }; - debug!("kind={:?} mir_place={:?} encoded_place={:?} ty={:?}", kind, mir_place, encoded_place, ty); + debug!( + "kind={:?} mir_place={:?} encoded_place={:?} ty={:?}", + kind, mir_place, encoded_place, ty + ); match kind { // Gives read permission to this node. It must not be a leaf node. PermissionKind::ReadNode => { if is_array_access { // if it's already there, it's at least read -> nothing to do here - array_pred_perms.entry(encoded_place) + array_pred_perms + .entry(encoded_place) .or_insert(vir::PermAmount::Read); } else { - let perm = vir::Expr::acc_permission(encoded_place, vir::PermAmount::Read); + let perm = + vir::Expr::acc_permission(encoded_place, vir::PermAmount::Read); permissions.push(perm); } } @@ -5092,7 +5374,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { if is_array_access { array_pred_perms.insert(encoded_place, vir::PermAmount::Write); } else { - let perm = vir::Expr::acc_permission(encoded_place, vir::PermAmount::Write); + let perm = + vir::Expr::acc_permission(encoded_place, vir::PermAmount::Write); permissions.push(perm); } } @@ -5110,7 +5393,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .loop_encoder .is_definitely_initialised(mir_place, loop_head); debug!(" perm_amount={} def_init={}", perm_amount, def_init); - if let Some(base) = utils::try_pop_deref(self.encoder.env().tcx(), mir_place) + if let Some(base) = + utils::try_pop_deref(self.encoder.env().tcx(), mir_place) { // will panic if attempting to encode unsupported type let ref_ty = self.mir_encoder.encode_place(base).unwrap().1; @@ -5151,8 +5435,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { &field_place, )); } - if def_init - && !(mutbl == &Mutability::Not && drop_read_references) + if def_init && !(mutbl == &Mutability::Not && drop_read_references) { permissions.push( vir::Expr::pred_permission(field_place, perm_amount) @@ -5162,12 +5445,18 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } _ => { if is_array_access { - array_pred_perms.entry(encoded_place) - .and_modify(|e| if perm_amount == vir::PermAmount::Write { *e = vir::PermAmount::Write; }) + array_pred_perms + .entry(encoded_place) + .and_modify(|e| { + if perm_amount == vir::PermAmount::Write { + *e = vir::PermAmount::Write; + } + }) .or_insert(perm_amount); } else { permissions.push( - vir::Expr::pred_permission(encoded_place, perm_amount).unwrap(), + vir::Expr::pred_permission(encoded_place, perm_amount) + .unwrap(), ); } @@ -5178,23 +5467,30 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // that we will lose information about the children of that // place after the loop and we need to preserve it via local // variables. - let encoded_child = self.mir_encoder.encode_place(child_place)?.0; + let encoded_child = + self.mir_encoder.encode_place(child_place)?.0; match encoded_child.into_array_base() { ExprOrArrayBase::Expr(e) => { - equalities.push(self.construct_value_preserving_equality( - loop_head, - &e, - )); + equalities.push( + self.construct_value_preserving_equality( + loop_head, &e, + ), + ); } ExprOrArrayBase::ArrayBase(b) => { - let eq = self.construct_value_preserving_array_equality(loop_head, b); + let eq = self + .construct_value_preserving_array_equality( + loop_head, b, + ); // arrays can be mentioned multiple times, so we // need to check here if !equalities.contains(&eq) { equalities.push(eq); } } - ExprOrArrayBase::SliceBase(_) => unimplemented!("slices in loops not yet implemented"), + ExprOrArrayBase::SliceBase(_) => unimplemented!( + "slices in loops not yet implemented" + ), } } } @@ -5217,21 +5513,24 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { trace!("array_pred_perms: {:?}", array_pred_perms); for (place, perm) in array_pred_perms.into_iter() { permissions.push( - vir::Expr::pred_permission(place, perm) - .expect("invalid place in array_pred_perms") + vir::Expr::pred_permission(place, perm).expect("invalid place in array_pred_perms"), ); } // encode type invariants let mut invs_spec = Vec::new(); for permission in &permissions { - if let vir::Expr::PredicateAccessPredicate( vir::PredicateAccessPredicate {predicate_type, argument, ..}) = permission { + if let vir::Expr::PredicateAccessPredicate(vir::PredicateAccessPredicate { + predicate_type, + argument, + .. + }) = permission + { let ty = self.encoder.decode_type_predicate_type(predicate_type)?; if !self.encoder.is_pure(self.proc_def_id, Some(self.substs)) { - let inv_func_app = self.encoder.encode_invariant_func_app( - ty, - (**argument).clone(), - )?; + let inv_func_app = self + .encoder + .encode_invariant_func_app(ty, (**argument).clone())?; invs_spec.push(inv_func_app); } } @@ -5281,6 +5580,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { &self, loop_head: BasicBlockIndex, _loop_inv_block: BasicBlockIndex, + scope_ids: &[isize], ) -> SpannedEncodingResult<(Vec, MultiSpan)> { let spec_blocks = self.get_loop_spec_blocks(loop_head); trace!( @@ -5297,17 +5597,31 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { for stmt in &self.mir.basic_blocks[bbi].statements { if let mir::StatementKind::Assign(box ( _, - mir::Rvalue::Aggregate(box mir::AggregateKind::Closure(cl_def_id, cl_substs), _), - )) = stmt.kind { + mir::Rvalue::Aggregate( + box mir::AggregateKind::Closure(cl_def_id, cl_substs), + _, + ), + )) = stmt.kind + { if let Some(spec) = self.encoder.get_loop_specs(cl_def_id.to_def_id()) { - encoded_specs.push(self.encoder.encode_invariant( + let invariant_expr = self.encoder.encode_invariant( self.mir, bbi, self.proc_def_id, cl_substs, - )?); + )?; + if resources::contains_resource_access_predicate(&invariant_expr) + .with_span(self.mir.span)? + { + encoded_specs + .push(resources::change_scope_id(invariant_expr, scope_ids)); + } else { + encoded_specs.push(invariant_expr); + } let invariant = match spec { - prusti_interface::specs::typed::LoopSpecification::Invariant(inv) => inv, + prusti_interface::specs::typed::LoopSpecification::Invariant(inv) => { + inv + } _ => continue, }; encoded_spec_spans.push(self.encoder.env().tcx().def_span(invariant)); @@ -5325,6 +5639,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { loop_head: BasicBlockIndex, loop_inv_block: BasicBlockIndex, after_loop_iteration: bool, + scope_ids: &[isize], ) -> SpannedEncodingResult> { trace!( "[enter] encode_loop_invariant_exhale_stmts loop_head={:?} \ @@ -5337,10 +5652,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .insert(loop_head, FxHashMap::default()); } let (func_spec, func_spec_span) = - self.encode_loop_invariant_specs(loop_head, loop_inv_block)?; - let (permissions, equalities, invs_spec) = - self.encode_loop_invariant_permissions(loop_head, loop_inv_block, true) - .with_span(func_spec_span.clone())?; + self.encode_loop_invariant_specs(loop_head, loop_inv_block, scope_ids)?; + let (permissions, equalities, invs_spec) = self + .encode_loop_invariant_permissions(loop_head, loop_inv_block, true) + .with_span(func_spec_span.clone())?; // TODO: use different positions, and generate different error messages, for the exhale // before the loop and after the loop body @@ -5373,7 +5688,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { stmts.push(vir::Stmt::label(label)); } for (place, field) in &self.pure_var_for_preserving_value_map[&loop_head] { - stmts.push(vir::Stmt::Assign( vir::Assign { + stmts.push(vir::Stmt::Assign(vir::Assign { target: field.into(), source: place.clone(), kind: vir::AssignKind::Ghost, @@ -5382,28 +5697,28 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } assert!(!assert_pos.is_default()); let obtain_predicates = permissions.iter().map(|p| { - vir::Stmt::Obtain( vir::Obtain { + vir::Stmt::Obtain(vir::Obtain { expr: p.clone(), position: assert_pos, }) // TODO: Use a better position. }); stmts.extend(obtain_predicates); - stmts.push(vir::Stmt::Exhale( vir::Exhale { + stmts.push(vir::Stmt::Exhale(vir::Exhale { expr: func_spec.into_iter().conjoin(), position: assert_pos, })); - stmts.push(vir::Stmt::Assert( vir::Assert { + stmts.push(vir::Stmt::Assert(vir::Assert { expr: invs_spec.into_iter().conjoin(), position: exhale_pos, })); let equalities_expr = equalities.into_iter().conjoin(); - stmts.push(vir::Stmt::Assert( vir::Assert { + stmts.push(vir::Stmt::Assert(vir::Assert { expr: equalities_expr, position: exhale_pos, })); let permission_expr = permissions.into_iter().conjoin(); - stmts.push(vir::Stmt::Exhale( vir::Exhale { + stmts.push(vir::Stmt::Exhale(vir::Exhale { expr: permission_expr, position: exhale_pos, })); @@ -5430,13 +5745,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let mut stmts = vec![vir::Stmt::comment(format!( "Inhale the loop permissions invariant of block {loop_head:?}" ))]; - stmts.push(vir::Stmt::Inhale( vir::Inhale { + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: permission_expr, })); - stmts.push(vir::Stmt::Inhale( vir::Inhale { + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: equality_expr, })); - stmts.push(vir::Stmt::Inhale( vir::Inhale { + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: invs_spec.into_iter().conjoin(), })); Ok(stmts) @@ -5447,6 +5762,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { loop_head: BasicBlockIndex, loop_inv_block: BasicBlockIndex, after_loop: bool, + scope_ids: &[isize], ) -> SpannedEncodingResult<(Vec, MultiSpan)> { trace!( "[enter] encode_loop_invariant_inhale_fnspec_stmts loop_head={:?} after_loop={}", @@ -5454,12 +5770,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { after_loop ); let (func_spec, func_spec_span) = - self.encode_loop_invariant_specs(loop_head, loop_inv_block)?; + self.encode_loop_invariant_specs(loop_head, loop_inv_block, scope_ids)?; let mut stmts = vec![vir::Stmt::comment(format!( "Inhale the loop fnspec invariant of block {loop_head:?}" ))]; - stmts.push(vir::Stmt::Inhale( vir::Inhale { + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: func_spec.into_iter().conjoin(), })); Ok((stmts, func_spec_span)) @@ -5469,7 +5785,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let var_name = self.locals.get_name(local); let typ = self .encoder - .encode_type(self.locals.get_type(local)).unwrap(); // will panic if attempting to encode unsupported type + .encode_type(self.locals.get_type(local)) + .unwrap(); // will panic if attempting to encode unsupported type vir::LocalVar::new(var_name, typ) } @@ -5500,9 +5817,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) -> EncodingResult<(vir::Expr, ty::Ty<'tcx>, Option)> { let mir_encoder = if let Some(location) = location { let block = &self.mir.basic_blocks[location.block]; - assert_eq!(block.statements.len(), location.statement_index, "expected terminator location"); + assert_eq!( + block.statements.len(), + location.statement_index, + "expected terminator location" + ); match &block.terminator().kind { - mir::TerminatorKind::Call{ args, destination, .. } => { + mir::TerminatorKind::Call { + args, destination, .. + } => { let tcx = self.encoder.env().tcx(); let arg_tys = args.iter().map(|arg| arg.ty(self.mir, tcx)).collect(); let return_ty = destination.ty(self.mir, tcx).ty; @@ -5528,7 +5851,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } Place::SubstitutedPlace { substituted_root, - place + place, } => { let (place_encoding, ty, variant) = mir_encoder.encode_place(place)?; let expr = place_encoding.try_into_expr()?; @@ -5538,7 +5861,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } use vir::ExprFolder; impl ExprFolder for RootReplacer { - fn fold_local(&mut self, vir::Local {position, ..}: vir::Local) -> vir::Expr { + fn fold_local(&mut self, vir::Local { position, .. }: vir::Local) -> vir::Expr { vir::Expr::local_with_pos(self.new_root.clone(), position) } } @@ -5623,15 +5946,20 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // inhale lookup_pure(array, index) == encoded_rhs // // now we have all the contents as before, just one item updated - let (encoded_array, mut stmts) = self.postprocess_place_encoding( - base, - ArrayAccessKind::Shared, // shouldn't be nested, so doesn't matter[tm] - ).with_span(span)?; + let (encoded_array, mut stmts) = self + .postprocess_place_encoding( + base, + ArrayAccessKind::Shared, // shouldn't be nested, so doesn't matter[tm] + ) + .with_span(span)?; let label = self.cfg_method.get_fresh_label_name(); stmts.push(vir::Stmt::label(&label)); - let sequence_types = self.encoder.encode_sequence_types(array_ty).with_span(span)?; + let sequence_types = self + .encoder + .encode_sequence_types(array_ty) + .with_span(span)?; let sequence_len = sequence_types.len(self.encoder, encoded_array.clone()); let array_acc_expr = vir::Expr::predicate_access_predicate( @@ -5641,54 +5969,78 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ); // exhale and re-inhale to havoc - stmts.push(vir_stmt!{ exhale [array_acc_expr] }); - stmts.push(vir_stmt!{ inhale [array_acc_expr] }); + stmts.push(vir_stmt! { exhale [array_acc_expr] }); + stmts.push(vir_stmt! { inhale [array_acc_expr] }); - let old = |e| { vir::Expr::labelled_old(&label, e) }; + let old = |e| vir::Expr::labelled_old(&label, e); // For sequences with fixed len (e.g. arrays) this will be Some if sequence_types.sequence_len.is_none() { // inhale that len unchanged - let len_eq = vir_expr!{ [ sequence_len ] == [ old(sequence_len.clone()) ] }; - stmts.push(vir_stmt!{ inhale [len_eq] }); + let len_eq = vir_expr! { [ sequence_len ] == [ old(sequence_len.clone()) ] }; + stmts.push(vir_stmt! { inhale [len_eq] }); } - let idx_val_int = self.encoder.patch_snapshots(vir::Expr::snap_app(index)).with_span(span)?; + let idx_val_int = self + .encoder + .patch_snapshots(vir::Expr::snap_app(index)) + .with_span(span)?; // inhale infos about array contents back - let i_var: vir::Expr = vir_local!{ i: Int }.into(); - let zero_le_i = vir_expr!{ [vir::Expr::from(0usize)] <= [ i_var ] }; - let i_lt_len = vir_expr!{ [ i_var ] < [ sequence_len ] }; - let i_ne_idx = vir_expr!{ [ i_var ] != [ old(idx_val_int.clone()) ] }; - let idx_conditions = vir_expr!{ [zero_le_i] && ([i_lt_len] && [i_ne_idx]) }; - let lookup_ret_ty = self.encoder.encode_snapshot_type(sequence_types.elem_ty_rs).with_span(span)?; - let lookup_array_i = sequence_types.encode_lookup_pure_call(self.encoder, encoded_array.clone(), i_var.clone(), lookup_ret_ty.clone()); + let i_var: vir::Expr = vir_local! { i: Int }.into(); + let zero_le_i = vir_expr! { [vir::Expr::from(0usize)] <= [ i_var ] }; + let i_lt_len = vir_expr! { [ i_var ] < [ sequence_len ] }; + let i_ne_idx = vir_expr! { [ i_var ] != [ old(idx_val_int.clone()) ] }; + let idx_conditions = vir_expr! { [zero_le_i] && ([i_lt_len] && [i_ne_idx]) }; + let lookup_ret_ty = self + .encoder + .encode_snapshot_type(sequence_types.elem_ty_rs) + .with_span(span)?; + let lookup_array_i = sequence_types.encode_lookup_pure_call( + self.encoder, + encoded_array.clone(), + i_var.clone(), + lookup_ret_ty.clone(), + ); // FIXME: using `old` here is a work-around for https://github.com/viperproject/prusti-dev/issues/877 // FIXME: which is due to an issue in Silicon, see https://github.com/viperproject/silicon/issues/603 - let lookup_array_i_old = sequence_types.encode_lookup_pure_call(self.encoder, old(encoded_array.clone()), i_var, lookup_ret_ty.clone()); - let lookup_same_as_old = vir_expr!{ [lookup_array_i] == [old(lookup_array_i)] }; - let forall_body = vir_expr!{ [idx_conditions] ==> [lookup_same_as_old] }; - let all_others_unchanged = vir_expr!{ forall i: Int :: { [lookup_array_i_old] } :: [ forall_body ] }; + let lookup_array_i_old = sequence_types.encode_lookup_pure_call( + self.encoder, + old(encoded_array.clone()), + i_var, + lookup_ret_ty.clone(), + ); + let lookup_same_as_old = vir_expr! { [lookup_array_i] == [old(lookup_array_i)] }; + let forall_body = vir_expr! { [idx_conditions] ==> [lookup_same_as_old] }; + let all_others_unchanged = + vir_expr! { forall i: Int :: { [lookup_array_i_old] } :: [ forall_body ] }; - stmts.push(vir_stmt!{ inhale [ all_others_unchanged ]}); + stmts.push(vir_stmt! { inhale [ all_others_unchanged ]}); - let tmp = vir::Expr::from(self.cfg_method.add_fresh_local_var(sequence_types.elem_pred_type.clone())); + let tmp = vir::Expr::from( + self.cfg_method + .add_fresh_local_var(sequence_types.elem_pred_type.clone()), + ); stmts.extend( - self.encode_assign( - tmp.clone(), - rhs, - sequence_types.elem_ty_rs, - location, - ).with_span(span)? + self.encode_assign(tmp.clone(), rhs, sequence_types.elem_ty_rs, location) + .with_span(span)?, ); - let tmp_val_field = self.encoder.encode_value_expr(tmp, sequence_types.elem_ty_rs).with_span(span)?; + let tmp_val_field = self + .encoder + .encode_value_expr(tmp, sequence_types.elem_ty_rs) + .with_span(span)?; - let indexed_lookup_pure_call = sequence_types - .encode_lookup_pure_call(self.encoder, encoded_array, old(idx_val_int), lookup_ret_ty); - let indexed_updated = vir_expr!{ [ indexed_lookup_pure_call ] == [ vir::Expr::snap_app(tmp_val_field) ] }; + let indexed_lookup_pure_call = sequence_types.encode_lookup_pure_call( + self.encoder, + encoded_array, + old(idx_val_int), + lookup_ret_ty, + ); + let indexed_updated = + vir_expr! { [ indexed_lookup_pure_call ] == [ vir::Expr::snap_app(tmp_val_field) ] }; - stmts.push(vir_stmt!{ inhale [ indexed_updated ] }); + stmts.push(vir_stmt! { inhale [ indexed_updated ] }); Ok(stmts) } @@ -5703,12 +6055,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) -> SpannedEncodingResult> { trace!( "[enter] encode_assign_operand(lhs={}, operand={:?}, location={:?})", - lhs, operand, location + lhs, + operand, + location ); let span = self.mir_encoder.get_span_of_location(location); let stmts = match operand { mir::Operand::Move(place) => { - let (src, mut stmts, ty, _) = self.encode_place(*place, ArrayAccessKind::Shared, location)?; + let (src, mut stmts, ty, _) = + self.encode_place(*place, ArrayAccessKind::Shared, location)?; let encode_stmts = match ty.kind() { ty::TyKind::RawPtr(..) | ty::TyKind::Ref(..) => { // Reborrow. @@ -5718,26 +6073,28 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { field.clone(), location, vir::AssignKind::Move, - false + false, )?; - alloc_stmts.push(vir::Stmt::Assign( vir::Assign { + alloc_stmts.push(vir::Stmt::Assign(vir::Assign { target: lhs.clone().field(field.clone()), source: src.field(field), kind: vir::AssignKind::Move, })); alloc_stmts } - _ if config::enable_purification_optimization() && - prusti_common::vir::optimizations::purification::is_purifiable_type(lhs.get_type()) => { + _ if config::enable_purification_optimization() + && prusti_common::vir::optimizations::purification::is_purifiable_type( + lhs.get_type(), + ) => + { self.encode_copy2(src, lhs.clone(), ty, location)? } _ => { // Just move. - let move_assign = - vir::Stmt::Assign( vir::Assign { - target: lhs.clone(), - source: src, - kind: vir::AssignKind::Move + let move_assign = vir::Stmt::Assign(vir::Assign { + target: lhs.clone(), + source: src, + kind: vir::AssignKind::Move, }); vec![move_assign] } @@ -5755,7 +6112,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } mir::Operand::Copy(place) => { - let (src, mut stmts, ty, _) = self.encode_place(*place, ArrayAccessKind::Shared, location)?; + let (src, mut stmts, ty, _) = + self.encode_place(*place, ArrayAccessKind::Shared, location)?; let encode_stmts = match ty.kind() { ty::TyKind::RawPtr(..) => { return Err(SpannedEncodingError::unsupported( @@ -5771,9 +6129,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ref_field.clone(), location, vir::AssignKind::SharedBorrow(loan.index().into()), - false + false, )?; - stmts.push(vir::Stmt::Assign( vir::Assign { + stmts.push(vir::Stmt::Assign(vir::Assign { target: lhs.clone().field(ref_field.clone()), source: src.field(ref_field), kind: vir::AssignKind::SharedBorrow(loan.index().into()), @@ -5805,17 +6163,18 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { field.clone(), location, vir::AssignKind::Copy, - true + true, )?; // TODO Encoding of string literals is not yet supported, // so do not encode an assignment if the RHS is a string if !is_str(ty) { // Initialize the constant - let const_val = self.encoder + let const_val = self + .encoder .encode_const_expr(ty, expr.literal) .with_span(span)?; // Initialize value of lhs - stmts.push(vir::Stmt::Assign( vir::Assign { + stmts.push(vir::Stmt::Assign(vir::Assign { target: lhs.clone().field(field), source: const_val, kind: vir::AssignKind::Copy, @@ -5854,13 +6213,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { right ); let span = self.mir_encoder.get_span_of_location(location); - let encoded_left = self.mir_encoder.encode_operand_expr(left) + let encoded_left = self.mir_encoder.encode_operand_expr(left).with_span(span)?; + let encoded_right = self + .mir_encoder + .encode_operand_expr(right) .with_span(span)?; - let encoded_right = self.mir_encoder.encode_operand_expr(right) + let encoded_value = self + .mir_encoder + .encode_bin_op_expr(op, encoded_left, encoded_right, ty) .with_span(span)?; - let encoded_value = - self.mir_encoder.encode_bin_op_expr(op, encoded_left, encoded_right, ty) - .with_span(span)?; self.encode_copy_value_assign(encoded_lhs, encoded_value, ty, location) } @@ -5873,12 +6234,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) -> SpannedEncodingResult> { let span = self.mir_encoder.get_span_of_location(location); let field = self.encoder.encode_value_field(ty).with_span(span)?; - self.encode_copy_value_assign2( - encoded_lhs, - encoded_rhs, - field, - location - ) + self.encode_copy_value_assign2(encoded_lhs, encoded_rhs, field, location) } /// Assignment with a(n overflow-)checked binary operation on the RHS. @@ -5904,20 +6260,19 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } else { unreachable!() }; - let encoded_left = self.mir_encoder.encode_operand_expr(left) + let encoded_left = self.mir_encoder.encode_operand_expr(left).with_span(span)?; + let encoded_right = self + .mir_encoder + .encode_operand_expr(right) .with_span(span)?; - let encoded_right = self.mir_encoder.encode_operand_expr(right) + let encoded_value = self + .mir_encoder + .encode_bin_op_expr(op, encoded_left.clone(), encoded_right.clone(), operand_ty) + .with_span(span)?; + let encoded_check = self + .mir_encoder + .encode_bin_op_check(op, encoded_left, encoded_right, operand_ty) .with_span(span)?; - let encoded_value = self.mir_encoder.encode_bin_op_expr( - op, - encoded_left.clone(), - encoded_right.clone(), - operand_ty, - ).with_span(span)?; - let encoded_check = - self.mir_encoder - .encode_bin_op_check(op, encoded_left, encoded_right, operand_ty) - .with_span(span)?; let field_types = if let ty::TyKind::Tuple(ref x) = ty.kind() { x } else { @@ -5927,19 +6282,25 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .encoder .encode_raw_ref_field("tuple_0".to_string(), field_types[0]) .with_span(span)?; - let value_field_value = self.encoder.encode_value_field(field_types[0]).with_span(span)?; + let value_field_value = self + .encoder + .encode_value_field(field_types[0]) + .with_span(span)?; let check_field = self .encoder .encode_raw_ref_field("tuple_1".to_string(), field_types[1]) .with_span(span)?; - let check_field_value = self.encoder.encode_value_field(field_types[1]).with_span(span)?; + let check_field_value = self + .encoder + .encode_value_field(field_types[1]) + .with_span(span)?; let mut stmts = if !self .init_info .is_vir_place_accessible(&encoded_lhs, location) { let mut alloc_stmts = self.encode_havoc(&encoded_lhs).with_span(span)?; let mut inhale_acc = |place| { - alloc_stmts.push(vir::Stmt::Inhale( vir::Inhale { + alloc_stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: vir::Expr::acc_permission(place, vir::PermAmount::Write), })); }; @@ -5962,7 +6323,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { Vec::with_capacity(2) }; // Initialize lhs.field - stmts.push(vir::Stmt::Assign( vir::Assign { + stmts.push(vir::Stmt::Assign(vir::Assign { target: encoded_lhs .clone() .field(value_field) @@ -5970,7 +6331,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { source: encoded_value, kind: vir::AssignKind::Copy, })); - stmts.push(vir::Stmt::Assign( vir::Assign { + stmts.push(vir::Stmt::Assign(vir::Assign { target: encoded_lhs.field(check_field).field(check_field_value), source: encoded_check, kind: vir::AssignKind::Copy, @@ -5994,10 +6355,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { op, operand ); - let encoded_val = self.mir_encoder.encode_operand_expr(operand) - .with_span( - self.mir_encoder.get_span_of_location(location) - )?; + let encoded_val = self + .mir_encoder + .encode_operand_expr(operand) + .with_span(self.mir_encoder.get_span_of_location(location))?; let encoded_value = self.mir_encoder.encode_unary_op_expr(op, encoded_val); // Initialize `lhs.field` self.encode_copy_value_assign(encoded_lhs, encoded_value, ty, location) @@ -6019,10 +6380,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let param_env = tcx.param_env(self.proc_def_id); let layout = match tcx.layout_of(param_env.and(op_ty)) { Ok(layout_of) => layout_of.layout, - Err(_) => return Err(SpannedEncodingError::internal( - format!("could not fetch layout of type `{op_ty}` while translating `{op:?}`"), - self.mir.source_info(location).span, - )), + Err(_) => { + return Err(SpannedEncodingError::internal( + format!("could not fetch layout of type `{op_ty}` while translating `{op:?}`"), + self.mir.source_info(location).span, + )) + } }; let bytes = match op { mir::NullOp::SizeOf => layout.size().bytes(), @@ -6030,12 +6393,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { mir::NullOp::AlignOf => layout.align().abi.bytes(), }; let bytes_vir = vir::Expr::from(bytes); - self.encode_copy_value_assign( - encoded_lhs, - bytes_vir, - ty, - location, - ) + self.encode_copy_value_assign(encoded_lhs, bytes_vir, ty, location) } fn encode_assign_box( @@ -6047,10 +6405,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) -> SpannedEncodingResult> { assert_eq!(op_ty, ty.boxed_ty()); let span = self.mir_encoder.get_span_of_location(location); - let ref_field = self.encoder.encode_dereference_field(op_ty) - .with_span( - self.mir_encoder.get_span_of_location(location) - )?; + let ref_field = self + .encoder + .encode_dereference_field(op_ty) + .with_span(self.mir_encoder.get_span_of_location(location))?; let box_content = encoded_lhs.clone().field(ref_field.clone()); let mut stmts = self.prepare_assign_target( @@ -6058,14 +6416,17 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ref_field, location, vir::AssignKind::Move, - false + false, )?; // Allocate `box_content` // TODO: This is also encoding initialization, which should be avoided because both // `Rvalue::NullaryOp(NullOp::Box)` and `Rvalue::ShallowInitBox` don't initialize the // content of the box. - stmts.extend(self.encode_havoc_and_initialization(&box_content).with_span(span)?); + stmts.extend( + self.encode_havoc_and_initialization(&box_content) + .with_span(span)?, + ); // Leave `box_content` uninitialized Ok(stmts) @@ -6086,7 +6447,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { location ); let span = self.mir_encoder.get_span_of_location(location); - let (encoded_src, mut stmts, src_ty, _) = self.encode_place(src, ArrayAccessKind::Shared, location)?; + let (encoded_src, mut stmts, src_ty, _) = + self.encode_place(src, ArrayAccessKind::Shared, location)?; let encode_stmts = match src_ty.kind() { ty::TyKind::Adt(adt_def, _) if !adt_def.is_box() => { let num_variants = adt_def.variants().len(); @@ -6103,16 +6465,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { self.proc_def_id, ); } - let encoded_rhs = self.encoder.encode_discriminant_func_app( - encoded_src, - *adt_def, - )?; - self.encode_copy_value_assign( - encoded_lhs, - encoded_rhs, - ty, - location - )? + let encoded_rhs = self + .encoder + .encode_discriminant_func_app(encoded_src, *adt_def)?; + self.encode_copy_value_assign(encoded_lhs, encoded_rhs, ty, location)? } else { vec![] } @@ -6121,12 +6477,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ty::TyKind::Int(_) | ty::TyKind::Uint(_) | ty::TyKind::Float(_) => { let value_field = self.encoder.encode_value_field(src_ty).with_span(span)?; let discr_value = encoded_src.field(value_field); - self.encode_copy_value_assign( - encoded_lhs, - discr_value, - ty, - location - )? + self.encode_copy_value_assign(encoded_lhs, discr_value, ty, location)? } ref x => { @@ -6157,26 +6508,28 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let span = self.mir_encoder.get_span_of_location(location); let loan = self.polonius_info().get_loan_at_location(location); let (vir_assign_kind, array_encode_kind) = match mir_borrow_kind { - mir::BorrowKind::Shared => - (vir::AssignKind::SharedBorrow(loan.index().into()), ArrayAccessKind::Shared), - mir::BorrowKind::Mut { .. } => - (vir::AssignKind::MutableBorrow(loan.index().into()), - ArrayAccessKind::Mutable(Some(loan.index().into()), location)), + mir::BorrowKind::Shared => ( + vir::AssignKind::SharedBorrow(loan.index().into()), + ArrayAccessKind::Shared, + ), + mir::BorrowKind::Mut { .. } => ( + vir::AssignKind::MutableBorrow(loan.index().into()), + ArrayAccessKind::Mutable(Some(loan.index().into()), location), + ), _ => return Err(Self::unsupported_borrow_kind(mir_borrow_kind).with_span(span)), }; - let (encoded_value, mut stmts, _, _) = self.encode_place(place, array_encode_kind, location)?; + let (encoded_value, mut stmts, _, _) = + self.encode_place(place, array_encode_kind, location)?; // Initialize ref_var.ref_field let field = self.encoder.encode_value_field(ty).with_span(span)?; - stmts.extend( - self.prepare_assign_target( - encoded_lhs.clone(), - field.clone(), - location, - vir_assign_kind, - false - )? - ); - stmts.push(vir::Stmt::Assign( vir::Assign { + stmts.extend(self.prepare_assign_target( + encoded_lhs.clone(), + field.clone(), + location, + vir_assign_kind, + false, + )?); + stmts.push(vir::Stmt::Assign(vir::Assign { target: encoded_lhs.field(field), source: encoded_value, kind: vir_assign_kind, @@ -6218,7 +6571,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ty: ty::Ty<'tcx>, location: mir::Location, ) -> SpannedEncodingResult> { - trace!("encode_assign_slice(lhs={:?}, operand={:?}, ty={:?})", encoded_lhs, operand, ty); + trace!( + "encode_assign_slice(lhs={:?}, operand={:?}, ty={:?})", + encoded_lhs, + operand, + ty + ); debug_assert!(ty.is_slice_ref()); let span = self.mir_encoder.get_span_of_location(location); let mut stmts = Vec::new(); @@ -6231,24 +6589,33 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } else { unreachable!("encode_assign_slice on a non-ref?!") }; - let slice_types = self.encoder.encode_sequence_types(*slice_ty).with_span(span)?; + let slice_types = self + .encoder + .encode_sequence_types(*slice_ty) + .with_span(span)?; stmts.extend(self.encode_havoc(&encoded_lhs).with_span(span)?); let val_ref_field = self.encoder.encode_value_field(ty).with_span(span)?; let slice_expr = encoded_lhs.field(val_ref_field); - stmts.push(vir_stmt!{ inhale [vir::Expr::FieldAccessPredicate( vir::FieldAccessPredicate { - base: box slice_expr.clone(), - permission: vir::PermAmount::Write, - position: vir::Position::default(), - })]}); + stmts.push( + vir_stmt! { inhale [vir::Expr::FieldAccessPredicate( vir::FieldAccessPredicate { + base: box slice_expr.clone(), + permission: vir::PermAmount::Write, + position: vir::Position::default(), + })]}, + ); - let slice_perm = vir::Expr::PredicateAccessPredicate( vir::PredicateAccessPredicate { + let slice_perm = vir::Expr::PredicateAccessPredicate(vir::PredicateAccessPredicate { predicate_type: slice_types.sequence_pred_type.clone(), argument: box slice_expr.clone(), - permission: if is_mut { vir::PermAmount::Write } else { vir::PermAmount::Read }, + permission: if is_mut { + vir::PermAmount::Write + } else { + vir::PermAmount::Read + }, position: vir::Position::default(), }); - stmts.push(vir_stmt!{ inhale [slice_perm] }); + stmts.push(vir_stmt! { inhale [slice_perm] }); let (rhs_place, rhs_ty) = if let mir::Operand::Move(place) = operand { let (rhs_place, rhs_ty, ..) = self.mir_encoder.encode_place(*place).with_span(span)?; @@ -6265,7 +6632,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let val_ref_field = self.encoder.encode_value_field(rhs_ty).with_span(span)?; let rhs_expr = rhs_place.field(val_ref_field); - let sequence_types = self.encoder.encode_sequence_types(*rhs_array_ty).with_span(span)?; + let sequence_types = self + .encoder + .encode_sequence_types(*rhs_array_ty) + .with_span(span)?; let slice_len_call = slice_types.len(self.encoder, slice_expr.clone()); @@ -6273,7 +6643,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { expr: vir_expr!{ [slice_len_call] == [sequence_types.len(self.encoder, rhs_expr.clone())] } })); - let elem_snap_ty = self.encoder.encode_snapshot_type(sequence_types.elem_ty_rs).with_span(span)?; + let elem_snap_ty = self + .encoder + .encode_snapshot_type(sequence_types.elem_ty_rs) + .with_span(span)?; let i: vir::Expr = vir_local! { i: Int }.into(); @@ -6284,20 +6657,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { elem_snap_ty.clone(), ); - let slice_lookup_call = slice_types.encode_lookup_pure_call( - self.encoder, - slice_expr, - i.clone(), - elem_snap_ty, - ); + let slice_lookup_call = + slice_types.encode_lookup_pure_call(self.encoder, slice_expr, i.clone(), elem_snap_ty); let indices = vir_expr! { ([vir::Expr::from(0usize)] <= [i]) && ([i] < [sequence_types.len(self.encoder, rhs_expr)]) }; - stmts.push(vir::Stmt::Inhale( vir::Inhale { + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: vir_expr! { - forall i: Int :: - { [array_lookup_call] } { [slice_lookup_call] } :: - ([indices] ==> ([array_lookup_call] == [slice_lookup_call])) } + forall i: Int :: + { [array_lookup_call] } { [slice_lookup_call] } :: + ([indices] ==> ([array_lookup_call] == [slice_lookup_call])) }, })); // Store a label for permissions got back from the call @@ -6323,18 +6692,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { dst_ty, ); let span = self.mir_encoder.get_span_of_location(location); - let (encoded_place, mut stmts, place_ty, ..) = self.encode_place( - place, - ArrayAccessKind::Mutable(None, location), - location - )?; + let (encoded_place, mut stmts, place_ty, ..) = + self.encode_place(place, ArrayAccessKind::Mutable(None, location), location)?; match place_ty.kind() { - ty::TyKind::Array(..) | - ty::TyKind::Slice(..) => { - let slice_types = self.encoder.encode_sequence_types(place_ty) - .with_span(span)?; + ty::TyKind::Array(..) | ty::TyKind::Slice(..) => { + let slice_types = self + .encoder + .encode_sequence_types(place_ty) + .with_span(span)?; - stmts.push(vir::Stmt::Assert( vir::Assert { + stmts.push(vir::Stmt::Assert(vir::Assert { expr: vir::Expr::predicate_access_predicate( slice_types.sequence_pred_type.clone(), encoded_place.clone(), @@ -6345,19 +6712,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let rhs = slice_types.len(self.encoder, encoded_place); - stmts.extend( - self.encode_copy_value_assign( - encoded_lhs, - rhs, - dst_ty, - location, - )? - ); - }, - other => return Err( - EncodingError::unsupported(format!("length operation on unsupported type '{other:?}'")) - .with_span(span) - ), + stmts.extend(self.encode_copy_value_assign(encoded_lhs, rhs, dst_ty, location)?); + } + other => { + return Err(EncodingError::unsupported(format!( + "length operation on unsupported type '{other:?}'" + )) + .with_span(span)) + } } Ok(stmts) @@ -6374,11 +6736,21 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let span = self.mir_encoder.get_span_of_location(location); let sequence_types = self.encoder.encode_sequence_types(ty).with_span(span)?; - let encoded_operand = self.mir_encoder.encode_operand_expr(operand) + let encoded_operand = self + .mir_encoder + .encode_operand_expr(operand) .with_span(span)?; - let len: usize = self.encoder.const_eval_intlike(mir::ConstantKind::Ty(times)).with_span(span)? - .to_u64().unwrap().try_into().unwrap(); - let lookup_ret_ty = self.encoder.encode_snapshot_type(sequence_types.elem_ty_rs) + let len: usize = self + .encoder + .const_eval_intlike(mir::ConstantKind::Ty(times)) + .with_span(span)? + .to_u64() + .unwrap() + .try_into() + .unwrap(); + let lookup_ret_ty = self + .encoder + .encode_snapshot_type(sequence_types.elem_ty_rs) .with_span(span)?; let inhaled_operand = if lookup_ret_ty.is_domain() || lookup_ret_ty.is_snapshot() { @@ -6387,20 +6759,19 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { encoded_operand }; - let mut stmts = self.encode_havoc_and_initialization(&encoded_lhs).with_span(span)?; + let mut stmts = self + .encode_havoc_and_initialization(&encoded_lhs) + .with_span(span)?; let idx: vir::Expr = vir_local! { i: Int }.into(); - let indices = vir_expr! { ([vir::Expr::from(0usize)] <= [idx]) && ([idx] < [vir::Expr::from(len)]) }; - let lookup_pure_call = sequence_types.encode_lookup_pure_call( - self.encoder, - encoded_lhs, - idx, - lookup_ret_ty, - ); - stmts.push(vir::Stmt::Inhale( vir::Inhale { + let indices = + vir_expr! { ([vir::Expr::from(0usize)] <= [idx]) && ([idx] < [vir::Expr::from(len)]) }; + let lookup_pure_call = + sequence_types.encode_lookup_pure_call(self.encoder, encoded_lhs, idx, lookup_ret_ty); + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: vir_expr! { - forall i: Int :: - { [lookup_pure_call] } :: - ([indices] ==> ([lookup_pure_call] == [inhaled_operand])) } + forall i: Int :: + { [lookup_pure_call] } :: + ([indices] ==> ([lookup_pure_call] == [inhaled_operand])) }, })); Ok(stmts) @@ -6423,8 +6794,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let havoc_ref_method_name = self .encoder .encode_builtin_method_use(BuiltinMethodKind::HavocRef)?; - if let vir::Expr::Local( vir::Local {variable: ref dst_local_var, ..} ) = dst { - Ok(vec![vir::Stmt::MethodCall( vir::MethodCall { + if let vir::Expr::Local(vir::Local { + variable: ref dst_local_var, + .. + }) = dst + { + Ok(vec![vir::Stmt::MethodCall(vir::MethodCall { method_name: havoc_ref_method_name, arguments: vec![], targets: vec![dst_local_var.clone()], @@ -6432,12 +6807,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } else { let tmp_var = self.get_auxiliary_local_var("havoc", dst.get_type().clone()); Ok(vec![ - vir::Stmt::MethodCall( vir::MethodCall { + vir::Stmt::MethodCall(vir::MethodCall { method_name: havoc_ref_method_name, arguments: vec![], targets: vec![tmp_var.clone()], }), - vir::Stmt::Assign( vir::Assign { + vir::Stmt::Assign(vir::Assign { target: dst.clone(), source: tmp_var.into(), kind: vir::AssignKind::Move, @@ -6447,17 +6822,21 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } /// Havoc and assume permission on fields - fn encode_havoc_and_initialization(&mut self, dst: &vir::Expr) -> EncodingResult> { + fn encode_havoc_and_initialization( + &mut self, + dst: &vir::Expr, + ) -> EncodingResult> { debug!("Encode havoc and allocation {:?}", dst); let mut stmts = vec![]; // Havoc `dst` stmts.extend(self.encode_havoc(dst)?); // Initialize `dst` - stmts.push(vir::Stmt::Inhale( vir::Inhale { - expr: self.mir_encoder - .encode_place_predicate_permission(dst.clone(), vir::PermAmount::Write) - .unwrap(), + stmts.push(vir::Stmt::Inhale(vir::Inhale { + expr: self + .mir_encoder + .encode_place_predicate_permission(dst.clone(), vir::PermAmount::Write) + .unwrap(), })); Ok(stmts) } @@ -6471,7 +6850,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { field: vir::Field, location: mir::Location, vir_assign_kind: vir::AssignKind, - can_copy_reference: bool // reference copies are allowed if the field is a constant + can_copy_reference: bool, // reference copies are allowed if the field is a constant ) -> SpannedEncodingResult> { trace!( "[enter] prepare_assign_target(dst={}, field={}, location={:?})", @@ -6484,9 +6863,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let mut alloc_stmts = self.encode_havoc(&dst).with_span(span)?; let dst_field = dst.clone().field(field.clone()); let acc = vir::Expr::acc_permission(dst_field.clone(), vir::PermAmount::Write); - alloc_stmts.push(vir::Stmt::Inhale( vir::Inhale { - expr: acc, - })); + alloc_stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: acc })); match vir_assign_kind { vir::AssignKind::Copy => { if field.typ.is_typed_ref_or_type_var() { @@ -6494,11 +6871,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let pred_acc = vir::Expr::predicate_access_predicate( field.typ, dst_field, - vir::PermAmount::Read + vir::PermAmount::Read, ); - alloc_stmts.push(vir::Stmt::Inhale( - vir::Inhale { expr: pred_acc } - )); + alloc_stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: pred_acc })); } else { // TODO: Inhale the predicate rooted at dst_field return Err(SpannedEncodingError::unsupported( @@ -6534,9 +6909,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { field.clone(), location, vir::AssignKind::Copy, - false + false, )?; - stmts.push(vir::Stmt::Assign( vir::Assign { + stmts.push(vir::Stmt::Assign(vir::Assign { target: lhs.field(field), source: rhs, kind: vir::AssignKind::Copy, @@ -6555,12 +6930,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) -> SpannedEncodingResult> { let span = self.mir_encoder.get_span_of_location(location); let field = self.encoder.encode_value_field(ty).with_span(span)?; - self.encode_copy_value_assign2( - dst, - src.field(field.clone()), - field, - location - ) + self.encode_copy_value_assign2(dst, src.field(field.clone()), field, location) } /// Copy a value by inhaling snapshot equality. @@ -6570,11 +6940,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { dst: vir::Expr, ) -> EncodingResult> { let mut stmts = self.encode_havoc_and_initialization(&dst)?; - stmts.push(vir::Stmt::Inhale( vir::Inhale { - expr: vir::Expr::eq_cmp( - vir::Expr::snap_app(src), - vir::Expr::snap_app(dst), - ), + stmts.push(vir::Stmt::Inhale(vir::Inhale { + expr: vir::Expr::eq_cmp(vir::Expr::snap_app(src), vir::Expr::snap_app(dst)), })); Ok(stmts) } @@ -6592,9 +6959,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { | ty::TyKind::Int(_) | ty::TyKind::Uint(_) | ty::TyKind::Float(_) - | ty::TyKind::Char => { - self.encode_copy_primitive_value(src, dst, self_ty, location)? - } + | ty::TyKind::Char => self.encode_copy_primitive_value(src, dst, self_ty, location)?, ty::TyKind::Adt(_, _) | ty::TyKind::Closure(_, _) @@ -6606,8 +6971,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { _ => { return Err(SpannedEncodingError::unsupported( - format!("copy operation for an unsupported type {:?}", self_ty.kind()), - span + format!( + "copy operation for an unsupported type {:?}", + self_ty.kind() + ), + span, )); } }; @@ -6660,7 +7028,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { if adt_def.is_union() { return Err(SpannedEncodingError::unsupported( "unions are not supported", - span + span, )); } let num_variants = adt_def.variants().len(); @@ -6671,10 +7039,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // Handle *signed* discriminats let discr_type = adt_def.repr().discr_type(); let discr_value: vir::Expr = if discr_type.is_signed() { - let bit_size = - Integer::from_attr(&tcx, discr_type) - .size() - .bits(); + let bit_size = Integer::from_attr(&tcx, discr_type).size().bits(); let shift = 128 - bit_size; let unsigned_discr = adt_def.discriminant_for_variant(tcx, variant_index).val; @@ -6691,20 +7056,24 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let discriminant = self .encoder .encode_discriminant_func_app(dst.clone(), adt_def)?; - stmts.push(vir::Stmt::Inhale( vir::Inhale { + stmts.push(vir::Stmt::Inhale(vir::Inhale { expr: vir::Expr::eq_cmp(discriminant, discr_value), })); let variant_name = variant_def.ident(tcx).to_string(); let new_dst_base = dst_base.variant(&variant_name); - let variant_field = if let vir::Expr::Variant( vir::Variant {variant_index: ref field, ..}) = new_dst_base { + let variant_field = if let vir::Expr::Variant(vir::Variant { + variant_index: ref field, + .. + }) = new_dst_base + { field.clone() } else { unreachable!() }; if !variant_def.fields.is_empty() { - stmts.push(vir::Stmt::Downcast( vir::Downcast { + stmts.push(vir::Stmt::Downcast(vir::Downcast { base: dst.clone(), field: variant_field, })); @@ -6716,7 +7085,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let operand = &operands[field_index]; let field_name = field.ident(tcx).to_string(); let field_ty = field.ty(tcx, subst); - let encoded_field = self.encoder + let encoded_field = self + .encoder .encode_struct_field(&field_name, field_ty) .with_span(span)?; stmts.extend(self.encode_assign_operand( @@ -6729,12 +7099,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { mir::AggregateKind::Closure(def_id, substs) => { // TODO: might need to assert history invariants? - assert!(!self.encoder.is_spec_closure(def_id.to_def_id()), "spec closure: {def_id:?}"); + assert!( + !self.encoder.is_spec_closure(def_id.to_def_id()), + "spec closure: {def_id:?}" + ); let cl_substs = substs.as_closure(); for (field_index, field_ty) in cl_substs.upvar_tys().enumerate() { let operand = &operands[field_index]; let field_name = format!("closure_{field_index}"); - let encoded_field = self.encoder + let encoded_field = self + .encoder .encode_raw_ref_field(field_name, field_ty) .with_span(span)?; stmts.extend(self.encode_assign_operand( @@ -6747,14 +7121,22 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { mir::AggregateKind::Array(..) => { let sequence_types = self.encoder.encode_sequence_types(ty).with_span(span)?; - let lookup_ret_ty = self.encoder.encode_snapshot_type(sequence_types.elem_ty_rs) + let lookup_ret_ty = self + .encoder + .encode_snapshot_type(sequence_types.elem_ty_rs) .with_span(span)?; for (idx, operand) in operands.iter().enumerate() { - let lookup_pure_call = sequence_types - .encode_lookup_pure_call(self.encoder, dst.clone(), idx.into(), lookup_ret_ty.clone()); + let lookup_pure_call = sequence_types.encode_lookup_pure_call( + self.encoder, + dst.clone(), + idx.into(), + lookup_ret_ty.clone(), + ); - let encoded_operand = self.mir_encoder.encode_operand_expr(operand) + let encoded_operand = self + .mir_encoder + .encode_operand_expr(operand) .with_span(span)?; stmts.push( @@ -6768,7 +7150,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { mir::AggregateKind::Generator(..) => { return Err(SpannedEncodingError::unsupported( "construction of generators is not supported", - span + span, )); } } @@ -6786,7 +7168,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { Ok(()) } - fn get_label_after_location(&mut self, location: mir::Location) -> SpannedEncodingResult> { + fn get_label_after_location( + &mut self, + location: mir::Location, + ) -> SpannedEncodingResult> { let opt_label = self.label_after_location.get(&location); if opt_label.is_none() { if config::allow_unreachable_unsupported_code() { @@ -6824,9 +7209,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { location: mir::Location, ) -> SpannedEncodingResult<(vir::Expr, Vec, ty::Ty<'tcx>, Option)> { let span = self.mir_encoder.get_span_of_location(location); - let (encoded_place, ty, variant_idx) = self.mir_encoder.encode_place(place).with_span(span)?; + let (encoded_place, ty, variant_idx) = + self.mir_encoder.encode_place(place).with_span(span)?; trace!("encode_place(ty={:?})", ty); - let (encoded_expr, encoding_stmts) = self.postprocess_place_encoding(encoded_place, encode_kind).with_span(span)?; + let (encoded_expr, encoding_stmts) = self + .postprocess_place_encoding(encoded_place, encode_kind) + .with_span(span)?; Ok((encoded_expr, encoding_stmts, ty, variant_idx)) } @@ -6839,13 +7227,23 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) -> EncodingResult<(vir::Expr, Vec)> { let sequence_types = self.encoder.encode_sequence_types(sequence_ty)?; - let lookup_res: vir::Expr = self.cfg_method.add_fresh_local_var(sequence_types.elem_pred_type.clone()).into(); - let lookup_res_val_field = self.encoder.encode_value_expr(lookup_res.clone(), sequence_types.elem_ty_rs)?; - let snap_lookup_res_val_field = self.encoder.patch_snapshots(vir::Expr::snap_app(lookup_res_val_field))?; + let lookup_res: vir::Expr = self + .cfg_method + .add_fresh_local_var(sequence_types.elem_pred_type.clone()) + .into(); + let lookup_res_val_field = self + .encoder + .encode_value_expr(lookup_res.clone(), sequence_types.elem_ty_rs)?; + let snap_lookup_res_val_field = self + .encoder + .patch_snapshots(vir::Expr::snap_app(lookup_res_val_field))?; - let lookup_ret_ty = self.encoder.encode_snapshot_type(sequence_types.elem_ty_rs)?; + let lookup_ret_ty = self + .encoder + .encode_snapshot_type(sequence_types.elem_ty_rs)?; - let (encoded_base_expr, mut stmts) = self.postprocess_place_encoding(base, ArrayAccessKind::Shared)?; + let (encoded_base_expr, mut stmts) = + self.postprocess_place_encoding(base, ArrayAccessKind::Shared)?; stmts.extend(self.encode_havoc_and_initialization(&lookup_res)?); let idx_val_int = self.encoder.patch_snapshots(vir::Expr::snap_app(index))?; @@ -6857,13 +7255,17 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { lookup_ret_ty, ); - stmts.push(vir::Stmt::Assert( vir::Assert { - expr: vir::Expr::predicate_access_predicate(sequence_types.sequence_pred_type, encoded_base_expr, vir::PermAmount::Read), + stmts.push(vir::Stmt::Assert(vir::Assert { + expr: vir::Expr::predicate_access_predicate( + sequence_types.sequence_pred_type, + encoded_base_expr, + vir::PermAmount::Read, + ), position: vir::Position::default(), })); - stmts.push(vir::Stmt::Inhale( vir::Inhale { - expr: vir_expr!{ [ lookup_pure_call ] == [ snap_lookup_res_val_field ] } + stmts.push(vir::Stmt::Inhale(vir::Inhale { + expr: vir_expr! { [ lookup_pure_call ] == [ snap_lookup_res_val_field ] }, })); Ok((lookup_res, stmts)) } @@ -6878,11 +7280,19 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) -> EncodingResult<(vir::Expr, Vec)> { let sequence_types = self.encoder.encode_sequence_types(sequence_ty)?; - let res: vir::Expr = self.cfg_method.add_fresh_local_var(sequence_types.elem_pred_type.clone()).into(); - let res_val_field = self.encoder.encode_value_expr(res.clone(), sequence_types.elem_ty_rs)?; - let snap_res_val_field = self.encoder.patch_snapshots(vir::Expr::snap_app(res_val_field.clone()))?; + let res: vir::Expr = self + .cfg_method + .add_fresh_local_var(sequence_types.elem_pred_type.clone()) + .into(); + let res_val_field = self + .encoder + .encode_value_expr(res.clone(), sequence_types.elem_ty_rs)?; + let snap_res_val_field = self + .encoder + .patch_snapshots(vir::Expr::snap_app(res_val_field.clone()))?; - let (encoded_base_expr, mut stmts) = self.postprocess_place_encoding(base, ArrayAccessKind::Mutable(None, location))?; + let (encoded_base_expr, mut stmts) = + self.postprocess_place_encoding(base, ArrayAccessKind::Mutable(None, location))?; let idx_val_int = self.encoder.patch_snapshots(vir::Expr::snap_app(index))?; @@ -6900,26 +7310,26 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { })); // exhale Array$4$i32(self) - let array_access_pred = vir::Expr::pred_permission( - encoded_base_expr.clone(), - vir::PermAmount::Write, - ).unwrap(); + let array_access_pred = + vir::Expr::pred_permission(encoded_base_expr.clone(), vir::PermAmount::Write).unwrap(); - stmts.push(vir_stmt!{ exhale [array_access_pred] }); + stmts.push(vir_stmt! { exhale [array_access_pred] }); - let old = |e| { vir::Expr::labelled_old(&before_label, e) }; - let old_lhs = |e| { vir::Expr::labelled_old("lhs", e) }; + let old = |e| vir::Expr::labelled_old(&before_label, e); + let old_lhs = |e| vir::Expr::labelled_old("lhs", e); // value of res - let lookup_ret_ty = self.encoder.encode_snapshot_type(sequence_types.elem_ty_rs)?; + let lookup_ret_ty = self + .encoder + .encode_snapshot_type(sequence_types.elem_ty_rs)?; let lookup_pure_call = sequence_types.encode_lookup_pure_call( self.encoder, encoded_base_expr.clone(), idx_val_int.clone(), lookup_ret_ty.clone(), ); - stmts.push(vir::Stmt::Inhale( vir::Inhale { - expr: vir_expr!{ [ old(lookup_pure_call) ] == [ snap_res_val_field ] } + stmts.push(vir::Stmt::Inhale(vir::Inhale { + expr: vir_expr! { [ old(lookup_pure_call) ] == [ snap_res_val_field ] }, })); // inhale magic wand @@ -6936,21 +7346,22 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // NEEDSWORK: the vir! macro can't do everything we want here.. // TODO: de-duplicate with encode_array_direct_assign - let i_var: vir::Expr = vir_local!{ i: Int }.into(); + let i_var: vir::Expr = vir_local! { i: Int }.into(); - let zero_le_i = vir_expr!{ [ vir::Expr::from(0) ] <= [ i_var ] }; - let i_lt_len = vir_expr!{ [ i_var ] < [ sequence_types.len(self.encoder, encoded_base_expr.clone()) ] }; - let i_ne_idx = vir_expr!{ [ i_var ] != [ old(idx_val_int.clone()) ] }; - let idx_conditions = vir_expr!{ [zero_le_i] && ([i_lt_len] && [i_ne_idx]) }; + let zero_le_i = vir_expr! { [ vir::Expr::from(0) ] <= [ i_var ] }; + let i_lt_len = vir_expr! { [ i_var ] < [ sequence_types.len(self.encoder, encoded_base_expr.clone()) ] }; + let i_ne_idx = vir_expr! { [ i_var ] != [ old(idx_val_int.clone()) ] }; + let idx_conditions = vir_expr! { [zero_le_i] && ([i_lt_len] && [i_ne_idx]) }; let lookup_array_i = sequence_types.encode_lookup_pure_call( self.encoder, encoded_base_expr.clone(), i_var, lookup_ret_ty.clone(), ); - let lookup_same_as_old = vir_expr!{ [lookup_array_i] == [old(lookup_array_i.clone())] }; - let forall_body = vir_expr!{ [idx_conditions] ==> [lookup_same_as_old] }; - let all_others_unchanged = vir_expr!{ forall i: Int :: { [lookup_array_i] } :: [ forall_body ] }; + let lookup_same_as_old = vir_expr! { [lookup_array_i] == [old(lookup_array_i.clone())] }; + let forall_body = vir_expr! { [idx_conditions] ==> [lookup_same_as_old] }; + let all_others_unchanged = + vir_expr! { forall i: Int :: { [lookup_array_i] } :: [ forall_body ] }; let indexed_lookup_pure = sequence_types.encode_lookup_pure_call( self.encoder, encoded_base_expr.clone(), @@ -6958,14 +7369,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { lookup_ret_ty, ); // TODO: old inside snapshot or the other way around? - let snap_old_res_val_field = self.encoder.patch_snapshots(vir::Expr::snap_app(res_val_field.clone()))?; - let indexed_updated = vir_expr!{ [ indexed_lookup_pure ] == [ old_lhs(snap_old_res_val_field) ] }; + let snap_old_res_val_field = self + .encoder + .patch_snapshots(vir::Expr::snap_app(res_val_field.clone()))?; + let indexed_updated = + vir_expr! { [ indexed_lookup_pure ] == [ old_lhs(snap_old_res_val_field) ] }; - let magic_wand_rhs = vir_expr!{ [all_others_unchanged] && [indexed_updated] }; - self.array_magic_wand_at.insert( - location, - (res_val_field, encoded_base_expr, magic_wand_rhs) - ); + let magic_wand_rhs = vir_expr! { [all_others_unchanged] && [indexed_updated] }; + self.array_magic_wand_at + .insert(location, (res_val_field, encoded_base_expr, magic_wand_rhs)); Ok((res, stmts)) } @@ -6982,8 +7394,18 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let (expr, stmts) = self.postprocess_place_encoding(base, array_encode_kind)?; (expr.field(field), stmts) } - PlaceEncoding::SliceAccess { base, index, rust_slice_ty: rust_ty, .. } | - PlaceEncoding::ArrayAccess { base, index, rust_array_ty: rust_ty, .. } => { + PlaceEncoding::SliceAccess { + base, + index, + rust_slice_ty: rust_ty, + .. + } + | PlaceEncoding::ArrayAccess { + base, + index, + rust_array_ty: rust_ty, + .. + } => { if let ArrayAccessKind::Mutable(loan, location) = array_encode_kind { self.encode_sequence_lookup_mut(*base, index, rust_ty, loan, location)? } else { @@ -6992,17 +7414,23 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } PlaceEncoding::Variant { box base, field } => { let (expr, stmts) = self.postprocess_place_encoding(base, array_encode_kind)?; - (vir::Expr::Variant( vir::Variant { - base: box expr, - variant_index: field, - position: vir::Position::default(), - }), - stmts) + ( + vir::Expr::Variant(vir::Variant { + base: box expr, + variant_index: field, + position: vir::Position::default(), + }), + stmts, + ) } }) } - pub fn register_error>(&self, span: T, error_ctxt: ErrorCtxt) -> vir::Position { + pub fn register_error>( + &self, + span: T, + error_ctxt: ErrorCtxt, + ) -> vir::Position { self.mir_encoder.register_error(span, error_ctxt) } } @@ -7022,17 +7450,19 @@ fn convert_loans_to_borrows(loans: &[facts::Loan]) -> Vec { /// len: Length of borrow_infos fn assert_one_magic_wand(len: usize) -> EncodingResult<()> { if len > 1 { - Err(EncodingError::internal( - format!("We can have at most one magic wand in the postcondition. But we have {len:?}") - )) - } else { Ok(()) } + Err(EncodingError::internal(format!( + "We can have at most one magic wand in the postcondition. But we have {len:?}" + ))) + } else { + Ok(()) + } } // Checks if a type is a reference to a string, or a reference to a reference to a string, etc. fn is_str(ty: ty::Ty<'_>) -> bool { match ty.kind() { ty::TyKind::Ref(_, inner, _) => inner.is_str() || is_str(*inner), - _ => false + _ => false, } } From 0bb826656bfaf1d8e10c8530c681de1e53b7a876 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Thu, 19 Jan 2023 14:50:37 +0100 Subject: [PATCH 25/26] More quadratic loops in a seperate test --- .../verify/pass/time-resources/double_loop.rs | 81 +++++++++++++++++++ .../tests/verify/pass/time-resources/loop.rs | 30 +++---- 2 files changed, 97 insertions(+), 14 deletions(-) create mode 100644 prusti-tests/tests/verify/pass/time-resources/double_loop.rs diff --git a/prusti-tests/tests/verify/pass/time-resources/double_loop.rs b/prusti-tests/tests/verify/pass/time-resources/double_loop.rs new file mode 100644 index 00000000000..84bbbd7ff24 --- /dev/null +++ b/prusti-tests/tests/verify/pass/time-resources/double_loop.rs @@ -0,0 +1,81 @@ +// compile-flags: -Ptime_reasoning=true + +use prusti_contracts::*; + +// ignore-test: z3 does not support well quadratic equations + +#[requires(time_credits((n * n) + 2 * n + 1))] +#[ensures(time_receipts((n * n) + 2 * n + 1))] +fn double_loop(n: usize) -> u32 { + let mut i = 0; + let mut res = 0; + while i < n { + body_invariant!(time_credits((n - i) * (n + 2))); + body_invariant!(time_receipts(i * (n + 2))); + res += sum(n as u32); + i += 1; + } + res +} + +#[requires(time_credits((n + 1) * n + 1))] +#[ensures(time_receipts((n + 1) * n + 1))] +fn two_level_loop(n: usize) -> usize { + let mut i = 0; + let mut res = 0; + while i < n { + body_invariant!(i < n); + body_invariant!(time_credits((n + 1) * (n - i))); + body_invariant!(time_receipts((n + 1) * i)); + let mut j = 0; + let mut inner_res = 0; + while j < n { + body_invariant!(time_credits(n - j)); + body_invariant!(time_receipts(j)); + res += j; + j += 1; + } + res += inner_res; + i += 1; + } + res +} + +#[requires(time_credits(n as usize + 1))] +#[ensures(time_receipts(n as usize + 1))] +fn sum(n: u32) -> u32 { + let mut i = 0; + let mut res = 0; + while i < n { + body_invariant!(time_credits((n - i) as usize)); + body_invariant!(time_receipts(i as usize)); + res += i; + i += 1; + } + res +} + +#[requires(time_credits((n + 1) * n + 1))] +#[ensures(time_receipts((n + 1) * n + 1))] +fn two_level_loop_body_inv_end(n: usize) -> usize { + let mut i = 0; + let mut res = 0; + while i < n { + let mut j = 0; + let mut inner_res = 0; + while j < n { + body_invariant!(time_credits(n - j)); + body_invariant!(time_receipts(j)); + res += j; + j += 1; + } + body_invariant!(time_credits((n + 1) * (n - i - 1) + 1)); + body_invariant!(time_receipts((n + 1) * i + n)); + res += inner_res; + i += 1; + } + res +} + +#[requires(time_credits(1))] +fn main() {} \ No newline at end of file diff --git a/prusti-tests/tests/verify/pass/time-resources/loop.rs b/prusti-tests/tests/verify/pass/time-resources/loop.rs index 02ac150f395..df610589dc4 100644 --- a/prusti-tests/tests/verify/pass/time-resources/loop.rs +++ b/prusti-tests/tests/verify/pass/time-resources/loop.rs @@ -30,20 +30,6 @@ fn sum2(n: u32) -> u32 { res } -#[requires(time_credits((n * n) + 2 * n + 1))] -#[ensures(time_receipts((n * n) + 2 * n + 1))] -fn double_loop(n: usize) -> u32 { - let mut i = 0; - let mut res = 0; - while i < n { - body_invariant!(time_credits((n - i) * (n + 2))); - body_invariant!(time_receipts(i * (n + 2))); - res += sum(n as u32); - i += 1; - } - res -} - #[requires(time_credits(1))] #[ensures(time_receipts(1))] fn foo() -> usize { @@ -65,6 +51,22 @@ fn loop_foo(n: usize) -> usize { res } +#[requires(time_credits(2 * n + 1))] +#[ensures(time_receipts(2 * n + 1))] +fn loop_foo_before_body_inv(n: usize) -> usize { + let mut i = 0; + let mut res = 0; + while i < n { + foo(); + body_invariant!(i < n); + body_invariant!(time_credits(2 * n - 2 * i - 1)); + body_invariant!(time_receipts(2 * i + 1)); + res += 1; + i += 1; + } + res +} + #[requires(time_credits(2 * n + 3))] #[ensures(time_receipts(2 * n + 3))] From 31c8871273d530ebeb7f0b4589328c6f8807d936 Mon Sep 17 00:00:00 2001 From: Pialex99 Date: Wed, 25 Jan 2023 17:01:21 +0100 Subject: [PATCH 26/26] Clippy --- prusti-viper/src/encoder/resources/interface.rs | 2 +- vir/src/legacy/ast/expr.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prusti-viper/src/encoder/resources/interface.rs b/prusti-viper/src/encoder/resources/interface.rs index fe62b039062..eb85fef7572 100644 --- a/prusti-viper/src/encoder/resources/interface.rs +++ b/prusti-viper/src/encoder/resources/interface.rs @@ -37,7 +37,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ResourcesEncoderInterface amount.into(), scope_id, ), - pos.clone(), + pos, ) })); stmts.extend(scope_ids.iter().map(|&scope_id| { diff --git a/vir/src/legacy/ast/expr.rs b/vir/src/legacy/ast/expr.rs index 87b68b2d6c5..56c9ba7d8d8 100644 --- a/vir/src/legacy/ast/expr.rs +++ b/vir/src/legacy/ast/expr.rs @@ -196,7 +196,7 @@ impl fmt::Display for Expr { write!(f, "acc({pred_name}({arg}), {perm})") } Expr::ResourceAccessPredicate(resouce_name, amount, scope_id, _) => { - write!(f, "acc({}({}), {}/1)", resouce_name, scope_id, amount) + write!(f, "acc({resouce_name}({scope_id}), {amount}/1)") } Expr::FieldAccessPredicate(ref expr, perm, ref _pos) => { write!(f, "acc({expr}, {perm})")