// Copyright 2012-2016 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 rustc::ty::TyCtxt; use rustc::mir::repr::{self, Mir}; use super::super::gather_moves::{Location}; use super::super::gather_moves::{MoveData, MoveOutIndex, MovePathIndex}; use super::super::DropFlagState; use super::super::drop_flag_effects_for_function_entry; use super::super::drop_flag_effects_for_location; use super::super::on_all_children_bits; use super::{BitDenotation, BlockSets, DataflowOperator}; use bitslice::BitSlice; // adds set_bit/get_bit to &[usize] bitvector rep. use bitslice::{BitwiseOperator}; use indexed_set::{Idx, IdxSet}; // Dataflow analyses are built upon some interpretation of the // bitvectors attached to each basic block, represented via a // zero-sized structure. /// `MaybeInitializedLvals` tracks all l-values that might be /// initialized upon reaching a particular point in the control flow /// for a function. /// /// For example, in code like the following, we have corresponding /// dataflow information shown in the right-hand comments. /// /// ```rust /// struct S; /// fn foo(pred: bool) { // maybe-init: /// // {} /// let a = S; let b = S; let c; let d; // {a, b} /// /// if pred { /// drop(a); // { b} /// b = S; // { b} /// /// } else { /// drop(b); // {a} /// d = S; // {a, d} /// /// } // {a, b, d} /// /// c = S; // {a, b, c, d} /// } /// ``` /// /// To determine whether an l-value *must* be initialized at a /// particular control-flow point, one can take the set-difference /// between this data and the data from `MaybeUninitializedLvals` at the /// corresponding control-flow point. /// /// Similarly, at a given `drop` statement, the set-intersection /// between this data and `MaybeUninitializedLvals` yields the set of /// l-values that would require a dynamic drop-flag at that statement. pub struct MaybeInitializedLvals<'a, 'tcx: 'a> { tcx: TyCtxt<'a, 'tcx, 'tcx>, mir: &'a Mir<'tcx>, } impl<'a, 'tcx: 'a> MaybeInitializedLvals<'a, 'tcx> { pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, mir: &'a Mir<'tcx>) -> Self { MaybeInitializedLvals { tcx: tcx, mir: mir } } } /// `MaybeUninitializedLvals` tracks all l-values that might be /// uninitialized upon reaching a particular point in the control flow /// for a function. /// /// For example, in code like the following, we have corresponding /// dataflow information shown in the right-hand comments. /// /// ```rust /// struct S; /// fn foo(pred: bool) { // maybe-uninit: /// // {a, b, c, d} /// let a = S; let b = S; let c; let d; // { c, d} /// /// if pred { /// drop(a); // {a, c, d} /// b = S; // {a, c, d} /// /// } else { /// drop(b); // { b, c, d} /// d = S; // { b, c } /// /// } // {a, b, c, d} /// /// c = S; // {a, b, d} /// } /// ``` /// /// To determine whether an l-value *must* be uninitialized at a /// particular control-flow point, one can take the set-difference /// between this data and the data from `MaybeInitializedLvals` at the /// corresponding control-flow point. /// /// Similarly, at a given `drop` statement, the set-intersection /// between this data and `MaybeInitializedLvals` yields the set of /// l-values that would require a dynamic drop-flag at that statement. pub struct MaybeUninitializedLvals<'a, 'tcx: 'a> { tcx: TyCtxt<'a, 'tcx, 'tcx>, mir: &'a Mir<'tcx>, } impl<'a, 'tcx: 'a> MaybeUninitializedLvals<'a, 'tcx> { pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, mir: &'a Mir<'tcx>) -> Self { MaybeUninitializedLvals { tcx: tcx, mir: mir } } } /// `DefinitelyInitializedLvals` tracks all l-values that are definitely /// initialized upon reaching a particular point in the control flow /// for a function. /// /// FIXME: Note that once flow-analysis is complete, this should be /// the set-complement of MaybeUninitializedLvals; thus we can get rid /// of one or the other of these two. I'm inclined to get rid of /// MaybeUninitializedLvals, simply because the sets will tend to be /// smaller in this analysis and thus easier for humans to process /// when debugging. /// /// For example, in code like the following, we have corresponding /// dataflow information shown in the right-hand comments. /// /// ```rust /// struct S; /// fn foo(pred: bool) { // definite-init: /// // { } /// let a = S; let b = S; let c; let d; // {a, b } /// /// if pred { /// drop(a); // { b, } /// b = S; // { b, } /// /// } else { /// drop(b); // {a, } /// d = S; // {a, d} /// /// } // { } /// /// c = S; // { c } /// } /// ``` /// /// To determine whether an l-value *may* be uninitialized at a /// particular control-flow point, one can take the set-complement /// of this data. /// /// Similarly, at a given `drop` statement, the set-difference between /// this data and `MaybeInitializedLvals` yields the set of l-values /// that would require a dynamic drop-flag at that statement. pub struct DefinitelyInitializedLvals<'a, 'tcx: 'a> { tcx: TyCtxt<'a, 'tcx, 'tcx>, mir: &'a Mir<'tcx>, } impl<'a, 'tcx: 'a> DefinitelyInitializedLvals<'a, 'tcx> { pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, mir: &'a Mir<'tcx>) -> Self { DefinitelyInitializedLvals { tcx: tcx, mir: mir } } } /// `MovingOutStatements` tracks the statements that perform moves out /// of particular l-values. More precisely, it tracks whether the /// *effect* of such moves (namely, the uninitialization of the /// l-value in question) can reach some point in the control-flow of /// the function, or if that effect is "killed" by some intervening /// operation reinitializing that l-value. /// /// The resulting dataflow is a more enriched version of /// `MaybeUninitializedLvals`. Both structures on their own only tell /// you if an l-value *might* be uninitialized at a given point in the /// control flow. But `MovingOutStatements` also includes the added /// data of *which* particular statement causing the deinitialization /// that the borrow checker's error meessage may need to report. #[allow(dead_code)] pub struct MovingOutStatements<'a, 'tcx: 'a> { tcx: TyCtxt<'a, 'tcx, 'tcx>, mir: &'a Mir<'tcx>, } impl<'a, 'tcx> MaybeInitializedLvals<'a, 'tcx> { fn update_bits(sets: &mut BlockSets, path: MovePathIndex, state: DropFlagState) { match state { DropFlagState::Absent => sets.kill(&path), DropFlagState::Present => sets.gen(&path), } } } impl<'a, 'tcx> MaybeUninitializedLvals<'a, 'tcx> { fn update_bits(sets: &mut BlockSets, path: MovePathIndex, state: DropFlagState) { match state { DropFlagState::Absent => sets.gen(&path), DropFlagState::Present => sets.kill(&path), } } } impl<'a, 'tcx> DefinitelyInitializedLvals<'a, 'tcx> { fn update_bits(sets: &mut BlockSets, path: MovePathIndex, state: DropFlagState) { match state { DropFlagState::Absent => sets.kill(&path), DropFlagState::Present => sets.gen(&path), } } } impl<'a, 'tcx> BitDenotation for MaybeInitializedLvals<'a, 'tcx> { type Idx = MovePathIndex; type Ctxt = MoveData<'tcx>; fn name() -> &'static str { "maybe_init" } fn bits_per_block(&self, ctxt: &Self::Ctxt) -> usize { ctxt.move_paths.len() } fn start_block_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets) { drop_flag_effects_for_function_entry( self.tcx, self.mir, ctxt, |path, s| { assert!(s == DropFlagState::Present); sets.on_entry.add(&path); }); } fn statement_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets, bb: repr::BasicBlock, idx: usize) { drop_flag_effects_for_location( self.tcx, self.mir, ctxt, Location { block: bb, index: idx }, |path, s| Self::update_bits(sets, path, s) ) } fn terminator_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets, bb: repr::BasicBlock, statements_len: usize) { drop_flag_effects_for_location( self.tcx, self.mir, ctxt, Location { block: bb, index: statements_len }, |path, s| Self::update_bits(sets, path, s) ) } fn propagate_call_return(&self, ctxt: &Self::Ctxt, in_out: &mut IdxSet, _call_bb: repr::BasicBlock, _dest_bb: repr::BasicBlock, dest_lval: &repr::Lvalue) { // when a call returns successfully, that means we need to set // the bits for that dest_lval to 1 (initialized). let move_path_index = ctxt.rev_lookup.find(dest_lval); on_all_children_bits(self.tcx, self.mir, ctxt, move_path_index, |mpi| { in_out.add(&mpi); }); } } impl<'a, 'tcx> BitDenotation for MaybeUninitializedLvals<'a, 'tcx> { type Idx = MovePathIndex; type Ctxt = MoveData<'tcx>; fn name() -> &'static str { "maybe_uninit" } fn bits_per_block(&self, ctxt: &Self::Ctxt) -> usize { ctxt.move_paths.len() } // sets on_entry bits for Arg lvalues fn start_block_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets) { // set all bits to 1 (uninit) before gathering counterevidence for e in sets.on_entry.words_mut() { *e = !0; } drop_flag_effects_for_function_entry( self.tcx, self.mir, ctxt, |path, s| { assert!(s == DropFlagState::Present); sets.on_entry.remove(&path); }); } fn statement_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets, bb: repr::BasicBlock, idx: usize) { drop_flag_effects_for_location( self.tcx, self.mir, ctxt, Location { block: bb, index: idx }, |path, s| Self::update_bits(sets, path, s) ) } fn terminator_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets, bb: repr::BasicBlock, statements_len: usize) { drop_flag_effects_for_location( self.tcx, self.mir, ctxt, Location { block: bb, index: statements_len }, |path, s| Self::update_bits(sets, path, s) ) } fn propagate_call_return(&self, ctxt: &Self::Ctxt, in_out: &mut IdxSet, _call_bb: repr::BasicBlock, _dest_bb: repr::BasicBlock, dest_lval: &repr::Lvalue) { // when a call returns successfully, that means we need to set // the bits for that dest_lval to 1 (initialized). let move_path_index = ctxt.rev_lookup.find(dest_lval); on_all_children_bits(self.tcx, self.mir, ctxt, move_path_index, |mpi| { in_out.remove(&mpi); }); } } impl<'a, 'tcx> BitDenotation for DefinitelyInitializedLvals<'a, 'tcx> { type Idx = MovePathIndex; type Ctxt = MoveData<'tcx>; fn name() -> &'static str { "definite_init" } fn bits_per_block(&self, ctxt: &Self::Ctxt) -> usize { ctxt.move_paths.len() } // sets on_entry bits for Arg lvalues fn start_block_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets) { for e in sets.on_entry.words_mut() { *e = 0; } drop_flag_effects_for_function_entry( self.tcx, self.mir, ctxt, |path, s| { assert!(s == DropFlagState::Present); sets.on_entry.add(&path); }); } fn statement_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets, bb: repr::BasicBlock, idx: usize) { drop_flag_effects_for_location( self.tcx, self.mir, ctxt, Location { block: bb, index: idx }, |path, s| Self::update_bits(sets, path, s) ) } fn terminator_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets, bb: repr::BasicBlock, statements_len: usize) { drop_flag_effects_for_location( self.tcx, self.mir, ctxt, Location { block: bb, index: statements_len }, |path, s| Self::update_bits(sets, path, s) ) } fn propagate_call_return(&self, ctxt: &Self::Ctxt, in_out: &mut IdxSet, _call_bb: repr::BasicBlock, _dest_bb: repr::BasicBlock, dest_lval: &repr::Lvalue) { // when a call returns successfully, that means we need to set // the bits for that dest_lval to 1 (initialized). let move_path_index = ctxt.rev_lookup.find(dest_lval); on_all_children_bits(self.tcx, self.mir, ctxt, move_path_index, |mpi| { in_out.add(&mpi); }); } } impl<'a, 'tcx> BitDenotation for MovingOutStatements<'a, 'tcx> { type Idx = MoveOutIndex; type Ctxt = MoveData<'tcx>; fn name() -> &'static str { "moving_out" } fn bits_per_block(&self, ctxt: &Self::Ctxt) -> usize { ctxt.moves.len() } fn start_block_effect(&self,_move_data: &Self::Ctxt, _sets: &mut BlockSets) { // no move-statements have been executed prior to function // execution, so this method has no effect on `_sets`. } fn statement_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets, bb: repr::BasicBlock, idx: usize) { let (tcx, mir, move_data) = (self.tcx, self.mir, ctxt); let stmt = &mir.basic_block_data(bb).statements[idx]; let loc_map = &move_data.loc_map; let path_map = &move_data.path_map; let rev_lookup = &move_data.rev_lookup; let loc = Location { block: bb, index: idx }; debug!("stmt {:?} at loc {:?} moves out of move_indexes {:?}", stmt, loc, &loc_map[loc]); for move_index in &loc_map[loc] { // Every path deinitialized by a *particular move* // has corresponding bit, "gen'ed" (i.e. set) // here, in dataflow vector zero_to_one(sets.gen_set.words_mut(), *move_index); } let bits_per_block = self.bits_per_block(ctxt); match stmt.kind { repr::StatementKind::Assign(ref lvalue, _) => { // assigning into this `lvalue` kills all // MoveOuts from it, and *also* all MoveOuts // for children and associated fragment sets. let move_path_index = rev_lookup.find(lvalue); on_all_children_bits(tcx, mir, move_data, move_path_index, |mpi| for moi in &path_map[mpi] { assert!(moi.idx() < bits_per_block); sets.kill_set.add(&moi); }); } } } fn terminator_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets, bb: repr::BasicBlock, statements_len: usize) { let (mir, move_data) = (self.mir, ctxt); let term = mir.basic_block_data(bb).terminator.as_ref().unwrap(); let loc_map = &move_data.loc_map; let loc = Location { block: bb, index: statements_len }; debug!("terminator {:?} at loc {:?} moves out of move_indexes {:?}", term, loc, &loc_map[loc]); let bits_per_block = self.bits_per_block(ctxt); for move_index in &loc_map[loc] { assert!(move_index.idx() < bits_per_block); zero_to_one(sets.gen_set.words_mut(), *move_index); } } fn propagate_call_return(&self, ctxt: &Self::Ctxt, in_out: &mut IdxSet, _call_bb: repr::BasicBlock, _dest_bb: repr::BasicBlock, dest_lval: &repr::Lvalue) { let move_data = ctxt; let move_path_index = move_data.rev_lookup.find(dest_lval); let bits_per_block = self.bits_per_block(ctxt); let path_map = &move_data.path_map; on_all_children_bits(self.tcx, self.mir, move_data, move_path_index, |mpi| for moi in &path_map[mpi] { assert!(moi.idx() < bits_per_block); in_out.remove(&moi); }); } } fn zero_to_one(bitvec: &mut [usize], move_index: MoveOutIndex) { let retval = bitvec.set_bit(move_index.idx()); assert!(retval); } impl<'a, 'tcx> BitwiseOperator for MovingOutStatements<'a, 'tcx> { #[inline] fn join(&self, pred1: usize, pred2: usize) -> usize { pred1 | pred2 // moves from both preds are in scope } } impl<'a, 'tcx> BitwiseOperator for MaybeInitializedLvals<'a, 'tcx> { #[inline] fn join(&self, pred1: usize, pred2: usize) -> usize { pred1 | pred2 // "maybe" means we union effects of both preds } } impl<'a, 'tcx> BitwiseOperator for MaybeUninitializedLvals<'a, 'tcx> { #[inline] fn join(&self, pred1: usize, pred2: usize) -> usize { pred1 | pred2 // "maybe" means we union effects of both preds } } impl<'a, 'tcx> BitwiseOperator for DefinitelyInitializedLvals<'a, 'tcx> { #[inline] fn join(&self, pred1: usize, pred2: usize) -> usize { pred1 & pred2 // "definitely" means we intersect effects of both preds } } // The way that dataflow fixed point iteration works, you want to // start at bottom and work your way to a fixed point. Control-flow // merges will apply the `join` operator to each block entry's current // state (which starts at that bottom value). // // This means, for propagation across the graph, that you either want // to start at all-zeroes and then use Union as your merge when // propagating, or you start at all-ones and then use Intersect as // your merge when propagating. impl<'a, 'tcx> DataflowOperator for MovingOutStatements<'a, 'tcx> { #[inline] fn bottom_value() -> bool { false // bottom = no loans in scope by default } } impl<'a, 'tcx> DataflowOperator for MaybeInitializedLvals<'a, 'tcx> { #[inline] fn bottom_value() -> bool { false // bottom = uninitialized } } impl<'a, 'tcx> DataflowOperator for MaybeUninitializedLvals<'a, 'tcx> { #[inline] fn bottom_value() -> bool { false // bottom = initialized (start_block_effect counters this at outset) } } impl<'a, 'tcx> DataflowOperator for DefinitelyInitializedLvals<'a, 'tcx> { #[inline] fn bottom_value() -> bool { true // bottom = initialized (start_block_effect counters this at outset) } }