diff --git a/Cargo.lock b/Cargo.lock index 7a380e2ad8..cc9ab93a4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,42 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "contracts" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a9006c4f456f14dad414612e77f05f95f2230439c20c2d0997a14caf303285" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ctor" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "datafrog" version = "2.0.1" @@ -20,6 +50,12 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" +[[package]] +name = "dyn-clone" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" + [[package]] name = "fixedbitset" version = "0.1.9" @@ -41,6 +77,21 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063" +[[package]] +name = "output_vt100" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +dependencies = [ + "winapi", +] + +[[package]] +name = "paste" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" + [[package]] name = "petgraph" version = "0.4.13" @@ -72,19 +123,99 @@ dependencies = [ [[package]] name = "polonius-engine" -version = "0.13.0" +version = "0.14.0" dependencies = [ + "contracts", "datafrog", + "dyn-clone", "log", + "paste", + "pretty_assertions", "rustc-hash", + "smallvec", ] [[package]] name = "polonius-parser" version = "0.5.0" +[[package]] +name = "pretty_assertions" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc" +dependencies = [ + "ansi_term", + "ctor", + "diff", + "output_vt100", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "syn" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/polonius-engine/Cargo.toml b/polonius-engine/Cargo.toml index a975e91b9c..a18e3f3077 100644 --- a/polonius-engine/Cargo.toml +++ b/polonius-engine/Cargo.toml @@ -1,14 +1,20 @@ [package] name = "polonius-engine" -version = "0.13.0" +version = "0.14.0" authors = ["The Rust Project Developers", "Polonius Developers"] description = "Core definition for the Rust borrow checker" license = "Apache-2.0/MIT" repository = "https://github.com/rust-lang-nursery/polonius" readme = "README.md" keywords = ["compiler", "borrowck", "datalog"] +edition = "2018" [dependencies] +contracts = "0.6.2" datafrog = "2.0.0" -rustc-hash = "1.0.0" +dyn-clone = "1.0.4" log = "0.4" +paste = "1.0.6" +pretty_assertions = "1.0.0" +rustc-hash = "1.0.0" +smallvec = "1.8.0" diff --git a/polonius-engine/src/facts.rs b/polonius-engine/src/compat/all_facts.rs similarity index 72% rename from polonius-engine/src/facts.rs rename to polonius-engine/src/compat/all_facts.rs index 442ba186cc..0c803b4cee 100644 --- a/polonius-engine/src/facts.rs +++ b/polonius-engine/src/compat/all_facts.rs @@ -1,5 +1,6 @@ -use std::fmt::Debug; -use std::hash::Hash; +//! An adapter for the existing `polonius-engine` interface. + +use crate::{Db, Dump, FactTypes, StoreTo}; /// The "facts" which are the basis of the NLL borrow analysis. #[derive(Clone, Debug)] @@ -114,16 +115,51 @@ impl Default for AllFacts { } } -pub trait Atom: - From + Into + Copy + Clone + Debug + Eq + Ord + Hash + 'static -{ - fn index(self) -> usize; -} - -pub trait FactTypes: Copy + Clone + Debug { - type Origin: Atom; - type Loan: Atom; - type Point: Atom; - type Variable: Atom; - type Path: Atom; +impl StoreTo for AllFacts { + const RELATIONS: crate::Rels = &[ + "loan_issued_at", + "universal_region", + "cfg_edge", + "loan_killed_at", + "subset_base", + "loan_invalidated_at", + "var_used_at", + "var_defined_at", + "var_dropped_at", + "use_of_var_derefs_origin", + "drop_of_var_derefs_origin", + "child_path", + "path_is_var", + "path_assigned_at_base", + "path_moved_at_base", + "path_accessed_at_base", + "known_placeholder_subset_base", + "placeholder", + ]; + + fn store_to_db(self, db: &mut Db, _: &mut Dump<'_>) { + db.loan_issued_at = Some(self.loan_issued_at.into()); + db.universal_region = Some(self.universal_region.into_iter().map(|x| (x,)).collect()); + db.cfg_edge = Some(self.cfg_edge.into()); + db.loan_killed_at = Some(self.loan_killed_at.into()); + db.subset_base = Some(self.subset_base.into()); + db.loan_invalidated_at = Some( + self.loan_invalidated_at + .into_iter() + .map(|(a, b)| (b, a)) + .collect(), + ); + db.var_used_at = Some(self.var_used_at.into()); + db.var_defined_at = Some(self.var_defined_at.into()); + db.var_dropped_at = Some(self.var_dropped_at.into()); + db.use_of_var_derefs_origin = Some(self.use_of_var_derefs_origin.into()); + db.drop_of_var_derefs_origin = Some(self.drop_of_var_derefs_origin.into()); + db.child_path = Some(self.child_path.into()); + db.path_is_var = Some(self.path_is_var.into()); + db.path_assigned_at_base = Some(self.path_assigned_at_base.into()); + db.path_moved_at_base = Some(self.path_moved_at_base.into()); + db.path_accessed_at_base = Some(self.path_accessed_at_base.into()); + db.known_placeholder_subset_base = Some(self.known_placeholder_subset.into()); + db.placeholder = Some(self.placeholder.into()); + } } diff --git a/polonius-engine/src/compat/mod.rs b/polonius-engine/src/compat/mod.rs new file mode 100644 index 0000000000..95dda51dd2 --- /dev/null +++ b/polonius-engine/src/compat/mod.rs @@ -0,0 +1,69 @@ +use crate::{FactTypes, Pipeline}; + +mod all_facts; +mod output; + +pub use self::all_facts::AllFacts; +pub use self::output::Output; + +#[derive(Debug, Clone, Copy)] +pub enum Algorithm { + /// Simple rules, but slower to execute + Naive, + + /// Optimized variant of the rules + DatafrogOpt, + + /// Fast to compute, but imprecise: there can be false-positives + /// but no false-negatives. Tailored for quick "early return" situations. + LocationInsensitive, + + /// Compares the `Naive` and `DatafrogOpt` variants to ensure they indeed + /// compute the same errors. + Compare, + + /// Combination of the fast `LocationInsensitive` pre-pass, followed by + /// the more expensive `DatafrogOpt` variant. + Hybrid, +} + +impl Algorithm { + /// Optimized variants that ought to be equivalent to "naive" + pub const OPTIMIZED: &'static [Algorithm] = &[Algorithm::DatafrogOpt]; + + pub fn variants() -> [&'static str; 5] { + [ + "Naive", + "DatafrogOpt", + "LocationInsensitive", + "Compare", + "Hybrid", + ] + } + + fn pipeline(&self) -> Pipeline { + match self { + Algorithm::Naive => Pipeline::naive(), + Algorithm::DatafrogOpt => Pipeline::opt(), + Algorithm::LocationInsensitive => Pipeline::location_insensitive(), + Algorithm::Compare => Pipeline::compare(), + Algorithm::Hybrid => Pipeline::hybrid(), + } + } +} + +impl ::std::str::FromStr for Algorithm { + type Err = String; + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_ref() { + "naive" => Ok(Algorithm::Naive), + "datafrogopt" => Ok(Algorithm::DatafrogOpt), + "locationinsensitive" => Ok(Algorithm::LocationInsensitive), + "compare" => Ok(Algorithm::Compare), + "hybrid" => Ok(Algorithm::Hybrid), + _ => Err(String::from( + "valid values: Naive, DatafrogOpt, LocationInsensitive, Compare, Hybrid", + )), + } + } +} diff --git a/polonius-engine/src/compat/output.rs b/polonius-engine/src/compat/output.rs new file mode 100644 index 0000000000..0ef800453a --- /dev/null +++ b/polonius-engine/src/compat/output.rs @@ -0,0 +1,254 @@ +use std::borrow::Cow; +use std::collections::{BTreeMap, BTreeSet}; + +use rustc_hash::FxHashMap; + +use super::{Algorithm, AllFacts}; +use crate::{dump, Db, FactTypes, LoadFrom}; + +#[derive(Clone, Debug)] +pub struct Output { + pub errors: FxHashMap>, + pub subset_errors: FxHashMap>, + pub move_errors: FxHashMap>, + + pub dump_enabled: bool, + + // these are just for debugging + pub loan_live_at: FxHashMap>, + pub origin_contains_loan_at: FxHashMap>>, + pub origin_contains_loan_anywhere: FxHashMap>, + pub origin_live_on_entry: FxHashMap>, + pub loan_invalidated_at: FxHashMap>, + pub subset: FxHashMap>>, + pub subset_anywhere: FxHashMap>, + pub var_live_on_entry: FxHashMap>, + pub var_drop_live_on_entry: FxHashMap>, + pub path_maybe_initialized_on_exit: FxHashMap>, + pub path_maybe_uninitialized_on_exit: FxHashMap>, + pub known_contains: FxHashMap>, + pub var_maybe_partly_initialized_on_exit: FxHashMap>, +} + +struct OutputErrors { + errors: FxHashMap>, + subset_errors: FxHashMap>, + move_errors: FxHashMap>, +} + +impl<'db, T: FactTypes> LoadFrom<'db, T> for OutputErrors { + const RELATIONS: crate::Rels = &["errors", "subset_errors", "move_errors"]; + + fn load_from_db(facts: &'db Db) -> Self { + let mut ret = OutputErrors { + errors: Default::default(), + subset_errors: Default::default(), + move_errors: Default::default(), + }; + + for &(l, p) in facts.errors.as_ref().unwrap().iter() { + ret.errors.entry(p).or_default().push(l); + } + + for &(o1, o2, p) in facts.subset_errors.as_ref().unwrap().iter() { + ret.subset_errors.entry(p).or_default().insert((o1, o2)); + } + + for &(l, p) in facts.move_errors.as_ref().unwrap().iter() { + ret.move_errors.entry(p).or_default().push(l); + } + + ret + } +} + +impl Output { + fn new(dump_enabled: bool) -> Self { + Self { + errors: Default::default(), + subset_errors: Default::default(), + move_errors: Default::default(), + + dump_enabled, + + loan_live_at: Default::default(), + origin_contains_loan_at: Default::default(), + origin_contains_loan_anywhere: Default::default(), + origin_live_on_entry: Default::default(), + loan_invalidated_at: Default::default(), + subset: Default::default(), + subset_anywhere: Default::default(), + var_live_on_entry: Default::default(), + var_drop_live_on_entry: Default::default(), + path_maybe_initialized_on_exit: Default::default(), + path_maybe_uninitialized_on_exit: Default::default(), + known_contains: Default::default(), + var_maybe_partly_initialized_on_exit: Default::default(), + } + } + + pub fn compute(input: &AllFacts, algorithm: Algorithm, dump_enabled: bool) -> Self { + let pipeline = algorithm.pipeline(); + let mut ret = Output::new(dump_enabled); + let ref mut counts = dump::Counts; + + let dumpers = if dump_enabled { + vec![counts as _, &mut ret as _] + } else { + vec![counts as _] + }; + + let out_errors: OutputErrors<_> = pipeline.compute(input.clone(), dumpers); + ret.errors = out_errors.errors; + ret.subset_errors = out_errors.subset_errors; + ret.move_errors = out_errors.move_errors; + + for &(p, l) in &input.loan_invalidated_at { + ret.loan_invalidated_at.entry(p).or_default().push(l); + } + + ret + } + + pub fn errors_at(&self, location: T::Point) -> &[T::Loan] { + match self.errors.get(&location) { + Some(v) => v, + None => &[], + } + } + + pub fn loans_in_scope_at(&self, location: T::Point) -> &[T::Loan] { + match self.loan_live_at.get(&location) { + Some(p) => p, + None => &[], + } + } + + pub fn origin_contains_loan_at( + &self, + location: T::Point, + ) -> Cow<'_, BTreeMap>> { + assert!(self.dump_enabled); + match self.origin_contains_loan_at.get(&location) { + Some(map) => Cow::Borrowed(map), + None => Cow::Owned(BTreeMap::default()), + } + } + + pub fn origins_live_at(&self, location: T::Point) -> &[T::Origin] { + assert!(self.dump_enabled); + match self.origin_live_on_entry.get(&location) { + Some(v) => v, + None => &[], + } + } + + pub fn subsets_at( + &self, + location: T::Point, + ) -> Cow<'_, BTreeMap>> { + assert!(self.dump_enabled); + match self.subset.get(&location) { + Some(v) => Cow::Borrowed(v), + None => Cow::Owned(BTreeMap::default()), + } + } +} + +impl dump::Dumper for Output { + fn dump_iter(&mut self, id: &dump::RelationId, tuples: Box + '_>) { + use crate::tuples::downcast_iter; + + if !self.dump_enabled { + return; + } + + match (id.relation_name(), id.unit_name()) { + ("loan_live_at", _) => { + for (l, p) in downcast_iter(tuples).unwrap() { + self.loan_live_at.entry(p).or_default().push(l); + } + } + + ("origin_contains_loan_at", "BorrowckLocationInsensitive") => { + for (o, l) in downcast_iter(tuples).unwrap() { + self.origin_contains_loan_anywhere.entry(o).or_default().insert(l); + } + } + + ("origin_contains_loan_at", _) => { + for (o, l, p) in downcast_iter(tuples).unwrap() { + self.origin_contains_loan_at + .entry(p) + .or_default() + .entry(o) + .or_default() + .insert(l); + } + } + + ("origin_live_on_entry", _) => { + for (o, p) in downcast_iter(tuples).unwrap() { + self.origin_live_on_entry.entry(p).or_default().push(o); + } + } + + // loan_invalidated_at + + ("subset", "BorrowckLocationInsensitive") => { + for (o1, o2) in downcast_iter(tuples).unwrap() { + self.subset_anywhere.entry(o1).or_default().insert(o2); + } + } + + ("subset", _) => { + for (o1, o2, p) in downcast_iter(tuples).unwrap() { + self.subset + .entry(p) + .or_default() + .entry(o1) + .or_default() + .insert(o2); + } + } + + ("var_live_on_entry", _) => { + for (v, p) in downcast_iter(tuples).unwrap() { + self.var_live_on_entry.entry(p).or_default().push(v); + } + } + + ("var_drop_live_on_entry", _) => { + for (pt, p) in downcast_iter(tuples).unwrap() { + self.var_drop_live_on_entry.entry(p).or_default().push(pt); + } + } + + ("path_maybe_initialized_on_exit", _) => { + for (pt, p) in downcast_iter(tuples).unwrap() { + self.path_maybe_initialized_on_exit.entry(p).or_default().push(pt); + } + } + + ("path_maybe_uninitialized_on_exit", _) => { + for (pt, p) in downcast_iter(tuples).unwrap() { + self.path_maybe_uninitialized_on_exit.entry(p).or_default().push(pt); + } + } + + ("known_placeholder_requires", _) => { + for (o, l) in downcast_iter(tuples).unwrap() { + self.known_contains.entry(o).or_default().insert(l); + } + } + + ("var_maybe_partly_initialized_on_exit", _) => { + for (v, p) in downcast_iter(tuples).unwrap() { + self.var_maybe_partly_initialized_on_exit.entry(p).or_default().push(v); + } + } + + _ => {} + } + } +} diff --git a/polonius-engine/src/compute/cfg.rs b/polonius-engine/src/compute/cfg.rs new file mode 100644 index 0000000000..6d094e4840 --- /dev/null +++ b/polonius-engine/src/compute/cfg.rs @@ -0,0 +1,29 @@ +use super::{Computation, Dump}; +use crate::FactTypes; + +#[derive(Clone, Copy)] +pub struct Cfg; + +input! { + CfgEdge { + cfg_edge, + } +} + +output!(cfg_node); + +impl Computation for Cfg { + type Input<'db> = CfgEdge<'db, T>; + type Output = CfgNode; + + fn compute(&self, input: Self::Input<'_>, _dump: &mut Dump<'_>) -> Self::Output { + let CfgEdge { cfg_edge } = input; + let cfg_node = cfg_edge + .iter() + .map(|e| e.0) + .chain(cfg_edge.iter().map(|e| e.1)) + .map(|n| (n,)) + .collect(); + CfgNode { cfg_node } + } +} diff --git a/polonius-engine/src/compute/initialization.rs b/polonius-engine/src/compute/initialization.rs new file mode 100644 index 0000000000..19eb1c8342 --- /dev/null +++ b/polonius-engine/src/compute/initialization.rs @@ -0,0 +1,326 @@ +use super::{Computation, Dump}; +use crate::FactTypes; + +use datafrog::{Iteration, Relation, RelationLeaper}; + +// Step 1: compute transitive closures of path operations. This would elaborate, +// for example, an access to x into an access to x.f, x.f.0, etc. We do this for: +// - access to a path +// - initialization of a path +// - moves of a path +// FIXME: transitive rooting in a variable (path_begins_with_var) +// Note that this step may not be entirely necessary! +#[derive(Clone, Copy)] +pub struct Paths; + +input! { + BasePaths { + child_path, + path_is_var, + path_moved_at_base, + path_assigned_at_base, + path_accessed_at_base, + } +} + +output! { + TransitivePaths { + path_moved_at, + path_assigned_at, + path_accessed_at, + path_begins_with_var + } +} + +impl Computation for Paths { + type Input<'db> = BasePaths<'db, T>; + type Output = TransitivePaths; + + fn compute(&self, input: Self::Input<'_>, _dump: &mut Dump<'_>) -> Self::Output { + let BasePaths { + child_path, + path_is_var, + path_moved_at_base, + path_assigned_at_base, + path_accessed_at_base, + } = input; + + let mut iteration = Iteration::new(); + + let ancestor_path = iteration.variable::<(T::Path, T::Path)>("ancestor"); + + // These are the actual targets: + let path_moved_at = iteration.variable::<(T::Path, T::Point)>("path_moved_at"); + let path_assigned_at = iteration.variable::<(T::Path, T::Point)>("path_initialized_at"); + let path_accessed_at = iteration.variable::<(T::Path, T::Point)>("path_accessed_at"); + let path_begins_with_var = + iteration.variable::<(T::Path, T::Variable)>("path_begins_with_var"); + + // ancestor_path(Parent, Child) :- child_path(Child, Parent). + ancestor_path.extend(child_path.iter().map(|&(child, parent)| (parent, child))); + + // path_moved_at(Path, Point) :- path_moved_at_base(Path, Point). + path_moved_at.insert(path_moved_at_base.clone()); + + // path_assigned_at(Path, Point) :- path_assigned_at_base(Path, Point). + path_assigned_at.insert(path_assigned_at_base.clone()); + + // path_accessed_at(Path, Point) :- path_accessed_at_base(Path, Point). + path_accessed_at.insert(path_accessed_at_base.clone()); + + // path_begins_with_var(Path, Var) :- path_is_var(Path, Var). + path_begins_with_var.insert(path_is_var.clone()); + + while iteration.changed() { + // ancestor_path(Grandparent, Child) :- + // ancestor_path(Parent, Child), + // child_path(Parent, Grandparent). + ancestor_path.from_join( + &ancestor_path, + child_path, + |&_parent, &child, &grandparent| (grandparent, child), + ); + + // moving a path moves its children + // path_moved_at(Child, Point) :- + // path_moved_at(Parent, Point), + // ancestor_path(Parent, Child). + path_moved_at.from_join(&path_moved_at, &ancestor_path, |&_parent, &p, &child| { + (child, p) + }); + + // initialising x at p initialises all x:s children + // path_assigned_at(Child, point) :- + // path_assigned_at(Parent, point), + // ancestor_path(Parent, Child). + path_assigned_at.from_join( + &path_assigned_at, + &ancestor_path, + |&_parent, &p, &child| (child, p), + ); + + // accessing x at p accesses all x:s children at p (actually, + // accesses should be maximally precise and this shouldn't happen?) + // path_accessed_at(Child, point) :- + // path_accessed_at(Parent, point), + // ancestor_path(Parent, Child). + path_accessed_at.from_join( + &path_accessed_at, + &ancestor_path, + |&_parent, &p, &child| (child, p), + ); + + // path_begins_with_var(Child, Var) :- + // path_begins_with_var(Parent, Var) + // ancestor_path(Parent, Child). + path_begins_with_var.from_join( + &path_begins_with_var, + &ancestor_path, + |&_parent, &var, &child| (child, var), + ); + } + + Self::Output { + path_assigned_at: path_assigned_at.complete(), + path_moved_at: path_moved_at.complete(), + path_accessed_at: path_accessed_at.complete(), + path_begins_with_var: path_begins_with_var.complete(), + } + } +} + +input! { + TransitivePathsAndCfg { + cfg_edge, + path_moved_at, + path_assigned_at, + } +} + +#[derive(Clone, Copy)] +pub struct MaybeInit; + +output!(path_maybe_initialized_on_exit); + +impl Computation for MaybeInit { + type Input<'db> = TransitivePathsAndCfg<'db, T>; + type Output = PathMaybeInitializedOnExit; + + fn compute(&self, input: Self::Input<'_>, _dump: &mut Dump<'_>) -> Self::Output { + let TransitivePathsAndCfg { + cfg_edge, + path_moved_at, + path_assigned_at, + } = input; + + let mut iteration = Iteration::new(); + + // path_maybe_initialized_on_exit(path, point): Upon leaving `point`, the + // move path `path` is initialized for some path through the CFG. + let path_maybe_initialized_on_exit = + iteration.variable::<(T::Path, T::Point)>("path_maybe_initialized_on_exit"); + + // path_maybe_initialized_on_exit(path, point) :- path_assigned_at(path, point). + path_maybe_initialized_on_exit.insert(path_assigned_at.clone()); + + while iteration.changed() { + // path_maybe_initialized_on_exit(path, point2) :- + // path_maybe_initialized_on_exit(path, point1), + // cfg_edge(point1, point2), + // !path_moved_at(path, point2). + path_maybe_initialized_on_exit.from_leapjoin( + &path_maybe_initialized_on_exit, + ( + cfg_edge.extend_with(|&(_path, point1)| point1), + path_moved_at.extend_anti(|&(path, _point1)| path), + ), + |&(path, _point1), &point2| (path, point2), + ); + } + + path_maybe_initialized_on_exit.complete().into() + } +} + +#[derive(Clone, Copy)] +pub struct MaybeUninit; + +output!(path_maybe_uninitialized_on_exit); + +impl Computation for MaybeUninit { + type Input<'db> = TransitivePathsAndCfg<'db, T>; + type Output = PathMaybeUninitializedOnExit; + + fn compute(&self, input: Self::Input<'_>, _dump: &mut Dump<'_>) -> Self::Output { + let TransitivePathsAndCfg { + cfg_edge, + path_moved_at, + path_assigned_at, + } = input; + + let mut iteration = Iteration::new(); + + // path_maybe_uninitialized_on_exit(Path, Point): There exists at least one + // path through the CFG to Point such that `Path` has been moved out by the + // time we arrive at `Point` without it being re-initialized for sure. + let path_maybe_uninitialized_on_exit = + iteration.variable::<(T::Path, T::Point)>("path_maybe_uninitialized_on_exit"); + + // path_maybe_uninitialized_on_exit(path, point) :- path_moved_at(path, point). + path_maybe_uninitialized_on_exit.insert(path_moved_at.clone()); + + while iteration.changed() { + // path_maybe_uninitialized_on_exit(path, point2) :- + // path_maybe_uninitialized_on_exit(path, point1), + // cfg_edge(point1, point2) + // !path_assigned_at(path, point2). + path_maybe_uninitialized_on_exit.from_leapjoin( + &path_maybe_uninitialized_on_exit, + ( + cfg_edge.extend_with(|&(_path, point1)| point1), + path_assigned_at.extend_anti(|&(path, _point1)| path), + ), + |&(path, _point1), &point2| (path, point2), + ); + } + + path_maybe_uninitialized_on_exit.complete().into() + } +} + +#[derive(Clone, Copy)] +pub struct VarDroppedWhileInit; + +input! { + VarDroppedWhileInitInput { + var_dropped_at, + path_maybe_initialized_on_exit, + path_begins_with_var, + } +} + +output!(var_dropped_while_init_at); + +impl Computation for VarDroppedWhileInit { + type Input<'db> = VarDroppedWhileInitInput<'db, T>; + type Output = VarDroppedWhileInitAt; + + fn compute(&self, input: Self::Input<'_>, dump: &mut Dump<'_>) -> Self::Output { + let VarDroppedWhileInitInput { + path_begins_with_var, + path_maybe_initialized_on_exit, + var_dropped_at, + } = input; + + // var_maybe_partly_initialized_on_exit(var, point): Upon leaving `point`, + // `var` is partially initialized for some path through the CFG, that is + // there has been an initialization of var, and var has not been moved in + // all paths through the CFG. + // + // var_maybe_partly_initialized_on_exit(var, point) :- + // path_maybe_initialized_on_exit(path, point). + // path_begins_with_var(path, var). + let var_maybe_partly_initialized_on_exit = Relation::from_join( + path_maybe_initialized_on_exit, + path_begins_with_var, + |_path, &point, &var| (var, point), + ); + + let var_dropped_while_init_at = Relation::from_join( + &var_maybe_partly_initialized_on_exit, + var_dropped_at, + |&var, &point, _point| (var, point), + ); + + dump.rel( + "var_maybe_partly_initialized_on_exit", + var_maybe_partly_initialized_on_exit, + ); + + var_dropped_while_init_at.into() + } +} + +#[derive(Clone, Copy)] +pub struct MoveError; + +input! { + MoveErrorInput { + cfg_edge, + path_maybe_uninitialized_on_exit, + path_accessed_at, + } +} + +output!(move_errors); + +impl Computation for MoveError { + type Input<'db> = MoveErrorInput<'db, T>; + type Output = MoveErrors; + + fn compute(&self, input: Self::Input<'_>, _dump: &mut Dump<'_>) -> Self::Output { + let MoveErrorInput { + cfg_edge, + path_maybe_uninitialized_on_exit, + path_accessed_at, + } = input; + + // move_error(Path, Point): There is an access to `Path` at `Point`, but + // `Path` is potentially moved (or never initialised). + // + // move_error(Path, TargetNode) :- + // path_maybe_uninitialized_on_exit(Path, SourceNode), + // cfg_edge(SourceNode, TargetNode), + // path_accessed_at(Path, TargetNode). + let move_errors = Relation::from_leapjoin( + path_maybe_uninitialized_on_exit, + ( + cfg_edge.extend_with(|&(_path, source_node)| source_node), + path_accessed_at.extend_with(|&(path, _source_node)| path), + ), + |&(path, _source_node), &target_node| (path, target_node), + ); + + move_errors.into() + } +} diff --git a/polonius-engine/src/compute/liveness.rs b/polonius-engine/src/compute/liveness.rs new file mode 100644 index 0000000000..8288b13eb7 --- /dev/null +++ b/polonius-engine/src/compute/liveness.rs @@ -0,0 +1,125 @@ +use super::{Computation, Dump}; +use crate::FactTypes; + +use datafrog::{Iteration, Relation, RelationLeaper}; + +#[derive(Clone, Copy)] +pub struct LiveOrigins; + +input! { + LiveOriginsInput { + cfg_edge, + cfg_node, + var_dropped_while_init_at, + var_used_at, + var_defined_at, + use_of_var_derefs_origin, + drop_of_var_derefs_origin, + universal_region, + } +} + +output!(origin_live_on_entry); + +impl Computation for LiveOrigins { + type Input<'db> = LiveOriginsInput<'db, T>; + type Output = OriginLiveOnEntry; + + fn compute(&self, input: Self::Input<'_>, dump: &mut Dump<'_>) -> Self::Output { + let LiveOriginsInput { + cfg_edge, + cfg_node, + var_dropped_while_init_at, + var_used_at, + var_defined_at, + use_of_var_derefs_origin, + drop_of_var_derefs_origin, + universal_region, + } = input; + + let cfg_edge_reverse: Relation<_> = cfg_edge + .iter() + .map(|&(point1, point2)| (point2, point1)) + .collect(); + + let mut iteration = Iteration::new(); + + // Variables + + // `var_live_on_entry`: variable `var` is live upon entry at `point` + let var_live_on_entry = iteration.variable::<(T::Variable, T::Point)>("var_live_on_entry"); + // `var_drop_live_on_entry`: variable `var` is drop-live (will be used for a drop) upon entry in `point` + let var_drop_live_on_entry = + iteration.variable::<(T::Variable, T::Point)>("var_drop_live_on_entry"); + + // This is what we are actually calculating: + let origin_live_on_entry = + iteration.variable::<(T::Origin, T::Point)>("origin_live_on_entry"); + + // var_live_on_entry(var, point) :- var_used_at(var, point). + var_live_on_entry.insert(var_used_at.clone()); + + // var_drop_live_on_entry(var, point) :- var_dropped_while_init_at(var, point). + var_drop_live_on_entry.insert(var_dropped_while_init_at.clone()); + + while iteration.changed() { + // origin_live_on_entry(origin, point) :- + // var_drop_live_on_entry(var, point), + // drop_of_var_derefs_origin(var, origin). + origin_live_on_entry.from_join( + &var_drop_live_on_entry, + drop_of_var_derefs_origin, + |_var, &point, &origin| (origin, point), + ); + + // origin_live_on_entry(origin, point) :- + // var_live_on_entry(var, point), + // use_of_var_derefs_origin(var, origin). + origin_live_on_entry.from_join( + &var_live_on_entry, + use_of_var_derefs_origin, + |_var, &point, &origin| (origin, point), + ); + + // var_live_on_entry(var, point1) :- + // var_live_on_entry(var, point2), + // cfg_edge(point1, point2), + // !var_defined(var, point1). + var_live_on_entry.from_leapjoin( + &var_live_on_entry, + ( + var_defined_at.extend_anti(|&(var, _point2)| var), + cfg_edge_reverse.extend_with(|&(_var, point2)| point2), + ), + |&(var, _point2), &point1| (var, point1), + ); + + // var_drop_live_on_entry(Var, SourceNode) :- + // var_drop_live_on_entry(Var, TargetNode), + // cfg_edge(SourceNode, TargetNode), + // !var_defined_at(Var, SourceNode). + // // var_maybe_partly_initialized_on_exit(Var, SourceNode). + var_drop_live_on_entry.from_leapjoin( + &var_drop_live_on_entry, + ( + var_defined_at.extend_anti(|&(var, _target_node)| var), + cfg_edge_reverse.extend_with(|&(_var, target_node)| target_node), + ), + |&(var, _targetnode), &source_node| (var, source_node), + ); + } + + // Universal regions are live at all points + let mut origin_live_on_entry = origin_live_on_entry.complete().elements; + origin_live_on_entry.reserve(cfg_node.len() * universal_region.len()); + for &(o,) in universal_region.iter() { + for &(n,) in cfg_node.iter() { + origin_live_on_entry.push((o, n)); + } + } + + dump.var(&var_live_on_entry); + + Relation::from_vec(origin_live_on_entry).into() + } +} diff --git a/polonius-engine/src/output/location_insensitive.rs b/polonius-engine/src/compute/location_insensitive.rs similarity index 56% rename from polonius-engine/src/output/location_insensitive.rs rename to polonius-engine/src/compute/location_insensitive.rs index 83ce27757d..07f5816d91 100644 --- a/polonius-engine/src/output/location_insensitive.rs +++ b/polonius-engine/src/compute/location_insensitive.rs @@ -1,40 +1,51 @@ -// Copyright 2017 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - +use super::BorrowckErrors; +use crate::{Computation, Dump, FactTypes}; use datafrog::{Iteration, Relation, RelationLeaper}; -use std::time::Instant; - -use crate::facts::FactTypes; -use crate::output::{Context, Output}; - -pub(super) fn compute( - ctx: &Context<'_, T>, - result: &mut Output, -) -> ( - Relation<(T::Loan, T::Point)>, - Relation<(T::Origin, T::Origin)>, -) { - let timer = Instant::now(); - - let (potential_errors, potential_subset_errors) = { - // Static inputs - let origin_live_on_entry = &ctx.origin_live_on_entry; - let loan_invalidated_at = &ctx.loan_invalidated_at; - let placeholder_origin = &ctx.placeholder_origin; - let placeholder_loan = &ctx.placeholder_loan; - let known_contains = &ctx.known_contains; + +input! { + BorrowckLocationInsensitiveInput { + origin_live_on_entry, + loan_invalidated_at, + known_placeholder_requires, + placeholder, + loan_issued_at, + subset_base, + } +} + +output! { + BorrowckLocationInsensitiveErrors { + potential_errors, + potential_subset_errors, + } +} + +#[derive(Clone, Copy)] +pub struct BorrowckLocationInsensitive; + +impl Computation for BorrowckLocationInsensitive { + type Input<'db> = BorrowckLocationInsensitiveInput<'db, T>; + type Output = BorrowckLocationInsensitiveErrors; + + fn compute(&self, input: Self::Input<'_>, dump: &mut Dump) -> Self::Output { + let BorrowckLocationInsensitiveInput { + origin_live_on_entry, + loan_invalidated_at, + loan_issued_at, + placeholder: placeholder_loan, + known_placeholder_requires: known_contains, + subset_base, + } = input; + + let placeholder_loan_lo: Relation<_> = + placeholder_loan.iter().map(|&(o, l)| (l, o)).collect(); + let placeholder_origin: Relation<_> = + placeholder_loan.iter().map(|&(o, _l)| (o, ())).collect(); // subset(Origin1, Origin2) :- // subset_base(Origin1, Origin2, _). let subset = Relation::from_iter( - ctx.subset_base + subset_base .iter() .map(|&(origin1, origin2, _point)| (origin1, origin2)), ); @@ -55,18 +66,14 @@ pub(super) fn compute( // origin_contains_loan_on_entry(Origin, Loan) :- // loan_issued_at(Origin, Loan, _). origin_contains_loan_on_entry.extend( - ctx.loan_issued_at + loan_issued_at .iter() .map(|&(origin, loan, _point)| (origin, loan)), ); // origin_contains_loan_on_entry(Origin, Loan) :- // placeholder_loan(Origin, Loan). - origin_contains_loan_on_entry.extend( - placeholder_loan - .iter() - .map(|&(loan, origin)| (origin, loan)), - ); + origin_contains_loan_on_entry.extend(placeholder_loan.iter().copied()); // .. and then start iterating rules! while iteration.changed() { @@ -112,7 +119,7 @@ pub(super) fn compute( ( known_contains.filter_anti(|&(origin2, loan1)| (origin2, loan1)), placeholder_origin.filter_with(|&(origin2, _loan1)| (origin2, ())), - placeholder_loan.extend_with(|&(_origin2, loan1)| loan1), + placeholder_loan_lo.extend_with(|&(_origin2, loan1)| loan1), // remove symmetries: datafrog::ValueFilter::from(|&(origin2, _loan1), &origin1| origin2 != origin1), ), @@ -120,37 +127,43 @@ pub(super) fn compute( ); } - if result.dump_enabled { - for &(origin1, origin2) in subset.iter() { - result - .subset_anywhere - .entry(origin1) - .or_default() - .insert(origin2); - } - - let origin_contains_loan_on_entry = origin_contains_loan_on_entry.complete(); - for &(origin, loan) in origin_contains_loan_on_entry.iter() { - result - .origin_contains_loan_anywhere - .entry(origin) - .or_default() - .insert(loan); - } + dump.var(&origin_contains_loan_on_entry); + dump.rel("subset", subset); + + Self::Output { + potential_errors: potential_errors.complete(), + potential_subset_errors: potential_subset_errors.complete(), } + } +} - ( - potential_errors.complete(), - potential_subset_errors.complete(), - ) - }; +/// Copies `potential_errors` and `potential_subset_errors` into `errors` and `subset_errors` +/// respectively. +/// +/// This is a hack to conform to the old `Output` interface. It will cause a panic if run +/// alongside any other location-sensitive borrow-checking one, since the results may not match. +#[derive(Clone, Copy)] +pub struct BorrowckLocationInsensitiveAsSensitive; + +input! { + BorrowckLocationInsensitiveErrorsRef { + potential_errors, + potential_subset_errors, + } +} - info!( - "analysis done: {} `potential_errors` tuples, {} `potential_subset_errors` tuples, {:?}", - potential_errors.len(), - potential_subset_errors.len(), - timer.elapsed() - ); +impl Computation for BorrowckLocationInsensitiveAsSensitive { + type Input<'db> = BorrowckLocationInsensitiveErrorsRef<'db, T>; + type Output = BorrowckErrors; - (potential_errors, potential_subset_errors) + fn compute(&self, input: Self::Input<'_>, _dump: &mut Dump<'_>) -> Self::Output { + BorrowckErrors { + errors: input.potential_errors.clone(), + subset_errors: input + .potential_subset_errors + .iter() + .map(|&(o1, o2)| (o1, o2, 0.into())) + .collect(), + } + } } diff --git a/polonius-engine/src/compute/mod.rs b/polonius-engine/src/compute/mod.rs new file mode 100644 index 0000000000..afe3978939 --- /dev/null +++ b/polonius-engine/src/compute/mod.rs @@ -0,0 +1,59 @@ +//! The available Polonius computations. +//! +//! To run, combine them into a valid [`Pipeline`](crate::Pipeline). + +use crate::{Dump, FactTypes}; + +/// A Polonius computation. Given an input, computes some output. +pub trait Computation { + /// The inputs required by a computation. + /// + /// Must implement [`db::LoadFrom`](crate::db::LoadFrom). + type Input<'db>; + + /// The outputs generated by a computation. + /// + /// Must implement [`db::StoreTo`](crate::db::StoreTo). + type Output; + + /// Performs the computation, returning the specified outputs. + fn compute(&self, input: Self::Input<'_>, dump: &mut Dump<'_>) -> Self::Output; +} + +input! { + BorrowckInput { + cfg_edge, + origin_live_on_entry, + loan_invalidated_at, + subset_base, + loan_issued_at, + loan_killed_at, + placeholder, + known_placeholder_subset, + } +} + +output! { + BorrowckErrors { + errors, + subset_errors, + } +} + +mod cfg; +mod initialization; +mod liveness; +mod location_insensitive; +mod naive; +mod optimized; +mod placeholder; + +pub use self::cfg::Cfg; +pub use self::initialization::{MaybeInit, MaybeUninit, MoveError, Paths, VarDroppedWhileInit}; +pub use self::liveness::LiveOrigins; +pub use self::location_insensitive::{ + BorrowckLocationInsensitive, BorrowckLocationInsensitiveAsSensitive, +}; +pub use self::naive::BorrowckNaive; +pub use self::optimized::BorrowckOptimized; +pub use self::placeholder::{KnownPlaceholder, KnownPlaceholderLoans}; diff --git a/polonius-engine/src/output/naive.rs b/polonius-engine/src/compute/naive.rs similarity index 81% rename from polonius-engine/src/output/naive.rs rename to polonius-engine/src/compute/naive.rs index aa42048673..5a8ea980a2 100644 --- a/polonius-engine/src/output/naive.rs +++ b/polonius-engine/src/compute/naive.rs @@ -11,27 +11,37 @@ //! A version of the Naive datalog analysis using Datafrog. use datafrog::{Iteration, Relation, RelationLeaper}; -use std::time::Instant; -use crate::facts::FactTypes; -use crate::output::{Context, Output}; +use super::{BorrowckErrors, BorrowckInput, Computation, Dump}; +use crate::FactTypes; -pub(super) fn compute( - ctx: &Context<'_, T>, - result: &mut Output, -) -> ( - Relation<(T::Loan, T::Point)>, - Relation<(T::Origin, T::Origin, T::Point)>, -) { - let timer = Instant::now(); +#[derive(Clone, Copy)] +pub struct BorrowckNaive; - let (errors, subset_errors) = { - // Static inputs - let origin_live_on_entry_rel = &ctx.origin_live_on_entry; - let cfg_edge = &ctx.cfg_edge; - let loan_killed_at = &ctx.loan_killed_at; - let known_placeholder_subset = &ctx.known_placeholder_subset; - let placeholder_origin = &ctx.placeholder_origin; +impl Computation for BorrowckNaive { + type Input<'db> = BorrowckInput<'db, T>; + type Output = BorrowckErrors; + + fn compute(&self, input: Self::Input<'_>, dump: &mut Dump<'_>) -> Self::Output { + let BorrowckInput { + cfg_edge, + origin_live_on_entry: origin_live_on_entry_rel, + loan_invalidated_at, + subset_base, + loan_issued_at, + loan_killed_at, + placeholder, + known_placeholder_subset, + } = input; + + let placeholder_origin: Relation<_> = placeholder.iter().map(|&(o, _l)| (o, ())).collect(); + + // `loan_invalidated_at` facts, stored ready for joins + let loan_invalidated_at = Relation::from_iter( + loan_invalidated_at + .iter() + .map(|&(loan, point)| ((loan, point), ())), + ); // Create a new iteration context, ... let mut iteration = Iteration::new(); @@ -42,13 +52,6 @@ pub(super) fn compute( iteration.variable::<(T::Origin, T::Loan, T::Point)>("origin_contains_loan_on_entry"); let loan_live_at = iteration.variable::<((T::Loan, T::Point), ())>("loan_live_at"); - // `loan_invalidated_at` facts, stored ready for joins - let loan_invalidated_at = Relation::from_iter( - ctx.loan_invalidated_at - .iter() - .map(|&(loan, point)| ((loan, point), ())), - ); - // different indices for `subset`. let subset_o1p = iteration.variable_indistinct("subset_o1p"); let subset_o2p = iteration.variable_indistinct("subset_o2p"); @@ -94,13 +97,13 @@ pub(super) fn compute( // // subset(Origin1, Origin2, Point) :- // subset_base(Origin1, Origin2, Point). - subset.extend(ctx.subset_base.iter()); + subset.extend(subset_base.iter()); // Rule 4: the issuing origins are the ones initially containing loans. // // origin_contains_loan_on_entry(Origin, Loan, Point) :- // loan_issued_at(Origin, Loan, Point). - origin_contains_loan_on_entry.extend(ctx.loan_issued_at.iter()); + origin_contains_loan_on_entry.extend(loan_issued_at.iter()); // .. and then start iterating rules! while iteration.changed() { @@ -247,53 +250,13 @@ pub(super) fn compute( ); } - // Handle verbose output data - if result.dump_enabled { - let subset = subset.complete(); - assert!( - subset - .iter() - .filter(|&(origin1, origin2, _)| origin1 == origin2) - .count() - == 0, - "unwanted subset symmetries" - ); - for &(origin1, origin2, location) in subset.iter() { - result - .subset - .entry(location) - .or_default() - .entry(origin1) - .or_default() - .insert(origin2); - } + dump.var(&subset); + dump.var(&origin_contains_loan_on_entry); + dump.var_mapped(&loan_live_at, |x| x.0); - let origin_contains_loan_on_entry = origin_contains_loan_on_entry.complete(); - for &(origin, loan, location) in origin_contains_loan_on_entry.iter() { - result - .origin_contains_loan_at - .entry(location) - .or_default() - .entry(origin) - .or_default() - .insert(loan); - } - - let loan_live_at = loan_live_at.complete(); - for &((loan, location), _) in loan_live_at.iter() { - result.loan_live_at.entry(location).or_default().push(loan); - } + BorrowckErrors { + errors: errors.complete(), + subset_errors: subset_errors.complete(), } - - (errors.complete(), subset_errors.complete()) - }; - - info!( - "analysis done: {} `errors` tuples, {} `subset_errors` tuples, {:?}", - errors.len(), - subset_errors.len(), - timer.elapsed() - ); - - (errors, subset_errors) + } } diff --git a/polonius-engine/src/output/datafrog_opt.rs b/polonius-engine/src/compute/optimized.rs similarity index 87% rename from polonius-engine/src/output/datafrog_opt.rs rename to polonius-engine/src/compute/optimized.rs index da9c343ccd..d973481a26 100644 --- a/polonius-engine/src/output/datafrog_opt.rs +++ b/polonius-engine/src/compute/optimized.rs @@ -9,34 +9,40 @@ // except according to those terms. use datafrog::{Iteration, Relation, RelationLeaper}; -use std::time::Instant; - -use crate::facts::FactTypes; -use crate::output::{Context, Output}; - -pub(super) fn compute( - ctx: &Context<'_, T>, - result: &mut Output, -) -> ( - Relation<(T::Loan, T::Point)>, - Relation<(T::Origin, T::Origin, T::Point)>, -) { - let timer = Instant::now(); - - let (errors, subset_errors) = { - // Static inputs - let origin_live_on_entry_rel = &ctx.origin_live_on_entry; - let cfg_edge_rel = &ctx.cfg_edge; - let loan_killed_at = &ctx.loan_killed_at; - let known_placeholder_subset = &ctx.known_placeholder_subset; - let placeholder_origin = &ctx.placeholder_origin; - // Create a new iteration context, ... - let mut iteration = Iteration::new(); +use super::{BorrowckErrors, BorrowckInput, Computation, Dump}; +use crate::FactTypes; + +#[derive(Clone, Copy)] +pub struct BorrowckOptimized; + +impl Computation for BorrowckOptimized { + type Input<'db> = BorrowckInput<'db, T>; + type Output = BorrowckErrors; + + fn compute(&self, input: Self::Input<'_>, dump: &mut Dump<'_>) -> Self::Output { + let BorrowckInput { + cfg_edge, + origin_live_on_entry: origin_live_on_entry_rel, + loan_invalidated_at, + subset_base, + loan_issued_at, + loan_killed_at, + placeholder, + known_placeholder_subset, + } = input; + + let placeholder_origin: Relation<_> = placeholder.iter().map(|&(o, _l)| (o, ())).collect(); // `loan_invalidated_at` facts, stored ready for joins - let loan_invalidated_at = - iteration.variable::<((T::Loan, T::Point), ())>("loan_invalidated_at"); + let loan_invalidated_at = Relation::from_iter( + loan_invalidated_at + .iter() + .map(|&(loan, point)| ((loan, point), ())), + ); + + // Create a new iteration context, ... + let mut iteration = Iteration::new(); // we need `origin_live_on_entry` in both variable and relation forms, // (respectively, for join and antijoin). @@ -140,15 +146,10 @@ pub(super) fn compute( // Make "variable" versions of the relations, needed for joins. loan_issued_at_op.extend( - ctx.loan_issued_at + loan_issued_at .iter() .map(|&(origin, loan, point)| ((origin, point), loan)), ); - loan_invalidated_at.extend( - ctx.loan_invalidated_at - .iter() - .map(|&(loan, point)| ((loan, point), ())), - ); origin_live_on_entry_var.extend( origin_live_on_entry_rel .iter() @@ -158,7 +159,7 @@ pub(super) fn compute( // subset(origin1, origin2, point) :- // subset_base(origin1, origin2, point). subset_o1p.extend( - ctx.subset_base + subset_base .iter() .map(|&(origin1, origin2, point)| ((origin1, point), origin2)), ); @@ -166,7 +167,7 @@ pub(super) fn compute( // origin_contains_loan_on_entry(origin, loan, point) :- // loan_issued_at(origin, loan, point). origin_contains_loan_on_entry_op.extend( - ctx.loan_issued_at + loan_issued_at .iter() .map(|&(origin, loan, point)| ((origin, point), loan)), ); @@ -204,7 +205,7 @@ pub(super) fn compute( live_to_dying_regions_o2pq.from_leapjoin( &subset_o1p, ( - cfg_edge_rel.extend_with(|&((_, point1), _)| point1), + cfg_edge.extend_with(|&((_, point1), _)| point1), origin_live_on_entry_rel.extend_with(|&((origin1, _), _)| origin1), origin_live_on_entry_rel.extend_anti(|&((_, _), origin2)| origin2), ), @@ -220,7 +221,7 @@ pub(super) fn compute( &origin_contains_loan_on_entry_op, ( loan_killed_at.filter_anti(|&((_, point1), loan)| (loan, point1)), - cfg_edge_rel.extend_with(|&((_, point1), _)| point1), + cfg_edge.extend_with(|&((_, point1), _)| point1), origin_live_on_entry_rel.extend_anti(|&((origin, _), _)| origin), ), |&((origin, point1), loan), &point2| ((origin, point1, point2), loan), @@ -290,7 +291,7 @@ pub(super) fn compute( subset_o1p.from_leapjoin( &subset_o1p, ( - cfg_edge_rel.extend_with(|&((_, point1), _)| point1), + cfg_edge.extend_with(|&((_, point1), _)| point1), origin_live_on_entry_rel.extend_with(|&((origin1, _), _)| origin1), origin_live_on_entry_rel.extend_with(|&((_, _), origin2)| origin2), ), @@ -330,7 +331,7 @@ pub(super) fn compute( &origin_contains_loan_on_entry_op, ( loan_killed_at.filter_anti(|&((_, point1), loan)| (loan, point1)), - cfg_edge_rel.extend_with(|&((_, point1), _)| point1), + cfg_edge.extend_with(|&((_, point1), _)| point1), origin_live_on_entry_rel.extend_with(|&((origin, _), _)| origin), ), |&((origin, _), loan), &point2| ((origin, point2), loan), @@ -393,8 +394,8 @@ pub(super) fn compute( // loan_invalidated_at(loan, point), // loan_live_at(loan, point). errors.from_join( - &loan_invalidated_at, &loan_live_at, + &loan_invalidated_at, |&(loan, point), _, _| (loan, point), ); @@ -444,52 +445,17 @@ pub(super) fn compute( ); } - if result.dump_enabled { - let subset_o1p = subset_o1p.complete(); - assert!( - subset_o1p - .iter() - .filter(|&((origin1, _), origin2)| origin1 == origin2) - .count() - == 0, - "unwanted subset symmetries" - ); - for &((origin1, location), origin2) in subset_o1p.iter() { - result - .subset - .entry(location) - .or_default() - .entry(origin1) - .or_default() - .insert(origin2); - } - - let origin_contains_loan_on_entry_op = origin_contains_loan_on_entry_op.complete(); - for &((origin, location), loan) in origin_contains_loan_on_entry_op.iter() { - result - .origin_contains_loan_at - .entry(location) - .or_default() - .entry(origin) - .or_default() - .insert(loan); - } - - let loan_live_at = loan_live_at.complete(); - for &((loan, location), _) in loan_live_at.iter() { - result.loan_live_at.entry(location).or_default().push(loan); - } - } - - (errors.complete(), subset_errors.complete()) - }; - - info!( - "analysis done: {} `errors` tuples, {} `subset_errors` tuples, {:?}", - errors.len(), - subset_errors.len(), - timer.elapsed() - ); + dump.var_mapped(&loan_live_at, |x| x.0); + dump.var_named_mapped("subset", &subset_o1p, |&((o1, p), o2)| (o1, o2, p)); + dump.var_named_mapped( + "origin_contains_loan_on_entry", + &origin_contains_loan_on_entry_op, + |&((o, p), l)| (o, l, p), + ); - (errors, subset_errors) + BorrowckErrors { + errors: errors.complete(), + subset_errors: subset_errors.complete(), + } + } } diff --git a/polonius-engine/src/compute/placeholder.rs b/polonius-engine/src/compute/placeholder.rs new file mode 100644 index 0000000000..a6502d152a --- /dev/null +++ b/polonius-engine/src/compute/placeholder.rs @@ -0,0 +1,89 @@ +use datafrog::{Iteration, RelationLeaper}; + +use super::{Computation, Dump}; +use crate::FactTypes; + +#[derive(Clone, Copy)] +pub struct KnownPlaceholder; + +input! { + KnownPlaceholderSubsetBase { known_placeholder_subset_base } +} + +output!(known_placeholder_subset); + +impl Computation for KnownPlaceholder { + type Input<'db> = KnownPlaceholderSubsetBase<'db, T>; + type Output = KnownPlaceholderSubset; + + fn compute(&self, input: Self::Input<'_>, _dump: &mut Dump<'_>) -> Self::Output { + let KnownPlaceholderSubsetBase { + known_placeholder_subset_base, + } = input; + + let mut iteration = Iteration::new(); + + let known_placeholder_subset = iteration.variable("known_placeholder_subset"); + + // known_placeholder_subset(Origin1, Origin2) :- + // known_placeholder_subset_base(Origin1, Origin2). + known_placeholder_subset.extend(known_placeholder_subset_base.iter()); + + while iteration.changed() { + // known_placeholder_subset(Origin1, Origin3) :- + // known_placeholder_subset(Origin1, Origin2), + // known_placeholder_subset_base(Origin2, Origin3). + known_placeholder_subset.from_leapjoin( + &known_placeholder_subset, + known_placeholder_subset_base.extend_with(|&(_origin1, origin2)| origin2), + |&(origin1, _origin2), &origin3| (origin1, origin3), + ); + } + + known_placeholder_subset.complete().into() + } +} + +#[derive(Clone, Copy)] +pub struct KnownPlaceholderLoans; + +input! { + KnownPlaceholderRequiresInput { + known_placeholder_subset, + placeholder, + } +} + +output!(known_placeholder_requires); + +impl Computation for KnownPlaceholderLoans { + type Input<'db> = KnownPlaceholderRequiresInput<'db, T>; + type Output = KnownPlaceholderRequires; + + fn compute(&self, input: Self::Input<'_>, _dump: &mut Dump<'_>) -> Self::Output { + let KnownPlaceholderRequiresInput { + known_placeholder_subset, + placeholder, + } = input; + + let mut iteration = datafrog::Iteration::new(); + let known_contains = iteration.variable("known_contains"); + + // known_contains(Origin1, Loan1) :- + // placeholder(Origin1, Loan1). + known_contains.extend(placeholder.iter()); + + while iteration.changed() { + // known_contains(Origin2, Loan1) :- + // known_contains(Origin1, Loan1), + // known_placeholder_subset(Origin1, Origin2). + known_contains.from_join( + &known_contains, + known_placeholder_subset, + |&_origin1, &loan1, &origin2| (origin2, loan1), + ); + } + + known_contains.complete().into() + } +} diff --git a/polonius-engine/src/db/io.rs b/polonius-engine/src/db/io.rs new file mode 100644 index 0000000000..d12c32b561 --- /dev/null +++ b/polonius-engine/src/db/io.rs @@ -0,0 +1,104 @@ +use std::fmt::Debug; + +use crate::tuples::Tuple; +use crate::{Db, Dump, FactTypes, Rels}; + +/// Types that can be loaded from a [`Db`]. +pub trait LoadFrom<'db, T: FactTypes> { + /// The names of all relations contained in this type. + const RELATIONS: Rels; + + fn load_from_db(facts: &'db Db) -> Self; +} + +/// Types that can be stored to a [`Db`]. +pub trait StoreTo { + /// The names of all relations contained in this type. + const RELATIONS: Rels; + + fn store_to_db(self, facts: &mut Db, dump: &mut Dump<'_>); +} + +/// Define a `struct` that references a subset of the fields in `Db`. +#[macro_export] +macro_rules! input { + ($Name:ident { $( $r:ident ),* $(,)* }) => {paste::paste!{ + pub struct $Name<'db, T: $crate::FactTypes> { + $( pub $r: &'db datafrog::Relation<$crate::db::[<$r:camel>]> ),* + } + + impl<'db, T: $crate::FactTypes> $crate::LoadFrom<'db, T> for $Name<'db, T> { + const RELATIONS: $crate::Rels = &[$(stringify!($r)),*]; + + fn load_from_db(db: &'db $crate::Db) -> Self { + Self { + $( $r: &db.$r.as_ref().expect("Missing input"), )* + } + } + } + }}; +} + +/// Define a `struct` that owns a subset of the fields in `Db`. +#[macro_export] +macro_rules! output { + // Single relation outputs are common + ($r:ident) => {paste::paste! { + output!([<$r:camel>] { $r }); + + impl From]>> for [<$r:camel>] { + fn from(r: datafrog::Relation<$crate::db::[<$r:camel>]>) -> Self { + Self { + $r: r, + } + } + } + }}; + + ($Name:ident { + $( $r:ident ),* $(,)? + }) => {paste::paste!{ + pub struct $Name { + $( pub $r: datafrog::Relation<$crate::db::[<$r:camel>]>, )* + } + + impl $crate::StoreTo for $Name { + const RELATIONS: $crate::Rels = &[$(stringify!($r)),*]; + + fn store_to_db(self, db: &mut $crate::Db, dump: &mut $crate::Dump<'_>) { + use crate::internal::store_to_db_field; + let curr_unit = db.curr_unit; + $( store_to_db_field(stringify!($r), curr_unit, dump, &mut db.$r, self.$r); )* + } + } + }}; +} + +/// Saves a computed relation to the `Db`. +/// +/// This is publicly exported because it is an implementation detail of the `output` macro. +/// It is not subject to stability guarantees. +#[doc(hidden)] +pub fn store_to_db_field( + name: &'static str, + curr_unit: &'static str, + dump: &mut Dump<'_>, + opt: &mut Option>, + val: datafrog::Relation, +) { + match opt { + Some(old) => { + pretty_assertions::assert_eq!( + old, + &val, + "`{}` computed by `{}` differed from the existing", + name, + curr_unit + ); + } + None => { + dump.rel_ref(name, &val); + *opt = Some(val); + } + } +} diff --git a/polonius-engine/src/db/mod.rs b/polonius-engine/src/db/mod.rs new file mode 100644 index 0000000000..e63f044f9b --- /dev/null +++ b/polonius-engine/src/db/mod.rs @@ -0,0 +1,154 @@ +#[macro_use] +pub(crate) mod io; + +pub use self::io::{LoadFrom, StoreTo}; + +use datafrog::Relation; +use paste::paste; + +use crate::FactTypes; + +macro_rules! relations { + ( $( $(#[$meta:meta])* $name:ident : [$($Ty:ident),+ $(,)?] ),* $(,)? ) => { + /// The "facts" which are the basis of the NLL borrow analysis. + #[derive(Clone)] + #[non_exhaustive] + pub struct Db { + /// The name of the computation unit that is currently executing. + /// + /// Used to print better messages results differ for a single relation. + pub(crate) curr_unit: &'static str, + + $( $(#[$meta])* pub $name: Option>, )* + } + + impl Default for Db { + fn default() -> Self { + Self { + curr_unit: Default::default(), + $( $name: Default::default(), )* + } + } + } + + paste!{ $( + #[allow(unused)] + pub type [<$name:camel>] = ($(::$Ty,)+); + )* } + } +} + +relations! { + /// `loan_issued_at(origin, loan, point)` indicates that the `loan` was "issued" + /// at the given `point`, creating a reference with the `origin`. + /// Effectively, `origin` may refer to data from `loan` starting at `point` (this is usually + /// the point *after* a borrow rvalue). + loan_issued_at: [Origin, Loan, Point], + + /// `universal_region(origin)` -- this is a "free region" within fn body + universal_region: [Origin], + + /// `cfg_edge(point1, point2)` for each edge `point1 -> point2` in the control flow + cfg_edge: [Point, Point], + + /// `cfg_node(point)` for each node that appears as the source *or* target of an edge in the + /// control flow graph. + cfg_node: [Point], + + /// `loan_killed_at(loan, point)` when some prefix of the path borrowed at `loan` + /// is assigned at `point`. + /// Indicates that the path borrowed by the `loan` has changed in some way that the loan no + /// longer needs to be tracked. (In particular, mutations to the path that was borrowed + /// no longer invalidate the loan) + loan_killed_at: [Loan, Point], + + /// `subset_base(origin1, origin2, point)` when we require `origin1@point: origin2@point`. + /// Indicates that `origin1 <= origin2` -- i.e., the set of loans in `origin1` are a subset + /// of those in `origin2`. + subset_base: [Origin, Origin, Point], + + /// `loan_invalidated_at(loan, point)` indicates that the `loan` is invalidated by some action + /// taking place at `point`; if any origin that references this loan is live, this is an error. + loan_invalidated_at: [Loan, Point], + + /// `var_used_at(var, point)` when the variable `var` is used for anything + /// but a drop at `point` + var_used_at: [Variable, Point], + + /// `var_defined_at(var, point)` when the variable `var` is overwritten at `point` + var_defined_at: [Variable, Point], + + /// `var_dropped_at(var, point)` when the variable `var` is used in a drop at `point` + var_dropped_at: [Variable, Point], + + /// `var_dropped_while_init_at(var, point)` when the variable `var` is used in a drop at + /// `point` *while it is (maybe) initialized*. + /// + /// Drops of variables that are known to be uninit are no-ops, and are ignored by borrowck. + var_dropped_while_init_at: [Variable, Point], + + /// `use_of_var_derefs_origin(variable, origin)`: References with the given + /// `origin` may be dereferenced when the `variable` is used. + /// + /// In rustc, we generate this whenever the type of the variable includes the + /// given origin. + use_of_var_derefs_origin: [Variable, Origin], + + /// `drop_of_var_derefs_origin(var, origin)` when the type of `var` includes + /// the `origin` and uses it when dropping + drop_of_var_derefs_origin: [Variable, Origin], + + /// `child_path(child, parent)` when the path `child` is the direct child of + /// `parent`, e.g. `child_path(x.y, x)`, but not `child_path(x.y.z, x)`. + child_path: [Path, Path], + + /// `path_is_var(path, var)` the root path `path` starting in variable `var`. + path_is_var: [Path, Variable], + + /// `path_assigned_at_base(path, point)` when the `path` was initialized at point + /// `point`. This fact is only emitted for a prefix `path`, and not for the + /// implicit initialization of all of `path`'s children. E.g. a statement like + /// `x.y = 3` at `point` would give the fact `path_assigned_at_base(x.y, point)` (but + /// neither `path_assigned_at_base(x.y.z, point)` nor `path_assigned_at_base(x, point)`). + path_assigned_at_base: [Path, Point], + + /// `path_moved_at_base(path, point)` when the `path` was moved at `point`. The + /// same logic is applied as for `path_assigned_at_base` above. + path_moved_at_base: [Path, Point], + + /// `path_accessed_at_base(path, point)` when the `path` was accessed at point + /// `point`. The same logic as for `path_assigned_at_base` and `path_moved_at_base` applies. + path_accessed_at_base: [Path, Point], + + /// These reflect the `'a: 'b` relations that are either declared by the user on function + /// declarations or which are inferred via implied bounds. + /// For example: `fn foo<'a, 'b: 'a, 'c>(x: &'c &'a u32)` would have two entries: + /// - one for the user-supplied subset `'b: 'a` + /// - and one for the `'a: 'c` implied bound from the `x` parameter, + /// (note that the transitive relation `'b: 'c` is not necessarily included + /// explicitly, but rather inferred by polonius). + known_placeholder_subset: [Origin, Origin], + known_placeholder_subset_base: [Origin, Origin], + + /// `placeholder(origin, loan)` describes a placeholder `origin`, with its associated + /// placeholder `loan`. + placeholder: [Origin, Loan], + + path_assigned_at: [Path, Point], + path_moved_at: [Path, Point], + path_accessed_at: [Path, Point], + path_begins_with_var: [Path, Variable], + + origin_live_on_entry: [Origin, Point], + path_maybe_initialized_on_exit: [Path, Point], + path_maybe_uninitialized_on_exit: [Path, Point], + + errors: [Loan, Point], + subset_errors: [Origin, Origin, Point], + move_errors: [Path, Point], + + known_placeholder_requires: [Origin, Loan], + + potential_errors: [Loan, Point], + potential_subset_errors: [Origin, Origin], +} diff --git a/polonius-engine/src/dump.rs b/polonius-engine/src/dump.rs new file mode 100644 index 0000000000..b7c924e165 --- /dev/null +++ b/polonius-engine/src/dump.rs @@ -0,0 +1,193 @@ +//! Inspect the intermediate results of a series of [`Compuation`](crate::Computation)s. + +use contracts::*; + +use crate::tuples::{Tuple, TupleIter, TupleVec}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RelationId { + relation: String, + unit: String, +} + +impl RelationId { + /// Returns of the id of the unit that generated this data. + pub fn unit_name(&self) -> &str { + &self.unit + } + + /// Returns a string identifying this data. + pub fn relation_name(&self) -> &str { + &self.relation + } +} + +/// A proxy object that allows [`Computation`](crate::Computation)s to expose intermediate results +/// for the purpose of debugging. +pub struct Dump<'a> { + dumpers: Vec<&'a mut dyn Dumper>, + curr_unit: String, +} + +impl<'a> Dump<'a> { + /// Returns a `Dump` object that will forward data to each [`Dumper`] in the list. + pub fn new(dumpers: Vec<&'a mut dyn Dumper>) -> Self { + Self { + dumpers, + curr_unit: "[input]".to_owned(), + } + } + + /// Marks the start of a computation. + pub(crate) fn unit_start(&mut self, name: &str) { + self.curr_unit = name.to_owned(); + } + + /// Marks the end of a computation. + pub(crate) fn unit_end(&mut self, _name: &str) {} + + fn id(&self, name: impl Into) -> RelationId { + let unit = self.curr_unit.clone(); + RelationId { + unit, + relation: name.into(), + } + } + + /// Dumps the contents of the given variable. + #[requires(var.is_stable())] + pub fn var(&mut self, var: &datafrog::Variable) { + self.var_mapped(var, |x| *x) + } + + /// Dumps the contents of the given variable. + #[requires(var.is_stable())] + pub fn var_named(&mut self, name: impl Into, var: &datafrog::Variable) { + self.var_named_mapped(name, var, |x| *x) + } + + /// Dumps the contents of the given variable, but transformed with `f`. + /// + /// Useful when the variable is in an unusual order for joins. + #[requires(var.is_stable())] + pub fn var_mapped(&mut self, var: &datafrog::Variable, f: fn(&T) -> U) { + self.var_named_mapped(var.name(), var, f) + } + + /// Dumps the contents of the given variable with the provided name, but transformed with `f`. + /// + /// Useful when the variable is in an unusual order for joins. + #[requires(var.is_stable())] + pub fn var_named_mapped( + &mut self, + name: impl Into, + var: &datafrog::Variable, + f: fn(&T) -> U, + ) { + let stable = var.stable.borrow(); + let tuples = stable.iter().flat_map(|x| x.iter().map(f)); + self.iter(name, tuples) + } + + /// Dumps the contents of the given relation. + pub fn rel_ref(&mut self, name: impl Into, tuples: &datafrog::Relation) { + self.vec_ref(name, &tuples.elements); + } + + /// Dumps the contents of the given relation, but takes ownership of that relation. + pub fn rel(&mut self, name: impl Into, tuples: datafrog::Relation) { + self.vec(name, tuples.elements); + } + + /// Dumps the contents of the given vector. + pub fn vec_ref(&mut self, name: impl Into, tuples: &Vec) { + let id = self.id(name); + for dumper in self.dumpers.iter_mut() { + dumper.dump_vec_ref(&id, tuples) + } + } + + /// Dumps the contents of the given vector, but takes ownership of that vector. + pub fn vec(&mut self, name: impl Into, tuples: Vec) { + let id = self.id(name); + + for dumper in self.dumpers.iter_mut().filter(|d| !d.prefer_vec_owned(&id)) { + dumper.dump_vec_ref(&id, &tuples) + } + + // Pass ownership of the `Vec` to the first dumper that wants it. + + let mut owned_dumpers = self.dumpers.iter_mut().filter(|d| d.prefer_vec_owned(&id)); + let first_owned = owned_dumpers.next(); + + for dumper in owned_dumpers { + dumper.dump_vec_ref(&id, &tuples); + } + + if let Some(first_owned) = first_owned { + first_owned.dump_vec_owned(&id, Box::new(tuples)); + } + } + + /// Dumps the contents of the given iterator. + pub fn iter<'it, T: Tuple>( + &mut self, + name: impl Into, + tuples: impl 'it + Clone + Iterator, + ) { + let id = self.id(name); + for dumper in self.dumpers.iter_mut() { + dumper.dump_iter(&id, Box::new(tuples.clone())) + } + } +} + +/// Introspect the relations generated by a series of computations. +pub trait Dumper { + /// Dumps the contents of the given [`TupleIter`]. + fn dump_iter(&mut self, id: &RelationId, tuples: Box + '_>); + + /// Returns `true` if this `Dumper` is more efficient when given ownership of `tuples`. + fn prefer_vec_owned(&self, _id: &RelationId) -> bool { + false + } + + /// Dumps the contents of the given [`TupleVec`]. + fn dump_vec_ref(&mut self, id: &RelationId, tuples: &(dyn TupleVec + 'static)) { + self.dump_iter(id, tuples.iter_tuples()) + } + + /// Dumps the contents of the given [`TupleVec`], but takes ownership of it. + fn dump_vec_owned(&mut self, id: &RelationId, tuples: Box) { + self.dump_iter(id, tuples.iter_tuples()) + } +} + +impl Dumper for &'_ mut D { + fn dump_iter(&mut self, id: &RelationId, tuples: Box + '_>) { + D::dump_iter(*self, id, tuples); + } + + fn prefer_vec_owned(&self, id: &RelationId) -> bool { + D::prefer_vec_owned(self, id) + } + + fn dump_vec_ref(&mut self, id: &RelationId, tuples: &(dyn TupleVec + 'static)) { + D::dump_vec_ref(*self, id, tuples); + } + + fn dump_vec_owned(&mut self, id: &RelationId, tuples: Box) { + D::dump_vec_owned(*self, id, tuples); + } +} + +/// Log the size of each intermediate relation. +#[derive(Debug)] +pub struct Counts; + +impl Dumper for Counts { + fn dump_iter(&mut self, id: &RelationId, tuples: Box + '_>) { + let count = tuples.count(); + info!("\t|{}.{}| = {}", id.unit_name(), id.relation_name(), count); + } +} diff --git a/polonius-engine/src/lib.rs b/polonius-engine/src/lib.rs index 0926be8f59..278e5918b6 100644 --- a/polonius-engine/src/lib.rs +++ b/polonius-engine/src/lib.rs @@ -1,16 +1,44 @@ -/// Contains the core of the Polonius borrow checking engine. -/// Input is fed in via AllFacts, and outputs are returned via Output -extern crate datafrog; +#![feature(const_type_id)] +#![feature(generic_associated_types)] + +#[macro_use] +mod util; #[macro_use] -extern crate log; -extern crate rustc_hash; +mod pipeline; +#[macro_use] +pub mod db; + +mod compat; +pub mod compute; +pub mod dump; +mod tuples; + +pub use self::compat::{Algorithm, AllFacts, Output}; +#[doc(inline)] +pub use self::compute::Computation; +pub use self::db::{Db, LoadFrom, StoreTo}; +pub use self::dump::{Dump, Dumper}; +pub use self::pipeline::{ComputationDyn, Pipeline}; +pub use self::tuples::{RawTuple, Tuple, TupleIter, TupleSchema, TupleVec}; +pub use self::tuples::{downcast_vec, downcast_iter}; + +type Rel = &'static str; +type Rels = &'static [Rel]; + +pub trait Atom: + From + Into + Copy + Clone + std::fmt::Debug + Eq + Ord + std::hash::Hash + 'static +{ + fn index(self) -> usize; +} -mod facts; -mod output; +pub trait FactTypes: Copy + Clone + std::fmt::Debug + 'static { + type Origin: Atom; + type Loan: Atom; + type Point: Atom; + type Variable: Atom; + type Path: Atom; +} -// Reexports of facts -pub use facts::AllFacts; -pub use facts::Atom; -pub use facts::FactTypes; -pub use output::Algorithm; -pub use output::Output; +pub mod internal { + pub use crate::db::io::store_to_db_field; +} diff --git a/polonius-engine/src/output/initialization.rs b/polonius-engine/src/output/initialization.rs deleted file mode 100644 index 30409d965a..0000000000 --- a/polonius-engine/src/output/initialization.rs +++ /dev/null @@ -1,284 +0,0 @@ -use std::time::Instant; - -use crate::facts::FactTypes; -use crate::output::{InitializationContext, Output}; - -use datafrog::{Iteration, Relation, RelationLeaper}; - -// This represents the output of an intermediate elaboration step (step 1). -struct TransitivePaths { - path_moved_at: Relation<(T::Path, T::Point)>, - path_assigned_at: Relation<(T::Path, T::Point)>, - path_accessed_at: Relation<(T::Path, T::Point)>, - path_begins_with_var: Relation<(T::Path, T::Variable)>, -} - -struct InitializationStatus { - var_maybe_partly_initialized_on_exit: Relation<(T::Variable, T::Point)>, - move_error: Relation<(T::Path, T::Point)>, -} - -pub(super) struct InitializationResult( - pub(super) Relation<(T::Variable, T::Point)>, - pub(super) Relation<(T::Path, T::Point)>, -); - -// Step 1: compute transitive closures of path operations. This would elaborate, -// for example, an access to x into an access to x.f, x.f.0, etc. We do this for: -// - access to a path -// - initialization of a path -// - moves of a path -// FIXME: transitive rooting in a variable (path_begins_with_var) -// Note that this step may not be entirely necessary! -fn compute_transitive_paths( - child_path: Vec<(T::Path, T::Path)>, - path_assigned_at_base: Vec<(T::Path, T::Point)>, - path_moved_at_base: Vec<(T::Path, T::Point)>, - path_accessed_at_base: Vec<(T::Path, T::Point)>, - path_is_var: Vec<(T::Path, T::Variable)>, -) -> TransitivePaths { - let mut iteration = Iteration::new(); - let child_path: Relation<(T::Path, T::Path)> = child_path.into(); - - let ancestor_path = iteration.variable::<(T::Path, T::Path)>("ancestor"); - - // These are the actual targets: - let path_moved_at = iteration.variable::<(T::Path, T::Point)>("path_moved_at"); - let path_assigned_at = iteration.variable::<(T::Path, T::Point)>("path_initialized_at"); - let path_accessed_at = iteration.variable::<(T::Path, T::Point)>("path_accessed_at"); - let path_begins_with_var = iteration.variable::<(T::Path, T::Variable)>("path_begins_with_var"); - - // ancestor_path(Parent, Child) :- child_path(Child, Parent). - ancestor_path.extend(child_path.iter().map(|&(child, parent)| (parent, child))); - - // path_moved_at(Path, Point) :- path_moved_at_base(Path, Point). - path_moved_at.insert(path_moved_at_base.into()); - - // path_assigned_at(Path, Point) :- path_assigned_at_base(Path, Point). - path_assigned_at.insert(path_assigned_at_base.into()); - - // path_accessed_at(Path, Point) :- path_accessed_at_base(Path, Point). - path_accessed_at.insert(path_accessed_at_base.into()); - - // path_begins_with_var(Path, Var) :- path_is_var(Path, Var). - path_begins_with_var.insert(path_is_var.into()); - - while iteration.changed() { - // ancestor_path(Grandparent, Child) :- - // ancestor_path(Parent, Child), - // child_path(Parent, Grandparent). - ancestor_path.from_join( - &ancestor_path, - &child_path, - |&_parent, &child, &grandparent| (grandparent, child), - ); - - // moving a path moves its children - // path_moved_at(Child, Point) :- - // path_moved_at(Parent, Point), - // ancestor_path(Parent, Child). - path_moved_at.from_join(&path_moved_at, &ancestor_path, |&_parent, &p, &child| { - (child, p) - }); - - // initialising x at p initialises all x:s children - // path_assigned_at(Child, point) :- - // path_assigned_at(Parent, point), - // ancestor_path(Parent, Child). - path_assigned_at.from_join(&path_assigned_at, &ancestor_path, |&_parent, &p, &child| { - (child, p) - }); - - // accessing x at p accesses all x:s children at p (actually, - // accesses should be maximally precise and this shouldn't happen?) - // path_accessed_at(Child, point) :- - // path_accessed_at(Parent, point), - // ancestor_path(Parent, Child). - path_accessed_at.from_join(&path_accessed_at, &ancestor_path, |&_parent, &p, &child| { - (child, p) - }); - - // path_begins_with_var(Child, Var) :- - // path_begins_with_var(Parent, Var) - // ancestor_path(Parent, Child). - path_begins_with_var.from_join( - &path_begins_with_var, - &ancestor_path, - |&_parent, &var, &child| (child, var), - ); - } - - TransitivePaths { - path_assigned_at: path_assigned_at.complete(), - path_moved_at: path_moved_at.complete(), - path_accessed_at: path_accessed_at.complete(), - path_begins_with_var: path_begins_with_var.complete(), - } -} - -// Step 2: Compute path initialization and deinitialization across the CFG. -fn compute_move_errors( - ctx: TransitivePaths, - cfg_edge: &Relation<(T::Point, T::Point)>, - output: &mut Output, -) -> InitializationStatus { - let mut iteration = Iteration::new(); - // Variables - - // var_maybe_partly_initialized_on_exit(var, point): Upon leaving `point`, - // `var` is partially initialized for some path through the CFG, that is - // there has been an initialization of var, and var has not been moved in - // all paths through the CFG. - let var_maybe_partly_initialized_on_exit = - iteration.variable::<(T::Variable, T::Point)>("var_maybe_partly_initialized_on_exit"); - - // path_maybe_initialized_on_exit(path, point): Upon leaving `point`, the - // move path `path` is initialized for some path through the CFG. - let path_maybe_initialized_on_exit = - iteration.variable::<(T::Path, T::Point)>("path_maybe_initialized_on_exit"); - - // path_maybe_uninitialized_on_exit(Path, Point): There exists at least one - // path through the CFG to Point such that `Path` has been moved out by the - // time we arrive at `Point` without it being re-initialized for sure. - let path_maybe_uninitialized_on_exit = - iteration.variable::<(T::Path, T::Point)>("path_maybe_uninitialized_on_exit"); - - // move_error(Path, Point): There is an access to `Path` at `Point`, but - // `Path` is potentially moved (or never initialised). - let move_error = iteration.variable::<(T::Path, T::Point)>("move_error"); - - // Initial propagation of static relations - - // path_maybe_initialized_on_exit(path, point) :- path_assigned_at(path, point). - path_maybe_initialized_on_exit.insert(ctx.path_assigned_at.clone()); - - // path_maybe_uninitialized_on_exit(path, point) :- path_moved_at(path, point). - path_maybe_uninitialized_on_exit.insert(ctx.path_moved_at.clone()); - - while iteration.changed() { - // path_maybe_initialized_on_exit(path, point2) :- - // path_maybe_initialized_on_exit(path, point1), - // cfg_edge(point1, point2), - // !path_moved_at(path, point2). - path_maybe_initialized_on_exit.from_leapjoin( - &path_maybe_initialized_on_exit, - ( - cfg_edge.extend_with(|&(_path, point1)| point1), - ctx.path_moved_at.extend_anti(|&(path, _point1)| path), - ), - |&(path, _point1), &point2| (path, point2), - ); - - // path_maybe_uninitialized_on_exit(path, point2) :- - // path_maybe_uninitialized_on_exit(path, point1), - // cfg_edge(point1, point2) - // !path_assigned_at(path, point2). - path_maybe_uninitialized_on_exit.from_leapjoin( - &path_maybe_uninitialized_on_exit, - ( - cfg_edge.extend_with(|&(_path, point1)| point1), - ctx.path_assigned_at.extend_anti(|&(path, _point1)| path), - ), - |&(path, _point1), &point2| (path, point2), - ); - - // var_maybe_partly_initialized_on_exit(var, point) :- - // path_maybe_initialized_on_exit(path, point). - // path_begins_with_var(path, var). - var_maybe_partly_initialized_on_exit.from_leapjoin( - &path_maybe_initialized_on_exit, - ctx.path_begins_with_var.extend_with(|&(path, _point)| path), - |&(_path, point), &var| (var, point), - ); - - // move_error(Path, TargetNode) :- - // path_maybe_uninitialized_on_exit(Path, SourceNode), - // cfg_edge(SourceNode, TargetNode), - // path_accessed_at(Path, TargetNode). - move_error.from_leapjoin( - &path_maybe_uninitialized_on_exit, - ( - cfg_edge.extend_with(|&(_path, source_node)| source_node), - ctx.path_accessed_at - .extend_with(|&(path, _source_node)| path), - ), - |&(path, _source_node), &target_node| (path, target_node), - ); - } - - if output.dump_enabled { - for &(path, location) in path_maybe_initialized_on_exit.complete().iter() { - output - .path_maybe_initialized_on_exit - .entry(location) - .or_default() - .push(path); - } - - for &(path, location) in path_maybe_uninitialized_on_exit.complete().iter() { - output - .path_maybe_uninitialized_on_exit - .entry(location) - .or_default() - .push(path); - } - } - - InitializationStatus { - var_maybe_partly_initialized_on_exit: var_maybe_partly_initialized_on_exit.complete(), - move_error: move_error.complete(), - } -} - -// Compute two things: -// -// - an over-approximation of the initialization of variables. This is used in -// the origin_live_on_entry computations to determine when a drop may happen; a -// definitely moved variable would not be actually dropped. -// - move errors. -// -// The process is split into two stages: -// -// 1. Compute the transitive closure of path accesses. That is, accessing `f.a` -// would access `f.a.b`, etc. -// 2. Use this to compute both paths that may be initialized and paths that may -// have been deinitialized, which in turn can be used to find move errors (an -// access to a path that may be deinitialized). -pub(super) fn compute( - ctx: InitializationContext, - cfg_edge: &Relation<(T::Point, T::Point)>, - output: &mut Output, -) -> InitializationResult { - let timer = Instant::now(); - - let transitive_paths = compute_transitive_paths::( - ctx.child_path, - ctx.path_assigned_at_base, - ctx.path_moved_at_base, - ctx.path_accessed_at_base, - ctx.path_is_var, - ); - info!("initialization phase 1 completed: {:?}", timer.elapsed()); - - let InitializationStatus { - var_maybe_partly_initialized_on_exit, - move_error, - } = compute_move_errors::(transitive_paths, cfg_edge, output); - info!( - "initialization phase 2: {} move errors in {:?}", - move_error.elements.len(), - timer.elapsed() - ); - - if output.dump_enabled { - for &(var, location) in var_maybe_partly_initialized_on_exit.iter() { - output - .var_maybe_partly_initialized_on_exit - .entry(location) - .or_default() - .push(var); - } - } - - InitializationResult(var_maybe_partly_initialized_on_exit, move_error) -} diff --git a/polonius-engine/src/output/liveness.rs b/polonius-engine/src/output/liveness.rs deleted file mode 100644 index 1b4b4ce53f..0000000000 --- a/polonius-engine/src/output/liveness.rs +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2019 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! An implementation of the origin liveness calculation logic - -use std::collections::BTreeSet; -use std::time::Instant; - -use crate::facts::FactTypes; -use crate::output::{LivenessContext, Output}; - -use datafrog::{Iteration, Relation, RelationLeaper}; - -pub(super) fn compute_live_origins( - ctx: LivenessContext, - cfg_edge: &Relation<(T::Point, T::Point)>, - var_maybe_partly_initialized_on_exit: Relation<(T::Variable, T::Point)>, - output: &mut Output, -) -> Vec<(T::Origin, T::Point)> { - let timer = Instant::now(); - let mut iteration = Iteration::new(); - - // Relations - let var_defined_at: Relation<(T::Variable, T::Point)> = ctx.var_defined_at.into(); - let cfg_edge_reverse: Relation<(T::Point, T::Point)> = cfg_edge - .iter() - .map(|&(point1, point2)| (point2, point1)) - .collect(); - let use_of_var_derefs_origin: Relation<(T::Variable, T::Origin)> = - ctx.use_of_var_derefs_origin.into(); - let drop_of_var_derefs_origin: Relation<(T::Variable, T::Origin)> = - ctx.drop_of_var_derefs_origin.into(); - let var_dropped_at: Relation<((T::Variable, T::Point), ())> = ctx - .var_dropped_at - .into_iter() - .map(|(var, point)| ((var, point), ())) - .collect(); - - // Variables - - // `var_live_on_entry`: variable `var` is live upon entry at `point` - let var_live_on_entry = iteration.variable::<(T::Variable, T::Point)>("var_live_on_entry"); - // `var_drop_live_on_entry`: variable `var` is drop-live (will be used for a drop) upon entry in `point` - let var_drop_live_on_entry = - iteration.variable::<(T::Variable, T::Point)>("var_drop_live_on_entry"); - - // This is what we are actually calculating: - let origin_live_on_entry = iteration.variable::<(T::Origin, T::Point)>("origin_live_on_entry"); - - // This propagates the relation `var_live_on_entry(var, point) :- var_used_at(var, point)`: - var_live_on_entry.insert(ctx.var_used_at.into()); - - // var_maybe_partly_initialized_on_entry(var, point2) :- - // var_maybe_partly_initialized_on_exit(var, point1), - // cfg_edge(point1, point2). - let var_maybe_partly_initialized_on_entry = Relation::from_leapjoin( - &var_maybe_partly_initialized_on_exit, - cfg_edge.extend_with(|&(_var, point1)| point1), - |&(var, _point1), &point2| ((var, point2), ()), - ); - - // var_drop_live_on_entry(var, point) :- - // var_dropped_at(var, point), - // var_maybe_partly_initialized_on_entry(var, point). - var_drop_live_on_entry.insert(Relation::from_join( - &var_dropped_at, - &var_maybe_partly_initialized_on_entry, - |&(var, point), _, _| (var, point), - )); - - while iteration.changed() { - // origin_live_on_entry(origin, point) :- - // var_drop_live_on_entry(var, point), - // drop_of_var_derefs_origin(var, origin). - origin_live_on_entry.from_join( - &var_drop_live_on_entry, - &drop_of_var_derefs_origin, - |_var, &point, &origin| (origin, point), - ); - - // origin_live_on_entry(origin, point) :- - // var_live_on_entry(var, point), - // use_of_var_derefs_origin(var, origin). - origin_live_on_entry.from_join( - &var_live_on_entry, - &use_of_var_derefs_origin, - |_var, &point, &origin| (origin, point), - ); - - // var_live_on_entry(var, point1) :- - // var_live_on_entry(var, point2), - // cfg_edge(point1, point2), - // !var_defined(var, point1). - var_live_on_entry.from_leapjoin( - &var_live_on_entry, - ( - var_defined_at.extend_anti(|&(var, _point2)| var), - cfg_edge_reverse.extend_with(|&(_var, point2)| point2), - ), - |&(var, _point2), &point1| (var, point1), - ); - - // var_drop_live_on_entry(Var, SourceNode) :- - // var_drop_live_on_entry(Var, TargetNode), - // cfg_edge(SourceNode, TargetNode), - // !var_defined_at(Var, SourceNode), - // var_maybe_partly_initialized_on_exit(Var, SourceNode). - var_drop_live_on_entry.from_leapjoin( - &var_drop_live_on_entry, - ( - var_defined_at.extend_anti(|&(var, _target_node)| var), - cfg_edge_reverse.extend_with(|&(_var, target_node)| target_node), - var_maybe_partly_initialized_on_exit.extend_with(|&(var, _target_node)| var), - ), - |&(var, _targetnode), &source_node| (var, source_node), - ); - } - - let origin_live_on_entry = origin_live_on_entry.complete(); - - info!( - "compute_live_origins() completed: {} tuples, {:?}", - origin_live_on_entry.len(), - timer.elapsed(), - ); - - if output.dump_enabled { - let var_drop_live_on_entry = var_drop_live_on_entry.complete(); - for &(var, location) in var_drop_live_on_entry.iter() { - output - .var_drop_live_on_entry - .entry(location) - .or_default() - .push(var); - } - - let var_live_on_entry = var_live_on_entry.complete(); - for &(var, location) in var_live_on_entry.iter() { - output - .var_live_on_entry - .entry(location) - .or_default() - .push(var); - } - } - - origin_live_on_entry.elements -} - -pub(super) fn make_universal_regions_live( - origin_live_on_entry: &mut Vec<(T::Origin, T::Point)>, - cfg_node: &BTreeSet, - universal_regions: &[T::Origin], -) { - debug!("make_universal_regions_live()"); - - origin_live_on_entry.reserve(universal_regions.len() * cfg_node.len()); - for &origin in universal_regions.iter() { - for &point in cfg_node.iter() { - origin_live_on_entry.push((origin, point)); - } - } -} diff --git a/polonius-engine/src/output/mod.rs b/polonius-engine/src/output/mod.rs deleted file mode 100644 index b840e4bec8..0000000000 --- a/polonius-engine/src/output/mod.rs +++ /dev/null @@ -1,614 +0,0 @@ -// Copyright 2017 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use datafrog::Relation; -use rustc_hash::{FxHashMap, FxHashSet}; -use std::borrow::Cow; -use std::collections::{BTreeMap, BTreeSet}; - -use crate::facts::{AllFacts, Atom, FactTypes}; - -mod datafrog_opt; -mod initialization; -mod liveness; -mod location_insensitive; -mod naive; - -#[derive(Debug, Clone, Copy)] -pub enum Algorithm { - /// Simple rules, but slower to execute - Naive, - - /// Optimized variant of the rules - DatafrogOpt, - - /// Fast to compute, but imprecise: there can be false-positives - /// but no false-negatives. Tailored for quick "early return" situations. - LocationInsensitive, - - /// Compares the `Naive` and `DatafrogOpt` variants to ensure they indeed - /// compute the same errors. - Compare, - - /// Combination of the fast `LocationInsensitive` pre-pass, followed by - /// the more expensive `DatafrogOpt` variant. - Hybrid, -} - -impl Algorithm { - /// Optimized variants that ought to be equivalent to "naive" - pub const OPTIMIZED: &'static [Algorithm] = &[Algorithm::DatafrogOpt]; - - pub fn variants() -> [&'static str; 5] { - [ - "Naive", - "DatafrogOpt", - "LocationInsensitive", - "Compare", - "Hybrid", - ] - } -} - -impl ::std::str::FromStr for Algorithm { - type Err = String; - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_ref() { - "naive" => Ok(Algorithm::Naive), - "datafrogopt" => Ok(Algorithm::DatafrogOpt), - "locationinsensitive" => Ok(Algorithm::LocationInsensitive), - "compare" => Ok(Algorithm::Compare), - "hybrid" => Ok(Algorithm::Hybrid), - _ => Err(String::from( - "valid values: Naive, DatafrogOpt, LocationInsensitive, Compare, Hybrid", - )), - } - } -} - -#[derive(Clone, Debug)] -pub struct Output { - pub errors: FxHashMap>, - pub subset_errors: FxHashMap>, - pub move_errors: FxHashMap>, - - pub dump_enabled: bool, - - // these are just for debugging - pub loan_live_at: FxHashMap>, - pub origin_contains_loan_at: FxHashMap>>, - pub origin_contains_loan_anywhere: FxHashMap>, - pub origin_live_on_entry: FxHashMap>, - pub loan_invalidated_at: FxHashMap>, - pub subset: FxHashMap>>, - pub subset_anywhere: FxHashMap>, - pub var_live_on_entry: FxHashMap>, - pub var_drop_live_on_entry: FxHashMap>, - pub path_maybe_initialized_on_exit: FxHashMap>, - pub path_maybe_uninitialized_on_exit: FxHashMap>, - pub known_contains: FxHashMap>, - pub var_maybe_partly_initialized_on_exit: FxHashMap>, -} - -/// Subset of `AllFacts` dedicated to initialization -struct InitializationContext { - child_path: Vec<(T::Path, T::Path)>, - path_is_var: Vec<(T::Path, T::Variable)>, - path_assigned_at_base: Vec<(T::Path, T::Point)>, - path_moved_at_base: Vec<(T::Path, T::Point)>, - path_accessed_at_base: Vec<(T::Path, T::Point)>, -} - -/// Subset of `AllFacts` dedicated to liveness -struct LivenessContext { - var_used_at: Vec<(T::Variable, T::Point)>, - var_defined_at: Vec<(T::Variable, T::Point)>, - var_dropped_at: Vec<(T::Variable, T::Point)>, - use_of_var_derefs_origin: Vec<(T::Variable, T::Origin)>, - drop_of_var_derefs_origin: Vec<(T::Variable, T::Origin)>, -} - -/// Subset of `AllFacts` dedicated to borrow checking, and data ready to use by the variants -struct Context<'ctx, T: FactTypes> { - // `Relation`s used as static inputs, by all variants - origin_live_on_entry: Relation<(T::Origin, T::Point)>, - loan_invalidated_at: Relation<(T::Loan, T::Point)>, - - // static inputs used via `Variable`s, by all variants - subset_base: &'ctx Vec<(T::Origin, T::Origin, T::Point)>, - loan_issued_at: &'ctx Vec<(T::Origin, T::Loan, T::Point)>, - - // static inputs used by variants other than `LocationInsensitive` - loan_killed_at: Relation<(T::Loan, T::Point)>, - known_contains: Relation<(T::Origin, T::Loan)>, - placeholder_origin: Relation<(T::Origin, ())>, - placeholder_loan: Relation<(T::Loan, T::Origin)>, - - // The `known_placeholder_subset` relation in the facts does not necessarily contain all the - // transitive subsets. The transitive closure is always needed, so this version here is fully - // closed over. - known_placeholder_subset: Relation<(T::Origin, T::Origin)>, - - // while this static input is unused by `LocationInsensitive`, it's depended on by - // initialization and liveness, so already computed by the time we get to borrowcking. - cfg_edge: Relation<(T::Point, T::Point)>, - - // Partial results possibly used by other variants as input. Not currently used yet. - #[allow(dead_code)] - potential_errors: Option>, - #[allow(dead_code)] - potential_subset_errors: Option>, -} - -impl Output { - /// All variants require the same initial preparations, done in multiple - /// successive steps: - /// - compute initialization data - /// - compute liveness - /// - prepare static inputs as shared `Relation`s - /// - in cases where `LocationInsensitive` variant is ran as a filtering pre-pass, - /// partial results can also be stored in the context, so that the following - /// variant can use it to prune its own input data - pub fn compute(all_facts: &AllFacts, algorithm: Algorithm, dump_enabled: bool) -> Self { - let mut result = Output::new(dump_enabled); - - // TODO: remove all the cloning thereafter, but that needs to be done in concert with rustc - - let cfg_edge = all_facts.cfg_edge.clone().into(); - - // 1) Initialization - let initialization_ctx = InitializationContext { - child_path: all_facts.child_path.clone(), - path_is_var: all_facts.path_is_var.clone(), - path_assigned_at_base: all_facts.path_assigned_at_base.clone(), - path_moved_at_base: all_facts.path_moved_at_base.clone(), - path_accessed_at_base: all_facts.path_accessed_at_base.clone(), - }; - - let initialization::InitializationResult::( - var_maybe_partly_initialized_on_exit, - move_errors, - ) = initialization::compute(initialization_ctx, &cfg_edge, &mut result); - - // FIXME: move errors should prevent the computation from continuing: we can't compute - // liveness and analyze loans accurately when there are move errors, and should early - // return here. - for &(path, location) in move_errors.iter() { - result.move_errors.entry(location).or_default().push(path); - } - - // 2) Liveness - let liveness_ctx = LivenessContext { - var_used_at: all_facts.var_used_at.clone(), - var_defined_at: all_facts.var_defined_at.clone(), - var_dropped_at: all_facts.var_dropped_at.clone(), - use_of_var_derefs_origin: all_facts.use_of_var_derefs_origin.clone(), - drop_of_var_derefs_origin: all_facts.drop_of_var_derefs_origin.clone(), - }; - - let mut origin_live_on_entry = liveness::compute_live_origins( - liveness_ctx, - &cfg_edge, - var_maybe_partly_initialized_on_exit, - &mut result, - ); - - let cfg_node = cfg_edge - .iter() - .map(|&(point1, _)| point1) - .chain(cfg_edge.iter().map(|&(_, point2)| point2)) - .collect(); - - liveness::make_universal_regions_live::( - &mut origin_live_on_entry, - &cfg_node, - &all_facts.universal_region, - ); - - // 3) Borrow checking - - // Prepare data as datafrog relations, ready to join. - // - // Note: if rustc and polonius had more interaction, we could also delay or avoid - // generating some of the facts that are now always present here. For example, - // the `LocationInsensitive` variant doesn't use the `loan_killed_at` relation, so we could - // technically delay computing and passing it from rustc, when using this or the `Hybrid` - // variants, to after the pre-pass has made sure we actually need to compute the full - // analysis. If these facts happened to be recorded in separate MIR walks, we might also - // avoid generating those facts. - - let origin_live_on_entry = origin_live_on_entry.into(); - - // TODO: also flip the order of this relation's arguments in rustc - // from `loan_invalidated_at(point, loan)` to `loan_invalidated_at(loan, point)`. - // to avoid this allocation. - let loan_invalidated_at = Relation::from_iter( - all_facts - .loan_invalidated_at - .iter() - .map(|&(point, loan)| (loan, point)), - ); - - let loan_killed_at = all_facts.loan_killed_at.clone().into(); - - // `known_placeholder_subset` is a list of all the `'a: 'b` subset relations the user gave: - // it's not required to be transitive. `known_contains` is its transitive closure: a list - // of all the known placeholder loans that each of these placeholder origins contains. - // Given the `known_placeholder_subset`s `'a: 'b` and `'b: 'c`: in the `known_contains` - // relation, `'a` will also contain `'c`'s placeholder loan. - let known_placeholder_subset = all_facts.known_placeholder_subset.clone().into(); - let known_contains = - Output::::compute_known_contains(&known_placeholder_subset, &all_facts.placeholder); - - // Fully close over the `known_placeholder_subset` relation. - let known_placeholder_subset = - Output::::compute_known_placeholder_subset(&known_placeholder_subset); - - let placeholder_origin: Relation<_> = Relation::from_iter( - all_facts - .universal_region - .iter() - .map(|&origin| (origin, ())), - ); - - let placeholder_loan = Relation::from_iter( - all_facts - .placeholder - .iter() - .map(|&(origin, loan)| (loan, origin)), - ); - - // Ask the variants to compute errors in their own way - let mut ctx = Context { - origin_live_on_entry, - loan_invalidated_at, - cfg_edge, - subset_base: &all_facts.subset_base, - loan_issued_at: &all_facts.loan_issued_at, - loan_killed_at, - known_contains, - known_placeholder_subset, - placeholder_origin, - placeholder_loan, - potential_errors: None, - potential_subset_errors: None, - }; - - let (errors, subset_errors) = match algorithm { - Algorithm::LocationInsensitive => { - let (potential_errors, potential_subset_errors) = - location_insensitive::compute(&ctx, &mut result); - - // Note: the error location is meaningless for a location-insensitive - // subset error analysis. This is acceptable here as this variant is not one - // which should be used directly besides debugging, the `Hybrid` variant will - // take advantage of its result. - let potential_subset_errors: Relation<(T::Origin, T::Origin, T::Point)> = - Relation::from_iter( - potential_subset_errors - .into_iter() - .map(|&(origin1, origin2)| (origin1, origin2, 0.into())), - ); - - (potential_errors, potential_subset_errors) - } - Algorithm::Naive => naive::compute(&ctx, &mut result), - Algorithm::DatafrogOpt => datafrog_opt::compute(&ctx, &mut result), - Algorithm::Hybrid => { - // Execute the fast `LocationInsensitive` computation as a pre-pass: - // if it finds no possible errors, we don't need to do the more complex - // computations as they won't find errors either, and we can return early. - let (potential_errors, potential_subset_errors) = - location_insensitive::compute(&ctx, &mut result); - - if potential_errors.is_empty() && potential_subset_errors.is_empty() { - // There are no loan errors, nor subset errors, we can early return - // empty errors lists and avoid doing the heavy analysis. - (potential_errors, Vec::new().into()) - } else { - // Record these potential errors as they can be used to limit the next - // variant's work to only these loans. - ctx.potential_errors = - Some(potential_errors.iter().map(|&(loan, _)| loan).collect()); - ctx.potential_subset_errors = Some(potential_subset_errors); - - datafrog_opt::compute(&ctx, &mut result) - } - } - Algorithm::Compare => { - // Ensure the `Naive` and `DatafrogOpt` errors are the same - let (naive_errors, naive_subset_errors) = naive::compute(&ctx, &mut result); - let (opt_errors, _) = datafrog_opt::compute(&ctx, &mut result); - - // TODO: compare illegal subset relations errors as well here ? - - let mut naive_errors_by_point = FxHashMap::default(); - for &(loan, point) in naive_errors.iter() { - naive_errors_by_point - .entry(point) - .or_insert_with(Vec::new) - .push(loan); - } - - let mut opt_errors_by_point = FxHashMap::default(); - for &(loan, point) in opt_errors.iter() { - opt_errors_by_point - .entry(point) - .or_insert_with(Vec::new) - .push(loan); - } - - if compare_errors(&naive_errors_by_point, &opt_errors_by_point) { - panic!(concat!( - "The errors reported by the naive algorithm differ from ", - "the errors reported by the optimized algorithm. ", - "See the error log for details." - )); - } else { - debug!("Naive and optimized algorithms reported the same errors."); - } - - (naive_errors, naive_subset_errors) - } - }; - - // Record illegal access errors - for &(loan, location) in errors.iter() { - result.errors.entry(location).or_default().push(loan); - } - - // Record illegal subset errors - for &(origin1, origin2, location) in subset_errors.iter() { - result - .subset_errors - .entry(location) - .or_default() - .insert((origin1, origin2)); - } - - // Record more debugging info when asked to do so - if dump_enabled { - for &(origin, location) in ctx.origin_live_on_entry.iter() { - result - .origin_live_on_entry - .entry(location) - .or_default() - .push(origin); - } - - for &(origin, loan) in ctx.known_contains.iter() { - result - .known_contains - .entry(origin) - .or_default() - .insert(loan); - } - } - - result - } - - /// Computes the transitive closure of the `known_placeholder_subset` relation, so that we have - /// the full list of placeholder loans contained by the placeholder origins. - fn compute_known_contains( - known_placeholder_subset: &Relation<(T::Origin, T::Origin)>, - placeholder: &[(T::Origin, T::Loan)], - ) -> Relation<(T::Origin, T::Loan)> { - let mut iteration = datafrog::Iteration::new(); - let known_contains = iteration.variable("known_contains"); - - // known_contains(Origin1, Loan1) :- - // placeholder(Origin1, Loan1). - known_contains.extend(placeholder.iter()); - - while iteration.changed() { - // known_contains(Origin2, Loan1) :- - // known_contains(Origin1, Loan1), - // known_placeholder_subset(Origin1, Origin2). - known_contains.from_join( - &known_contains, - known_placeholder_subset, - |&_origin1, &loan1, &origin2| (origin2, loan1), - ); - } - - known_contains.complete() - } - - /// Computes the transitive closure of the `known_placeholder_subset` relation. - fn compute_known_placeholder_subset( - known_placeholder_subset_base: &Relation<(T::Origin, T::Origin)>, - ) -> Relation<(T::Origin, T::Origin)> { - use datafrog::{Iteration, RelationLeaper}; - let mut iteration = Iteration::new(); - - let known_placeholder_subset = iteration.variable("known_placeholder_subset"); - - // known_placeholder_subset(Origin1, Origin2) :- - // known_placeholder_subset_base(Origin1, Origin2). - known_placeholder_subset.extend(known_placeholder_subset_base.iter()); - - while iteration.changed() { - // known_placeholder_subset(Origin1, Origin3) :- - // known_placeholder_subset(Origin1, Origin2), - // known_placeholder_subset_base(Origin2, Origin3). - known_placeholder_subset.from_leapjoin( - &known_placeholder_subset, - known_placeholder_subset_base.extend_with(|&(_origin1, origin2)| origin2), - |&(origin1, _origin2), &origin3| (origin1, origin3), - ); - } - - known_placeholder_subset.complete() - } - - fn new(dump_enabled: bool) -> Self { - Output { - errors: FxHashMap::default(), - subset_errors: FxHashMap::default(), - dump_enabled, - loan_live_at: FxHashMap::default(), - origin_contains_loan_at: FxHashMap::default(), - origin_contains_loan_anywhere: FxHashMap::default(), - origin_live_on_entry: FxHashMap::default(), - loan_invalidated_at: FxHashMap::default(), - move_errors: FxHashMap::default(), - subset: FxHashMap::default(), - subset_anywhere: FxHashMap::default(), - var_live_on_entry: FxHashMap::default(), - var_drop_live_on_entry: FxHashMap::default(), - path_maybe_initialized_on_exit: FxHashMap::default(), - path_maybe_uninitialized_on_exit: FxHashMap::default(), - var_maybe_partly_initialized_on_exit: FxHashMap::default(), - known_contains: FxHashMap::default(), - } - } - - pub fn errors_at(&self, location: T::Point) -> &[T::Loan] { - match self.errors.get(&location) { - Some(v) => v, - None => &[], - } - } - - pub fn loans_in_scope_at(&self, location: T::Point) -> &[T::Loan] { - match self.loan_live_at.get(&location) { - Some(p) => p, - None => &[], - } - } - - pub fn origin_contains_loan_at( - &self, - location: T::Point, - ) -> Cow<'_, BTreeMap>> { - assert!(self.dump_enabled); - match self.origin_contains_loan_at.get(&location) { - Some(map) => Cow::Borrowed(map), - None => Cow::Owned(BTreeMap::default()), - } - } - - pub fn origins_live_at(&self, location: T::Point) -> &[T::Origin] { - assert!(self.dump_enabled); - match self.origin_live_on_entry.get(&location) { - Some(v) => v, - None => &[], - } - } - - pub fn subsets_at( - &self, - location: T::Point, - ) -> Cow<'_, BTreeMap>> { - assert!(self.dump_enabled); - match self.subset.get(&location) { - Some(v) => Cow::Borrowed(v), - None => Cow::Owned(BTreeMap::default()), - } - } -} - -/// Compares errors reported by Naive implementation with the errors -/// reported by the optimized implementation. -fn compare_errors( - all_naive_errors: &FxHashMap>, - all_opt_errors: &FxHashMap>, -) -> bool { - let points = all_naive_errors.keys().chain(all_opt_errors.keys()); - - let mut differ = false; - for point in points { - let mut naive_errors = all_naive_errors.get(&point).cloned().unwrap_or_default(); - naive_errors.sort(); - - let mut opt_errors = all_opt_errors.get(&point).cloned().unwrap_or_default(); - opt_errors.sort(); - - for err in naive_errors.iter() { - if !opt_errors.contains(err) { - error!( - "Error {0:?} at {1:?} reported by naive, but not opt.", - err, point - ); - differ = true; - } - } - - for err in opt_errors.iter() { - if !naive_errors.contains(err) { - error!( - "Error {0:?} at {1:?} reported by opt, but not naive.", - err, point - ); - differ = true; - } - } - } - - differ -} - -#[cfg(test)] -mod tests { - use super::*; - - impl Atom for usize { - fn index(self) -> usize { - self - } - } - - fn compare( - errors1: &FxHashMap>, - errors2: &FxHashMap>, - ) -> bool { - let diff1 = compare_errors(errors1, errors2); - let diff2 = compare_errors(errors2, errors1); - assert_eq!(diff1, diff2); - diff1 - } - - #[test] - fn test_compare_errors() { - let empty = FxHashMap::default(); - assert_eq!(false, compare(&empty, &empty)); - let mut empty_vec = FxHashMap::default(); - empty_vec.insert(1, vec![]); - empty_vec.insert(2, vec![]); - assert_eq!(false, compare(&empty, &empty_vec)); - - let mut singleton1 = FxHashMap::default(); - singleton1.insert(1, vec![10]); - assert_eq!(false, compare(&singleton1, &singleton1)); - let mut singleton2 = FxHashMap::default(); - singleton2.insert(1, vec![11]); - assert_eq!(false, compare(&singleton2, &singleton2)); - let mut singleton3 = FxHashMap::default(); - singleton3.insert(2, vec![10]); - assert_eq!(false, compare(&singleton3, &singleton3)); - - assert_eq!(true, compare(&singleton1, &singleton2)); - assert_eq!(true, compare(&singleton2, &singleton3)); - assert_eq!(true, compare(&singleton1, &singleton3)); - - assert_eq!(true, compare(&empty, &singleton1)); - assert_eq!(true, compare(&empty, &singleton2)); - assert_eq!(true, compare(&empty, &singleton3)); - - let mut errors1 = FxHashMap::default(); - errors1.insert(1, vec![11]); - errors1.insert(2, vec![10]); - assert_eq!(false, compare(&errors1, &errors1)); - assert_eq!(true, compare(&errors1, &singleton1)); - assert_eq!(true, compare(&errors1, &singleton2)); - assert_eq!(true, compare(&errors1, &singleton3)); - } -} diff --git a/polonius-engine/src/pipeline.rs b/polonius-engine/src/pipeline.rs new file mode 100644 index 0000000000..ead2987b17 --- /dev/null +++ b/polonius-engine/src/pipeline.rs @@ -0,0 +1,225 @@ +use std::time::Instant; + +use rustc_hash::FxHashSet; + +use crate::dump::{Dump, Dumper}; +use crate::{compute, Computation, Db, FactTypes, LoadFrom, Rels, StoreTo}; + +pub struct Pipeline(&'static [&'static dyn ComputationDyn]); + +macro_rules! pipeline { + ( $($unit:expr),* $(,)? ) => { + $crate::pipeline::Pipeline::new(&[ + $( &$unit as _, )* + ]) + }; +} + +impl Pipeline { + pub fn new(x: &'static [&'static dyn ComputationDyn]) -> Self { + Pipeline(x) + } + + pub(crate) fn naive() -> Self { + pipeline![ + compute::Cfg, + compute::Paths, + compute::MaybeInit, + compute::VarDroppedWhileInit, + compute::MaybeUninit, + compute::MoveError, + compute::KnownPlaceholder, + compute::LiveOrigins, + compute::BorrowckNaive, + ] + } + + pub(crate) fn opt() -> Self { + pipeline![ + compute::Cfg, + compute::Paths, + compute::MaybeInit, + compute::VarDroppedWhileInit, + compute::MaybeUninit, + compute::MoveError, + compute::KnownPlaceholder, + compute::LiveOrigins, + compute::BorrowckOptimized, + ] + } + + pub(crate) fn location_insensitive() -> Self { + pipeline![ + compute::Cfg, + compute::Paths, + compute::MaybeInit, + compute::VarDroppedWhileInit, + compute::MaybeUninit, + compute::MoveError, + compute::KnownPlaceholder, + compute::KnownPlaceholderLoans, + compute::LiveOrigins, + compute::BorrowckLocationInsensitive, + compute::BorrowckLocationInsensitiveAsSensitive, + ] + } + + pub(crate) fn compare() -> Self { + pipeline![ + compute::Cfg, + compute::Paths, + compute::MaybeInit, + compute::VarDroppedWhileInit, + compute::MaybeUninit, + compute::MoveError, + compute::KnownPlaceholder, + compute::LiveOrigins, + compute::BorrowckNaive, + compute::BorrowckOptimized, + ] + } + + pub(crate) fn hybrid() -> Self { + pipeline![ + compute::Cfg, + compute::Paths, + compute::MaybeInit, + compute::VarDroppedWhileInit, + compute::MaybeUninit, + compute::MoveError, + compute::KnownPlaceholder, + compute::KnownPlaceholderLoans, + compute::LiveOrigins, + compute::BorrowckLocationInsensitive, + compute::BorrowckOptimized, + ] + } + + pub fn compute(&self, input: I, dumpers: Vec<&mut dyn Dumper>) -> O + where + I: StoreTo, + O: for<'db> LoadFrom<'db, T>, + { + self.validate(I::RELATIONS, O::RELATIONS); + + let mut cx = Dump::new(dumpers); + + let mut facts = Db::default(); + input.store_to_db(&mut facts, &mut cx); + + // FIXME: clean up relations that aren't needed for the final output in-between units. + for unit in self.0 { + unit.compute(&mut facts, &mut cx); + } + + O::load_from_db(&facts) + } + + /// Check that this pipeline is able to compute the specified outputs if given the specificied + /// inputs. + /// + /// Panics if this requirement is not met. + fn validate(&self, inputs: Rels, outputs: Rels) { + let mut available: FxHashSet<&str> = Default::default(); + available.extend(inputs.iter()); + + for unit in self.0 { + // Ensure that the required inputs have all been computed + for input in unit.inputs() { + if !available.contains(input) { + panic!( + "`{}` required by {} but not provided by input or preceding computation", + input, + unit.name() + ) + } + } + available.extend(unit.outputs()); + } + + for output in outputs { + if !available.contains(output) { + panic!( + "Required output `{}` not computed by any computation", + output + ) + } + } + } +} + +/// An object-safe wrapper around a [`Computation`]. +pub trait ComputationDyn { + /// A human-readable name for this computation. + fn name(&self) -> &'static str; + + /// The required inputs. + fn inputs(&self) -> Rels; + + /// The outputs. + fn outputs(&self) -> Rels; + + /// Loads the input for a [`Computation`], runs it, and stores the result. + fn compute(&self, db: &mut Db, dump: &mut Dump<'_>); +} + +// `#![feature(associated_type_bounds)]` is required for this impl. Otherwise the type parameters +// we create to represent for `Input` and `Output` are unbound. +// +// FIXME: Can we write this impl without the feature gate? +impl ComputationDyn for C +where + C: Computation, + for<'db> C::Input<'db>: LoadFrom<'db, T>, + C::Output: StoreTo, + T: FactTypes, +{ + fn name(&self) -> &'static str { + readable_typename::() + } + + fn inputs(&self) -> Rels { + >::RELATIONS + } + + fn outputs(&self) -> Rels { + ::RELATIONS + } + + fn compute(&self, db: &mut Db, dump: &mut Dump<'_>) { + compute_(self, db, dump) + } +} + +/// Loads the input for a [`Computation`], runs it, and stores the result. +fn compute_(computation: &C, db: &mut Db, dump: &mut Dump<'_>) +where + C: Computation, + for<'db> C::Input<'db>: LoadFrom<'db, T>, + C::Output: StoreTo, + T: FactTypes, +{ + let name = readable_typename::(); + db.curr_unit = name; + info!("Running computation `{}`...", name); + + let input = >::load_from_db(db); + dump.unit_start(name); + let start_time = Instant::now(); + let output = computation.compute(input, dump); + let end_time = Instant::now(); + dump.unit_end(name); + + output.store_to_db(db, dump); + + let elapsed_time = end_time - start_time; + info!( + "Finished computation `{}` in {:.5}s", + name, + elapsed_time.as_secs_f64(), + ); +} + +fn readable_typename() -> &'static str { + std::any::type_name::().split(':').last().unwrap() +} diff --git a/polonius-engine/src/tuples.rs b/polonius-engine/src/tuples.rs new file mode 100644 index 0000000000..1ed250a60a --- /dev/null +++ b/polonius-engine/src/tuples.rs @@ -0,0 +1,154 @@ +//! Ob +//! +//! Allows us to represent relations as trait objects. + +use std::any::{Any, TypeId}; + +use dyn_clone::DynClone; +use smallvec::{smallvec, SmallVec}; + +use crate::Atom; + +/// A series of `TypeId`s representing the type of a tuple (e.g. `(u32, i32)`). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TupleSchema { + tys: &'static [TypeId], +} + +impl TupleSchema { + pub const fn new(tys: &'static [TypeId]) -> Self { + TupleSchema { tys } + } + + pub const fn arity(&self) -> usize { + self.tys.len() + } +} + +/// Types that have an associated `TupleSchema`. +/// +/// This includes both iterators over `Tuple`s and `Vec`s containing them. +pub trait HasSchema { + /// Returns the `TupleSchema` associated with this type. + fn schema(&self) -> TupleSchema; +} + +impl HasSchema for I +where + I: IntoIterator, +{ + fn schema(&self) -> TupleSchema { + T::SCHEMA + } +} + +pub type RawTuple = SmallVec<[usize; 6]>; + +/// A single datafrog fact. A tuple of newtyped indices. +pub trait Tuple: 'static + Copy + Sized { + const SCHEMA: TupleSchema; + + fn into_raw(self) -> RawTuple; + fn from_raw(raw: RawTuple) -> Self; +} + +macro_rules! impl_raw_tuple { + ($($T:ident)*) => { + impl<$($T: Atom),*> Tuple for ($($T,)*) { + const SCHEMA: TupleSchema = TupleSchema::new(&[$( TypeId::of::<$T>() ),*]); + + #[allow(non_snake_case)] + fn into_raw(self) -> RawTuple { + let ($($T,)*) = self; + smallvec![$($T.into()),*] + } + + #[allow(non_snake_case)] + fn from_raw(raw: RawTuple) -> Self { + assert_eq!(raw.len(), count_idents!($($T)*)); + + match raw.as_slice() { + &[$($T),*] => ($($T.into(),)*), + _ => unreachable!(), + } + } + } + } +} + +for_each_tuple!(impl_raw_tuple => [F E D C B A]); + +/// An object-safe representation of `Vec where T: Tuple`. +pub trait TupleVec: HasSchema + DynClone { + /// Returns an iterator over the tuples in this vector. + fn iter_tuples(&self) -> Box + '_>; + + /// Converts to a [`std::any::Any`] for downcasting. + fn as_any(&self) -> &dyn Any; +} + +impl TupleVec for Vec { + fn iter_tuples(&self) -> Box + '_> { + Box::new(self.iter().copied()) + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +/// An object-safe representation of `Iterator where T: Tuple`. +/// +/// You might think that `Box>` would be sufficient. +/// However, `TupleVec` needs the underlying type to be an actual `Vec` for downcasting, and we +/// cannot create one from an iterator over [`RawTuple`]s, even if the [`TupleSchema`] is known. +/// [`TupleIter::collect_tuples`] serves this purpose. +pub trait TupleIter<'me>: HasSchema + DynClone { + /// Consumes this iterator, returning the number of tuples it would yield. + /// + /// Analagous to [`std::iter::Iterator::count`]. + fn count(self: Box) -> usize; + + /// Maps this iterator to one that returns [`RawTuple`]s. + fn map_raw(self: Box) -> Box + 'me>; + + /// Collects the tuples yielded by this iterator into a [`Box`](TupleVec). + fn collect_tuples(self: Box) -> Box; +} + +impl<'me, T, I> TupleIter<'me> for I +where + I: 'me + Clone + Iterator + ?Sized, + T: Tuple, +{ + fn count(self: Box) -> usize { + Iterator::count(*self) + } + + fn map_raw(self: Box) -> Box + 'me> { + let iter = Box::new(self.map(T::into_raw)); + iter + } + + fn collect_tuples(self: Box) -> Box { + Box::new(self.collect::>()) + } +} + +impl From> for Box { + fn from(x: Vec) -> Self { + Box::new(x) + } +} + +/// Casts a [`TupleVec`] to its underlying `Vec`. +pub fn downcast_vec(x: &dyn TupleVec) -> Option<&Vec> { + (T::SCHEMA == x.schema()).then(|| x.as_any().downcast_ref::>().unwrap()) +} + +/// Casts a [`TupleIter`] to its underlying `Iterator`. +pub fn downcast_iter( + x: Box + '_>, +) -> Option + '_> { + (T::SCHEMA == x.schema()).then(|| x.map_raw().map(|raw| T::from_raw(raw))) +} diff --git a/polonius-engine/src/util.rs b/polonius-engine/src/util.rs new file mode 100644 index 0000000000..282d58b01f --- /dev/null +++ b/polonius-engine/src/util.rs @@ -0,0 +1,28 @@ +macro_rules! for_each_tuple { + ($m:ident => [$($T:ident)*]) => { + for_each_tuple!(@IMPL $m => [$($T)*]); + }; + + (@IMPL $m:ident => []) => { + $m!(); + }; + + (@IMPL $m:ident => [$H:ident $($T:ident)*]) => { + $m!($H $($T)*); + for_each_tuple!(@IMPL $m => [$($T)*]); + }; +} + +macro_rules! count_idents { + () => { 0 }; + ($odd:ident $($a:ident $b:ident)*) => { count_idents!($($a)*) << 1 | 1 }; + ($($a:ident $b:ident)*) => { count_idents!($($a)*) << 1 }; +} + +macro_rules! lg { + ($m:path, $($tt:tt)*) => { $m!(target: "polonius_engine", $($tt)*) } +} + +macro_rules! info { + ($($tt:tt)*) => { lg!(log::info, $($tt)*) } +} diff --git a/src/test.rs b/src/test.rs index 30d94bdf34..51eab67493 100644 --- a/src/test.rs +++ b/src/test.rs @@ -573,7 +573,7 @@ fn var_drop_used_simple() { fn illegal_subset_error() { let program = r" placeholders { 'a, 'b } - + block B0 { // creates a transitive `'b: 'a` subset loan_issued_at('x, L0), @@ -638,7 +638,7 @@ fn transitive_known_subset() { let program = r" placeholders { 'a, 'b, 'c } known_subsets { 'a: 'b, 'b: 'c } - + block B0 { loan_issued_at('x, L0), outlives('a: 'x), @@ -650,17 +650,17 @@ fn transitive_known_subset() { assert_eq!(checker.facts.universal_region.len(), 3); assert_eq!(checker.facts.placeholder.len(), 3); + assert_eq!(checker.subset_errors_count(), 0); + + assert_checkers_match(&checker, &opt_checker_for(program)); + + let noloc = location_insensitive_checker_for(program); // the 2 `known_placeholder_subset`s here mean 3 `known_contains`, transitively - assert_eq!(checker.facts.known_placeholder_subset.len(), 2); - assert_eq!(checker.output.known_contains.len(), 3); + assert_eq!(noloc.facts.known_placeholder_subset.len(), 2); + assert_eq!(noloc.output.known_contains.len(), 3); - assert_eq!(checker.subset_errors_count(), 0); - assert_eq!( - location_insensitive_checker_for(program).subset_errors_count(), - 0 - ); - assert_checkers_match(&checker, &opt_checker_for(program)); + assert_eq!(noloc.subset_errors_count(), 0); } /// Even if `'a: 'b` is known, `'a`'s placeholder loan can flow into `'b''s supersets, @@ -670,7 +670,7 @@ fn transitive_illegal_subset_error() { let program = r" placeholders { 'a, 'b, 'c } known_subsets { 'a: 'b } - + block B0 { // this transitive `'a: 'b` subset is already known loan_issued_at('x, L0), @@ -679,7 +679,7 @@ fn transitive_illegal_subset_error() { // creates unknown transitive subsets: // - `'b: 'c` - // - and therefore `'a: 'c` + // - and therefore `'a: 'c` loan_issued_at('y, L1), outlives('b: 'y), outlives('y: 'c);