Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Iterator tracking issues #980

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
4caee3a
Adjust copy check in environment to better handle regions
vl0w Apr 26, 2022
ed7dffd
Maybe we need to handle TyKind::Ref differently?
vl0w Apr 26, 2022
03b3640
Support associated types in copy check
vl0w Apr 27, 2022
138d98e
Pass type with binder to type_is_copy to better account for regions
vl0w Apr 27, 2022
a408861
Normalize function signature in PureFunctionEncoder to account for as…
vl0w Apr 28, 2022
dbbed0d
Remove check for validity of pure function in process_encoding_queue
vl0w Apr 28, 2022
161b956
Erase all regions in copy check
vl0w Apr 28, 2022
eda1445
Do not perform normalization when there are no projections
vl0w Apr 28, 2022
41aad37
Partially undo removal of code in process_encoding_queue.
vl0w Apr 28, 2022
e2a5467
Rename normalize_to, return original value if normalized value contai…
vl0w Apr 28, 2022
43a5387
Pass ParamEnv to resolve_assoc_types, make it fallible
vl0w Apr 29, 2022
b604cd4
Small cleanups
vl0w Apr 29, 2022
5dc9705
Add custom iterator tests
vl0w Apr 20, 2022
f8f2859
Add flag to add experimental iterator support
vl0w Apr 21, 2022
0da3287
Move predicate normalization in constraint solver for better debugging
vl0w Apr 27, 2022
c1cbead
Do not copy preconditions of base spec to constrained spec
vl0w Apr 27, 2022
59bc78c
Use predicate_must_hold_modulo_regions when resolving ghost constrain…
vl0w Apr 29, 2022
0af45d7
Handle lifetimes in merge_generics
vl0w Apr 29, 2022
c527dc1
Support lifetimes in type models
vl0w May 2, 2022
c8081fb
Relax needs_infer check in Environment::resolve_method_call to ignore…
vl0w May 2, 2022
78c51fb
Merge branch 'master' into iterators-feature-flag
vl0w May 2, 2022
af55da2
Support lifetimes in type models
vl0w May 3, 2022
8c7a2ab
Add first tests
vl0w May 3, 2022
fa1c14a
Verification of Iter in while loop
vl0w May 4, 2022
5e396ff
Create custom Copy/Clone impls for type models (do not derive them)
vl0w May 4, 2022
3afba7c
Adjust flag docs for iterator killswitch
vl0w May 4, 2022
70e5b0f
Normalize substs in Environment::resolve_method_call to account for n…
vl0w May 6, 2022
ed1a7a5
Fix typo
vl0w May 8, 2022
d1d4953
Support associated types in quantifiers
vl0w May 9, 2022
7e7bde4
Remove dead comment in any_type_needs_infer
vl0w May 9, 2022
e3bbbd9
Remove dead code
vl0w May 11, 2022
8e05591
Merge branch 'master' into iterators-feature-flag
vl0w May 23, 2022
116bcd6
Merge branch 'master' into iterators-feature-flag
vl0w May 30, 2022
f16f646
fix for #1033 with test cases
Pointerbender Jun 7, 2022
96ee8b9
snapshot equality
Aurel300 Jul 12, 2022
414e081
remove special Fn*::call* treatment
Aurel300 Jul 18, 2022
2b6d632
test closures using type-dependent contracts
Aurel300 Jul 18, 2022
9142078
Merge remote-tracking branch 'upstream/master' into iterators-feature…
Aurel300 Jul 19, 2022
186e97e
fix
Aurel300 Jul 19, 2022
8ed4eb2
Merge remote-tracking branch 'upstream/master' into iterators-feature…
Aurel300 Jul 22, 2022
dbd4d2d
erase regions less eagerly for method calls
Aurel300 Jul 22, 2022
1eaa7d0
Merge remote-tracking branch 'upstream/master' into iterators-feature…
Aurel300 Jul 27, 2022
c74215b
fix
Aurel300 Jul 27, 2022
e9e5c78
Merge remote-tracking branch 'upstream/master' into iterators-feature…
Aurel300 Jul 29, 2022
fa449f9
another unsafe core proof workaround for trait resolution
Aurel300 Jul 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/dev-guide/src/config/flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
| [`DUMP_VIPER_PROGRAM`](#dump_viper_program) | `bool` | `false` |
| [`ENABLE_CACHE`](#enable_cache) | `bool` | `true` |
| [`ENABLE_GHOST_CONSTRAINTS`](#enable_ghost_constraints) | `bool` | `false` |
| [`ENABLE_ITERATORS`](#enable_iterators) | `bool` | `false` |
| [`ENABLE_PURIFICATION_OPTIMIZATION`](#enable_purification_optimization) | `bool` | `false` |
| [`ENABLE_TYPE_INVARIANTS`](#enable_type_invariants) | `bool` | `false` |
| [`ENABLE_VERIFY_ONLY_BASIC_BLOCK_PATH`](#enable_verify_only_basic_block_path) | `bool` | `false` |
Expand Down Expand Up @@ -174,6 +175,13 @@ on a generic type parameter) is satisfied.

**This is an experimental feature**, because it is currently possible to introduce unsound verification behavior.

## `ENABLE_ITERATORS`

When enabled, calls to `Iterator::next` will be encoded in the Viper program.
Otherwise, an error is thrown during the encoding.

**This is an experimental feature**, iterator support is still experimental.

## `ENABLE_PURIFICATION_OPTIMIZATION`

When enabled, impure methods are optimized using the purification optimization, which tries to convert heap operations to pure (snapshot-based) operations.
Expand Down
7 changes: 7 additions & 0 deletions prusti-common/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ lazy_static::lazy_static! {
settings.set_default("print_hash", false).unwrap();
settings.set_default("enable_cache", true).unwrap();
settings.set_default("enable_ghost_constraints", false).unwrap();
settings.set_default("enable_iterators", false).unwrap();

// Flags for testing.
settings.set_default::<Option<i64>>("verification_deadline", None).unwrap();
Expand Down Expand Up @@ -884,3 +885,9 @@ pub fn enable_ghost_constraints() -> bool {
pub fn enable_type_invariants() -> bool {
read_setting("enable_type_invariants")
}

/// When enabled, calls to `Iterator::next` will be encoded in the Viper program.
/// Otherwise, an error is thrown during the encoding.
pub fn enable_iterators() -> bool {
read_setting("enable_iterators")
}
8 changes: 8 additions & 0 deletions prusti-contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,4 +351,12 @@ pub fn exists<T, F>(_trigger_set: T, _closure: F) -> bool {
true
}

pub fn snap<T>(_x: &T) -> T {
unimplemented!()
}

pub fn snapshot_equality<T>(_l: T, _r: T) -> bool {
true
}

pub use private::*;
122 changes: 102 additions & 20 deletions prusti-interface/src/environment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ use prusti_rustc_interface::hir::def_id::{DefId, LocalDefId};
use prusti_rustc_interface::middle::ty::{self, Binder, BoundConstness, ImplPolarity, TraitPredicate, TraitRef, TyCtxt};
use prusti_rustc_interface::middle::ty::subst::{Subst, SubstsRef};
use prusti_rustc_interface::trait_selection::infer::{InferCtxtExt, TyCtxtInferExt};
use prusti_rustc_interface::middle::ty::TypeSuperFoldable;
use prusti_rustc_interface::trait_selection::traits::{ImplSource, Obligation, ObligationCause, SelectionContext};
use std::path::PathBuf;
use prusti_rustc_interface::errors::{DiagnosticBuilder, EmissionGuarantee, MultiSpan};
use prusti_rustc_interface::span::{Span, symbol::Symbol};
use prusti_common::config;
use std::collections::HashSet;
use log::{debug, trace};
use std::rc::Rc;
Expand Down Expand Up @@ -308,7 +310,11 @@ impl<'tcx> Environment<'tcx> {
body
.monomorphised_bodies
.entry(substs)
.or_insert_with(|| ty::EarlyBinder(body.base_body.clone()).subst(self.tcx, substs))
.or_insert_with(|| {
let param_env = self.tcx.param_env(def_id);
let substituted = ty::EarlyBinder(body.base_body.clone()).subst(self.tcx, substs);
self.resolve_assoc_types(substituted.clone(), param_env)
})
.clone()
}

Expand Down Expand Up @@ -519,14 +525,31 @@ impl<'tcx> Environment<'tcx> {
) -> (ProcedureDefId, SubstsRef<'tcx>) {
use prusti_rustc_interface::middle::ty::TypeVisitable;

// avoids a compiler-internal panic
if call_substs.needs_infer() {
// Avoids a compiler-internal panic
// this check ignores any lifetimes/regions, which at this point would
// need inference. They are thus ignored.
// TODO: different behaviour used for unsafe core proof
let needs_infer = if config::unsafe_core_proof() {
call_substs.needs_infer()
} else {
self.any_type_needs_infer(call_substs)
};
if needs_infer {
return (called_def_id, call_substs);
}

let param_env = self.tcx.param_env(caller_def_id);
traits::resolve_instance(self.tcx, param_env.and((called_def_id, call_substs)))

// `resolve_instance` below requires normalized substs.
let normalized_call_substs = self.tcx.normalize_erasing_regions(param_env, call_substs);

traits::resolve_instance(self.tcx, param_env.and((called_def_id, normalized_call_substs)))
.map(|opt_instance| opt_instance
// if the resolved instance is the same as what we queried for
// anyway, ignore it: this way we keep the regions in substs
// at least for non-trait-impl method calls
// TODO: different behaviour used for unsafe core proof
.filter(|instance| !config::unsafe_core_proof() || instance.def_id() != called_def_id)
.map(|instance| (instance.def_id(), instance.substs))
.unwrap_or((called_def_id, call_substs)))
.unwrap_or((called_def_id, call_substs))
Expand All @@ -538,6 +561,7 @@ impl<'tcx> Environment<'tcx> {
// Normalize the type to account for associated types
let ty = self.resolve_assoc_types(ty, param_env);
let ty = self.tcx.erase_late_bound_regions(ty);
let ty = self.tcx.erase_regions(ty);
ty.is_copy_modulo_regions(self.tcx.at(prusti_rustc_interface::span::DUMMY_SP), param_env)
}

Expand Down Expand Up @@ -577,28 +601,86 @@ impl<'tcx> Environment<'tcx> {
);

self.tcx.infer_ctxt().enter(|infcx| {
infcx.predicate_must_hold_considering_regions(&obligation)
infcx.predicate_must_hold_modulo_regions(&obligation)
})
}

/// Normalizes associated types in foldable types,
/// i.e. this resolves projection types ([ty::TyKind::Projection]s)
/// **Important:** Regions while be erased during this process
pub fn resolve_assoc_types<T: ty::TypeFoldable<'tcx> + std::fmt::Debug + Copy>(&self, normalizable: T, param_env: ty::ParamEnv<'tcx>) -> T {
let norm_res = self.tcx.try_normalize_erasing_regions(
param_env,
normalizable
);
pub fn resolve_assoc_types<T: ty::TypeFoldable<'tcx> + std::fmt::Debug>(&self, normalizable: T, param_env: ty::ParamEnv<'tcx>) -> T {
struct Normalizer<'a, 'tcx> {
tcx: &'a ty::TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
}
impl<'a, 'tcx> ty::fold::TypeFolder<'tcx> for Normalizer<'a, 'tcx> {
fn tcx(&self) -> ty::TyCtxt<'tcx> {
*self.tcx
}

fn fold_mir_const(&mut self, c: mir::ConstantKind<'tcx>) -> mir::ConstantKind<'tcx> {
// rustc by default panics when we execute this TypeFolder on a mir::* type,
// but we want to resolve associated types when we retrieve a local mir::Body
c
}

fn fold_ty(&mut self, ty: ty::Ty<'tcx>) -> ty::Ty<'tcx> {
match ty.kind() {
ty::TyKind::Projection(_) => {
let normalized = self.tcx.infer_ctxt().enter(|infcx| {
use prusti_rustc_interface::trait_selection::traits::{fully_normalize, FulfillmentContext};

let normalization_result = fully_normalize(
&infcx,
FulfillmentContext::new(),
ObligationCause::dummy(),
self.param_env,
ty
);

match normalization_result {
Ok(res) => res,
Err(errors) => {
debug!("Error while resolving associated types: {:?}", errors);
ty
}
}
});
normalized.super_fold_with(self)
}
_ => ty.super_fold_with(self)
}
}
}

normalizable.fold_with(&mut Normalizer {tcx: &self.tcx, param_env})
}

fn any_type_needs_infer<T: ty::TypeFoldable<'tcx>>(&self, t: T) -> bool {
// Helper
fn is_nested_ty(ty: ty::Ty<'_>) -> bool {
let mut walker = ty.walk();
let first = walker.next().unwrap().expect_ty();
assert!(ty == first);
walker.next().is_some()
}

match norm_res {
Ok(normalized) => {
debug!("Normalized {:?}: {:?}", normalizable, normalized);
normalized
},
Err(err) => {
debug!("Error while resolving associated types for {:?}: {:?}", normalizable, err);
normalizable
// Visitor
struct NeedsInfer;
impl<'tcx> ty::TypeVisitor<'tcx> for NeedsInfer {
type BreakTy = ();

fn visit_ty(&mut self, ty: ty::Ty<'tcx>) -> std::ops::ControlFlow<Self::BreakTy> {
use prusti_rustc_interface::middle::ty::{TypeVisitable, TypeSuperVisitable};
if is_nested_ty(ty) {
ty.super_visit_with(self)
} else if ty.needs_infer() {
std::ops::ControlFlow::BREAK
} else {
std::ops::ControlFlow::CONTINUE
}
}
}

t.visit_with(&mut NeedsInfer).is_break()
}
}
}
93 changes: 10 additions & 83 deletions prusti-interface/src/specs/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,16 @@ impl SpecGraph<ProcedureSpecification> {
) -> &mut ProcedureSpecification {
self.specs_with_constraints
.entry(constraint)
.or_insert_with(|| self.base_spec.clone())
.or_insert_with(|| {
let mut base = self.base_spec.clone();

// Preconditions of the base spec do not appear in the constrained spec
// Any preconditions that exist on the base spec are thus pruned
// (see comment on impl block)
base.pres = SpecificationItem::Empty;

base
})
}

/// Gets the constraint of a spec function `spec`.
Expand Down Expand Up @@ -462,24 +471,6 @@ pub enum ProcedureSpecificationKindError {
}

impl SpecificationItem<ProcedureSpecificationKind> {
pub fn is_pure(&self) -> Result<bool, ProcedureSpecificationKindError> {
self.validate()?;

Ok(matches!(
self.extract_with_selective_replacement(),
Some(ProcedureSpecificationKind::Pure) | Some(ProcedureSpecificationKind::Predicate(_))
))
}

pub fn is_impure(&self) -> Result<bool, ProcedureSpecificationKindError> {
self.validate()?;

Ok(match self.extract_with_selective_replacement() {
Some(refined) => matches!(refined, ProcedureSpecificationKind::Impure),
_ => true,
})
}

pub fn get_predicate_body(
&self,
) -> Result<Option<&LocalDefId>, ProcedureSpecificationKindError> {
Expand Down Expand Up @@ -806,69 +797,5 @@ mod tests {
));
}
}

mod is_impure {
use super::*;

macro_rules! impure_checks {
($($name:ident: $value:expr,)*) => {
$(
#[test]
fn $name() {
let (item, expected) = $value;
let item: SpecificationItem<ProcedureSpecificationKind> = item;
let result = item.is_impure().expect("Expected impure");
assert_eq!(result, expected);
}
)*
}
}

impure_checks!(
empty: (Empty, true),
inherent_impure: (Inherent(Impure), true),
inherent_pure: (Inherent(Pure), false),
inherent_predicate: (Inherent(Predicate(None)), false),
inherited_impure: (Inherited(Impure), true),
inherited_pure: (Inherited(Pure), false),
inherited_predicate: (Inherited(Predicate(None)), false),
refined_impure_parent_impure_child: (Refined(Impure, Impure), true),
refined_impure_parent_pure_child: (Refined(Impure, Pure), false),
refined_pure_parent_with_pure_child: (Refined(Pure, Pure), false),
refined_predicate_parent_with_predicate_child: (Refined(Predicate(None), Predicate(None)), false),
);
}

mod is_pure {
use super::*;

macro_rules! pure_checks {
($($name:ident: $value:expr,)*) => {
$(
#[test]
fn $name() {
let (item, expected) = $value;
let item: SpecificationItem<ProcedureSpecificationKind> = item;
let result = item.is_pure().expect("Expected pure");
assert_eq!(result, expected);
}
)*
}
}

pure_checks!(
empty: (Empty, false),
inherent_impure: (Inherent(Impure), false),
inherent_pure: (Inherent(Pure), true),
inherent_predicate: (Inherent(Predicate(None)), true),
inherited_impure: (Inherited(Impure), false),
inherited_pure: (Inherited(Pure), true),
inherited_predicate: (Inherited(Predicate(None)), true),
refined_impure_parent_impure_child: (Refined(Impure, Impure), false),
refined_impure_parent_pure_child: (Refined(Impure, Pure), true),
refined_pure_parent_with_pure: (Refined(Pure, Pure), true),
refined_predicate_parent_with_predicate_child: (Refined(Predicate(None), Predicate(None)), true),
);
}
}
}
Loading