summaryrefslogtreecommitdiffstats
path: root/vendor/polonius-engine/src/output/initialization.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/polonius-engine/src/output/initialization.rs')
-rw-r--r--vendor/polonius-engine/src/output/initialization.rs284
1 files changed, 284 insertions, 0 deletions
diff --git a/vendor/polonius-engine/src/output/initialization.rs b/vendor/polonius-engine/src/output/initialization.rs
new file mode 100644
index 000000000..30409d965
--- /dev/null
+++ b/vendor/polonius-engine/src/output/initialization.rs
@@ -0,0 +1,284 @@
+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<T: FactTypes> {
+ 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<T: FactTypes> {
+ var_maybe_partly_initialized_on_exit: Relation<(T::Variable, T::Point)>,
+ move_error: Relation<(T::Path, T::Point)>,
+}
+
+pub(super) struct InitializationResult<T: FactTypes>(
+ 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<T: FactTypes>(
+ 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<T> {
+ 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<T: FactTypes>(
+ ctx: TransitivePaths<T>,
+ cfg_edge: &Relation<(T::Point, T::Point)>,
+ output: &mut Output<T>,
+) -> InitializationStatus<T> {
+ 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<T: FactTypes>(
+ ctx: InitializationContext<T>,
+ cfg_edge: &Relation<(T::Point, T::Point)>,
+ output: &mut Output<T>,
+) -> InitializationResult<T> {
+ let timer = Instant::now();
+
+ let transitive_paths = compute_transitive_paths::<T>(
+ 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::<T>(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)
+}