| use std::assert_matches::assert_matches; |
| |
| use rustc_abi::VariantIdx; |
| use rustc_index::Idx; |
| use rustc_index::bit_set::{DenseBitSet, MixedBitSet}; |
| use rustc_middle::bug; |
| use rustc_middle::mir::{ |
| self, Body, CallReturnPlaces, Location, SwitchTargetValue, TerminatorEdges, |
| }; |
| use rustc_middle::ty::util::Discr; |
| use rustc_middle::ty::{self, TyCtxt}; |
| use smallvec::SmallVec; |
| use tracing::{debug, instrument}; |
| |
| use crate::drop_flag_effects::{DropFlagState, InactiveVariants}; |
| use crate::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex}; |
| use crate::{ |
| Analysis, GenKill, MaybeReachable, drop_flag_effects, drop_flag_effects_for_function_entry, |
| drop_flag_effects_for_location, on_all_children_bits, on_lookup_result_bits, |
| }; |
| |
| // Used by both `MaybeInitializedPlaces` and `MaybeUninitializedPlaces`. |
| pub struct MaybePlacesSwitchIntData<'tcx> { |
| enum_place: mir::Place<'tcx>, |
| discriminants: Vec<(VariantIdx, Discr<'tcx>)>, |
| index: usize, |
| } |
| |
| impl<'tcx> MaybePlacesSwitchIntData<'tcx> { |
| /// Creates a `SmallVec` mapping each target in `targets` to its `VariantIdx`. |
| fn variants(&mut self, targets: &mir::SwitchTargets) -> SmallVec<[VariantIdx; 4]> { |
| self.index = 0; |
| targets.all_values().iter().map(|value| self.next_discr(value.get())).collect() |
| } |
| |
| // The discriminant order in the `SwitchInt` targets should match the order yielded by |
| // `AdtDef::discriminants`. We rely on this to match each discriminant in the targets to its |
| // corresponding variant in linear time. |
| fn next_discr(&mut self, value: u128) -> VariantIdx { |
| // An out-of-bounds abort will occur if the discriminant ordering isn't as described above. |
| loop { |
| let (variant, discr) = self.discriminants[self.index]; |
| self.index += 1; |
| if discr.val == value { |
| return variant; |
| } |
| } |
| } |
| } |
| |
| impl<'tcx> MaybePlacesSwitchIntData<'tcx> { |
| fn new( |
| tcx: TyCtxt<'tcx>, |
| body: &Body<'tcx>, |
| block: mir::BasicBlock, |
| discr: &mir::Operand<'tcx>, |
| ) -> Option<Self> { |
| let Some(discr) = discr.place() else { return None }; |
| |
| // Inspect a `SwitchInt`-terminated basic block to see if the condition of that `SwitchInt` |
| // is an enum discriminant. |
| // |
| // We expect such blocks to have a call to `discriminant` as their last statement like so: |
| // ```text |
| // ... |
| // _42 = discriminant(_1) |
| // SwitchInt(_42, ..) |
| // ``` |
| // If the basic block matches this pattern, this function gathers the place corresponding |
| // to the enum (`_1` in the example above) as well as the discriminants. |
| let block_data = &body[block]; |
| for statement in block_data.statements.iter().rev() { |
| match statement.kind { |
| mir::StatementKind::Assign(box (lhs, mir::Rvalue::Discriminant(enum_place))) |
| if lhs == discr => |
| { |
| match enum_place.ty(body, tcx).ty.kind() { |
| ty::Adt(enum_def, _) => { |
| return Some(MaybePlacesSwitchIntData { |
| enum_place, |
| discriminants: enum_def.discriminants(tcx).collect(), |
| index: 0, |
| }); |
| } |
| |
| // `Rvalue::Discriminant` is also used to get the active yield point for a |
| // coroutine, but we do not need edge-specific effects in that case. This |
| // may change in the future. |
| ty::Coroutine(..) => break, |
| |
| t => bug!("`discriminant` called on unexpected type {:?}", t), |
| } |
| } |
| mir::StatementKind::Coverage(_) => continue, |
| _ => break, |
| } |
| } |
| None |
| } |
| } |
| |
| /// `MaybeInitializedPlaces` tracks all places 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 mut 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 a place is *definitely* initialized at a |
| /// particular control-flow point, one can take the set-complement |
| /// of the data from `MaybeUninitializedPlaces` at the corresponding |
| /// control-flow point. |
| /// |
| /// Similarly, at a given `drop` statement, the set-intersection |
| /// between this data and `MaybeUninitializedPlaces` yields the set of |
| /// places that would require a dynamic drop-flag at that statement. |
| pub struct MaybeInitializedPlaces<'a, 'tcx> { |
| tcx: TyCtxt<'tcx>, |
| body: &'a Body<'tcx>, |
| move_data: &'a MoveData<'tcx>, |
| exclude_inactive_in_otherwise: bool, |
| skip_unreachable_unwind: bool, |
| } |
| |
| impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> { |
| pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, move_data: &'a MoveData<'tcx>) -> Self { |
| MaybeInitializedPlaces { |
| tcx, |
| body, |
| move_data, |
| exclude_inactive_in_otherwise: false, |
| skip_unreachable_unwind: false, |
| } |
| } |
| |
| /// Ensures definitely inactive variants are excluded from the set of initialized places for |
| /// blocks reached through an `otherwise` edge. |
| pub fn exclude_inactive_in_otherwise(mut self) -> Self { |
| self.exclude_inactive_in_otherwise = true; |
| self |
| } |
| |
| pub fn skipping_unreachable_unwind(mut self) -> Self { |
| self.skip_unreachable_unwind = true; |
| self |
| } |
| |
| pub fn is_unwind_dead( |
| &self, |
| place: mir::Place<'tcx>, |
| state: &<Self as Analysis<'tcx>>::Domain, |
| ) -> bool { |
| if let LookupResult::Exact(path) = self.move_data().rev_lookup.find(place.as_ref()) { |
| let mut maybe_live = false; |
| on_all_children_bits(self.move_data(), path, |child| { |
| maybe_live |= state.contains(child); |
| }); |
| !maybe_live |
| } else { |
| false |
| } |
| } |
| } |
| |
| impl<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> { |
| fn move_data(&self) -> &MoveData<'tcx> { |
| self.move_data |
| } |
| } |
| |
| /// `MaybeUninitializedPlaces` tracks all places 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 mut 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 a place is *definitely* uninitialized at a |
| /// particular control-flow point, one can take the set-complement |
| /// of the data from `MaybeInitializedPlaces` at the corresponding |
| /// control-flow point. |
| /// |
| /// Similarly, at a given `drop` statement, the set-intersection |
| /// between this data and `MaybeInitializedPlaces` yields the set of |
| /// places that would require a dynamic drop-flag at that statement. |
| pub struct MaybeUninitializedPlaces<'a, 'tcx> { |
| tcx: TyCtxt<'tcx>, |
| body: &'a Body<'tcx>, |
| move_data: &'a MoveData<'tcx>, |
| |
| mark_inactive_variants_as_uninit: bool, |
| include_inactive_in_otherwise: bool, |
| skip_unreachable_unwind: DenseBitSet<mir::BasicBlock>, |
| } |
| |
| impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> { |
| pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, move_data: &'a MoveData<'tcx>) -> Self { |
| MaybeUninitializedPlaces { |
| tcx, |
| body, |
| move_data, |
| mark_inactive_variants_as_uninit: false, |
| include_inactive_in_otherwise: false, |
| skip_unreachable_unwind: DenseBitSet::new_empty(body.basic_blocks.len()), |
| } |
| } |
| |
| /// Causes inactive enum variants to be marked as "maybe uninitialized" after a switch on an |
| /// enum discriminant. |
| /// |
| /// This is correct in a vacuum but is not the default because it causes problems in the borrow |
| /// checker, where this information gets propagated along `FakeEdge`s. |
| pub fn mark_inactive_variants_as_uninit(mut self) -> Self { |
| self.mark_inactive_variants_as_uninit = true; |
| self |
| } |
| |
| /// Ensures definitely inactive variants are included in the set of uninitialized places for |
| /// blocks reached through an `otherwise` edge. |
| pub fn include_inactive_in_otherwise(mut self) -> Self { |
| self.include_inactive_in_otherwise = true; |
| self |
| } |
| |
| pub fn skipping_unreachable_unwind( |
| mut self, |
| unreachable_unwind: DenseBitSet<mir::BasicBlock>, |
| ) -> Self { |
| self.skip_unreachable_unwind = unreachable_unwind; |
| self |
| } |
| } |
| |
| impl<'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { |
| fn move_data(&self) -> &MoveData<'tcx> { |
| self.move_data |
| } |
| } |
| |
| /// `EverInitializedPlaces` tracks all places that might have ever been |
| /// initialized upon reaching a particular point in the control flow |
| /// for a function, without an intervening `StorageDead`. |
| /// |
| /// This dataflow is used to determine if an immutable local variable may |
| /// be assigned to. |
| /// |
| /// 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) { // ever-init: |
| /// // { } |
| /// let a = S; let mut b = S; let c; let d; // {a, b } |
| /// |
| /// if pred { |
| /// drop(a); // {a, b, } |
| /// b = S; // {a, b, } |
| /// |
| /// } else { |
| /// drop(b); // {a, b, } |
| /// d = S; // {a, b, d } |
| /// |
| /// } // {a, b, d } |
| /// |
| /// c = S; // {a, b, c, d } |
| /// } |
| /// ``` |
| pub struct EverInitializedPlaces<'a, 'tcx> { |
| body: &'a Body<'tcx>, |
| move_data: &'a MoveData<'tcx>, |
| } |
| |
| impl<'a, 'tcx> EverInitializedPlaces<'a, 'tcx> { |
| pub fn new(body: &'a Body<'tcx>, move_data: &'a MoveData<'tcx>) -> Self { |
| EverInitializedPlaces { body, move_data } |
| } |
| } |
| |
| impl<'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'_, 'tcx> { |
| fn move_data(&self) -> &MoveData<'tcx> { |
| self.move_data |
| } |
| } |
| |
| impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> { |
| fn update_bits( |
| state: &mut <Self as Analysis<'tcx>>::Domain, |
| path: MovePathIndex, |
| dfstate: DropFlagState, |
| ) { |
| match dfstate { |
| DropFlagState::Absent => state.kill(path), |
| DropFlagState::Present => state.gen_(path), |
| } |
| } |
| } |
| |
| impl<'tcx> MaybeUninitializedPlaces<'_, 'tcx> { |
| fn update_bits( |
| state: &mut <Self as Analysis<'tcx>>::Domain, |
| path: MovePathIndex, |
| dfstate: DropFlagState, |
| ) { |
| match dfstate { |
| DropFlagState::Absent => state.gen_(path), |
| DropFlagState::Present => state.kill(path), |
| } |
| } |
| } |
| |
| impl<'tcx> Analysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> { |
| /// There can be many more `MovePathIndex` than there are locals in a MIR body. |
| /// We use a mixed bitset to avoid paying too high a memory footprint. |
| type Domain = MaybeReachable<MixedBitSet<MovePathIndex>>; |
| |
| type SwitchIntData = MaybePlacesSwitchIntData<'tcx>; |
| |
| const NAME: &'static str = "maybe_init"; |
| |
| fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain { |
| // bottom = uninitialized |
| MaybeReachable::Unreachable |
| } |
| |
| fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) { |
| *state = |
| MaybeReachable::Reachable(MixedBitSet::new_empty(self.move_data().move_paths.len())); |
| drop_flag_effects_for_function_entry(self.body, self.move_data, |path, s| { |
| assert!(s == DropFlagState::Present); |
| state.gen_(path); |
| }); |
| } |
| |
| fn apply_primary_statement_effect( |
| &mut self, |
| state: &mut Self::Domain, |
| statement: &mir::Statement<'tcx>, |
| location: Location, |
| ) { |
| drop_flag_effects_for_location(self.body, self.move_data, location, |path, s| { |
| Self::update_bits(state, path, s) |
| }); |
| |
| // Mark all places as "maybe init" if they are mutably borrowed. See #90752. |
| if self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration |
| && let Some((_, rvalue)) = statement.kind.as_assign() |
| && let mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, place) |
| // FIXME: Does `&raw const foo` allow mutation? See #90413. |
| | mir::Rvalue::RawPtr(_, place) = rvalue |
| && let LookupResult::Exact(mpi) = self.move_data().rev_lookup.find(place.as_ref()) |
| { |
| on_all_children_bits(self.move_data(), mpi, |child| { |
| state.gen_(child); |
| }) |
| } |
| } |
| |
| fn apply_primary_terminator_effect<'mir>( |
| &mut self, |
| state: &mut Self::Domain, |
| terminator: &'mir mir::Terminator<'tcx>, |
| location: Location, |
| ) -> TerminatorEdges<'mir, 'tcx> { |
| // Note: `edges` must be computed first because `drop_flag_effects_for_location` can change |
| // the result of `is_unwind_dead`. |
| let mut edges = terminator.edges(); |
| if self.skip_unreachable_unwind |
| && let mir::TerminatorKind::Drop { |
| target, |
| unwind, |
| place, |
| replace: _, |
| drop: _, |
| async_fut: _, |
| } = terminator.kind |
| && matches!(unwind, mir::UnwindAction::Cleanup(_)) |
| && self.is_unwind_dead(place, state) |
| { |
| edges = TerminatorEdges::Single(target); |
| } |
| drop_flag_effects_for_location(self.body, self.move_data, location, |path, s| { |
| Self::update_bits(state, path, s) |
| }); |
| edges |
| } |
| |
| fn apply_call_return_effect( |
| &mut self, |
| state: &mut Self::Domain, |
| _block: mir::BasicBlock, |
| return_places: CallReturnPlaces<'_, 'tcx>, |
| ) { |
| return_places.for_each(|place| { |
| // when a call returns successfully, that means we need to set |
| // the bits for that dest_place to 1 (initialized). |
| on_lookup_result_bits( |
| self.move_data(), |
| self.move_data().rev_lookup.find(place.as_ref()), |
| |mpi| { |
| state.gen_(mpi); |
| }, |
| ); |
| }); |
| } |
| |
| fn get_switch_int_data( |
| &mut self, |
| block: mir::BasicBlock, |
| discr: &mir::Operand<'tcx>, |
| ) -> Option<Self::SwitchIntData> { |
| if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration { |
| return None; |
| } |
| |
| MaybePlacesSwitchIntData::new(self.tcx, self.body, block, discr) |
| } |
| |
| fn apply_switch_int_edge_effect( |
| &mut self, |
| data: &mut Self::SwitchIntData, |
| state: &mut Self::Domain, |
| value: SwitchTargetValue, |
| targets: &mir::SwitchTargets, |
| ) { |
| let inactive_variants = match value { |
| SwitchTargetValue::Normal(value) => InactiveVariants::Active(data.next_discr(value)), |
| SwitchTargetValue::Otherwise if self.exclude_inactive_in_otherwise => { |
| InactiveVariants::Inactives(data.variants(targets)) |
| } |
| _ => return, |
| }; |
| |
| // Kill all move paths that correspond to variants we know to be inactive along this |
| // particular outgoing edge of a `SwitchInt`. |
| drop_flag_effects::on_all_inactive_variants( |
| self.move_data, |
| data.enum_place, |
| &inactive_variants, |
| |mpi| state.kill(mpi), |
| ); |
| } |
| } |
| |
| /// There can be many more `MovePathIndex` than there are locals in a MIR body. |
| /// We use a mixed bitset to avoid paying too high a memory footprint. |
| pub type MaybeUninitializedPlacesDomain = MixedBitSet<MovePathIndex>; |
| |
| impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { |
| type Domain = MaybeUninitializedPlacesDomain; |
| |
| type SwitchIntData = MaybePlacesSwitchIntData<'tcx>; |
| |
| const NAME: &'static str = "maybe_uninit"; |
| |
| fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain { |
| // bottom = initialized (`initialize_start_block` overwrites this on first entry) |
| MixedBitSet::new_empty(self.move_data().move_paths.len()) |
| } |
| |
| // sets state bits for Arg places |
| fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) { |
| // set all bits to 1 (uninit) before gathering counter-evidence |
| state.insert_all(); |
| |
| drop_flag_effects_for_function_entry(self.body, self.move_data, |path, s| { |
| assert!(s == DropFlagState::Present); |
| state.remove(path); |
| }); |
| } |
| |
| fn apply_primary_statement_effect( |
| &mut self, |
| state: &mut Self::Domain, |
| _statement: &mir::Statement<'tcx>, |
| location: Location, |
| ) { |
| drop_flag_effects_for_location(self.body, self.move_data, location, |path, s| { |
| Self::update_bits(state, path, s) |
| }); |
| |
| // Unlike in `MaybeInitializedPlaces` above, we don't need to change the state when a |
| // mutable borrow occurs. Places cannot become uninitialized through a mutable reference. |
| } |
| |
| fn apply_primary_terminator_effect<'mir>( |
| &mut self, |
| state: &mut Self::Domain, |
| terminator: &'mir mir::Terminator<'tcx>, |
| location: Location, |
| ) -> TerminatorEdges<'mir, 'tcx> { |
| drop_flag_effects_for_location(self.body, self.move_data, location, |path, s| { |
| Self::update_bits(state, path, s) |
| }); |
| if self.skip_unreachable_unwind.contains(location.block) { |
| let mir::TerminatorKind::Drop { target, unwind, .. } = terminator.kind else { bug!() }; |
| assert_matches!(unwind, mir::UnwindAction::Cleanup(_)); |
| TerminatorEdges::Single(target) |
| } else { |
| terminator.edges() |
| } |
| } |
| |
| fn apply_call_return_effect( |
| &mut self, |
| state: &mut Self::Domain, |
| _block: mir::BasicBlock, |
| return_places: CallReturnPlaces<'_, 'tcx>, |
| ) { |
| return_places.for_each(|place| { |
| // when a call returns successfully, that means we need to set |
| // the bits for that dest_place to 0 (initialized). |
| on_lookup_result_bits( |
| self.move_data(), |
| self.move_data().rev_lookup.find(place.as_ref()), |
| |mpi| { |
| state.kill(mpi); |
| }, |
| ); |
| }); |
| } |
| |
| fn get_switch_int_data( |
| &mut self, |
| block: mir::BasicBlock, |
| discr: &mir::Operand<'tcx>, |
| ) -> Option<Self::SwitchIntData> { |
| if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration { |
| return None; |
| } |
| |
| if !self.mark_inactive_variants_as_uninit { |
| return None; |
| } |
| |
| MaybePlacesSwitchIntData::new(self.tcx, self.body, block, discr) |
| } |
| |
| fn apply_switch_int_edge_effect( |
| &mut self, |
| data: &mut Self::SwitchIntData, |
| state: &mut Self::Domain, |
| value: SwitchTargetValue, |
| targets: &mir::SwitchTargets, |
| ) { |
| let inactive_variants = match value { |
| SwitchTargetValue::Normal(value) => InactiveVariants::Active(data.next_discr(value)), |
| SwitchTargetValue::Otherwise if self.include_inactive_in_otherwise => { |
| InactiveVariants::Inactives(data.variants(targets)) |
| } |
| _ => return, |
| }; |
| |
| // Mark all move paths that correspond to variants other than this one as maybe |
| // uninitialized (in reality, they are *definitely* uninitialized). |
| drop_flag_effects::on_all_inactive_variants( |
| self.move_data, |
| data.enum_place, |
| &inactive_variants, |
| |mpi| state.gen_(mpi), |
| ); |
| } |
| } |
| |
| /// There can be many more `InitIndex` than there are locals in a MIR body. |
| /// We use a mixed bitset to avoid paying too high a memory footprint. |
| pub type EverInitializedPlacesDomain = MixedBitSet<InitIndex>; |
| |
| impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> { |
| type Domain = EverInitializedPlacesDomain; |
| |
| const NAME: &'static str = "ever_init"; |
| |
| fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain { |
| // bottom = no initialized variables by default |
| MixedBitSet::new_empty(self.move_data().inits.len()) |
| } |
| |
| fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) { |
| for arg_init in 0..body.arg_count { |
| state.insert(InitIndex::new(arg_init)); |
| } |
| } |
| |
| #[instrument(skip(self, state), level = "debug")] |
| fn apply_primary_statement_effect( |
| &mut self, |
| state: &mut Self::Domain, |
| stmt: &mir::Statement<'tcx>, |
| location: Location, |
| ) { |
| let move_data = self.move_data(); |
| let init_path_map = &move_data.init_path_map; |
| let init_loc_map = &move_data.init_loc_map; |
| let rev_lookup = &move_data.rev_lookup; |
| |
| debug!("initializes move_indexes {:?}", init_loc_map[location]); |
| state.gen_all(init_loc_map[location].iter().copied()); |
| |
| if let mir::StatementKind::StorageDead(local) = stmt.kind { |
| // End inits for StorageDead, so that an immutable variable can |
| // be reinitialized on the next iteration of the loop. |
| if let Some(move_path_index) = rev_lookup.find_local(local) { |
| debug!( |
| "clears the ever initialized status of {:?}", |
| init_path_map[move_path_index] |
| ); |
| state.kill_all(init_path_map[move_path_index].iter().copied()); |
| } |
| } |
| } |
| |
| #[instrument(skip(self, state, terminator), level = "debug")] |
| fn apply_primary_terminator_effect<'mir>( |
| &mut self, |
| state: &mut Self::Domain, |
| terminator: &'mir mir::Terminator<'tcx>, |
| location: Location, |
| ) -> TerminatorEdges<'mir, 'tcx> { |
| let (body, move_data) = (self.body, self.move_data()); |
| let term = body[location.block].terminator(); |
| let init_loc_map = &move_data.init_loc_map; |
| debug!(?term); |
| debug!("initializes move_indexes {:?}", init_loc_map[location]); |
| state.gen_all( |
| init_loc_map[location] |
| .iter() |
| .filter(|init_index| { |
| move_data.inits[**init_index].kind != InitKind::NonPanicPathOnly |
| }) |
| .copied(), |
| ); |
| terminator.edges() |
| } |
| |
| fn apply_call_return_effect( |
| &mut self, |
| state: &mut Self::Domain, |
| block: mir::BasicBlock, |
| _return_places: CallReturnPlaces<'_, 'tcx>, |
| ) { |
| let move_data = self.move_data(); |
| let init_loc_map = &move_data.init_loc_map; |
| |
| let call_loc = self.body.terminator_loc(block); |
| for init_index in &init_loc_map[call_loc] { |
| state.gen_(*init_index); |
| } |
| } |
| } |