| use std::assert_matches; | |
| use rustc_abi::VariantIdx; | |
| use rustc_data_structures::fx::FxIndexSet; | |
| use rustc_index::bit_set::{DenseBitSet, MixedBitSet}; | |
| use rustc_middle::bug; | |
| use rustc_middle::mir::{ | |
| self, BasicBlock, Body, CallReturnPlaces, Local, Location, StatementKind, TerminatorEdges, | |
| }; | |
| use rustc_middle::ty::{self, TyCtxt}; | |
| use smallvec::SmallVec; | |
| use tracing::instrument; | |
| use crate::drop_flag_effects::{DropFlagState, InactiveVariants}; | |
| use crate::move_paths::{ | |
| HasMoveData, Init, InitKind, InitLocation, LookupResult, MoveData, MovePathIndex, | |
| }; | |
| use crate::{ | |
| Analysis, GenKill, MaybeReachable, SwitchTargetIndex, 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>, | |
| // Variant indices targeted by the SwitchInt. For example, if you have: | |
| // ``` | |
| // enum E { A = 1, B = 3, C = 5, D = 7 } | |
| // ``` | |
| // and a `SwitchInt(A -> bb1, C -> bb2, _ -> bb3)`, this vec will contain `[0, 2]` because | |
| // those are the variant indices for `A` and `C`. | |
| variants: SmallVec<[VariantIdx; 4]>, | |
| } | |
| impl<'tcx> MaybePlacesSwitchIntData<'tcx> { | |
| fn new( | |
| tcx: TyCtxt<'tcx>, | |
| body: &Body<'tcx>, | |
| block: mir::BasicBlock, | |
| targets: &mir::SwitchTargets, | |
| 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((lhs, mir::Rvalue::Discriminant(enum_place))) | |
| if lhs == discr => | |
| { | |
| match enum_place.ty(body, tcx).ty.kind() { | |
| ty::Adt(enum_def, _) => { | |
| // For each value in the SwitchInt, find the VariantIdx for the variant | |
| // with that value. This works because `discriminant_vals` and | |
| // `targets.all_values()` are guaranteed to list variants in the same | |
| // AdtDef order. (If that ever changes the `expect` will panic.) | |
| let mut discriminants = enum_def.discriminants(tcx); | |
| let variants = targets | |
| .all_values() | |
| .iter() | |
| .map(|value| { | |
| // On each call to this closure `find` only consumes part of | |
| // the `discriminants` iterator. | |
| discriminants | |
| .find(|(_, discr)| discr.val == value.get()) | |
| .expect("SwitchInt vals should match a variant") | |
| .0 | |
| }) | |
| .collect(); | |
| return Some(MaybePlacesSwitchIntData { enum_place, variants }); | |
| } | |
| // `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; | |
| /// #[rustfmt::skip] | |
| /// fn foo(p: bool) { // maybe-init: | |
| /// // {p} | |
| /// let a = S; let mut b = S; let c; let d; // {p, a, b} | |
| /// | |
| /// if p { | |
| /// drop(a); // {p, b} | |
| /// b = S; // {p, b} | |
| /// | |
| /// } else { | |
| /// drop(b); // {p, a} | |
| /// d = S; // {p, a, d} | |
| /// | |
| /// } // {p, a, b, d} | |
| /// | |
| /// c = S; // {p, 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, | |
| } | |
| } | |
| } | |
| impl<'tcx> MaybeInitializedPlaces<'_, 'tcx> { | |
| /// 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 | |
| } | |
| } | |
| 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<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> { | |
| fn move_data(&self) -> &MoveData<'tcx> { | |
| self.move_data | |
| } | |
| } | |
| 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| { | |
| debug_assert!(s == DropFlagState::Present); | |
| state.gen_(path); | |
| }); | |
| } | |
| fn apply_primary_statement_effect( | |
| &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 get_terminator_edges<'mir>( | |
| &self, | |
| state: &Self::Domain, | |
| terminator: &'mir mir::Terminator<'tcx>, | |
| _location: Location, | |
| ) -> TerminatorEdges<'mir, 'tcx> { | |
| // Note: this relies on `get_terminator_edges` being called before | |
| // `apply_primary_terminator_effect` because the result of `is_unwind_dead` is affected by | |
| // the `drop_flag_effects_for_location` in `apply_primary_terminator_effect`. | |
| let mut edges = terminator.edges(); | |
| if self.skip_unreachable_unwind | |
| && let mir::TerminatorKind::Drop { target, unwind, place, replace: _, drop: _ } = | |
| terminator.kind | |
| && matches!(unwind, mir::UnwindAction::Cleanup(_)) | |
| && self.is_unwind_dead(place, state) | |
| { | |
| edges = TerminatorEdges::Single(target); | |
| } | |
| edges | |
| } | |
| fn apply_primary_terminator_effect( | |
| &self, | |
| state: &mut Self::Domain, | |
| _terminator: &mir::Terminator<'tcx>, | |
| location: Location, | |
| ) { | |
| drop_flag_effects_for_location(self.body, self.move_data, location, |path, s| { | |
| Self::update_bits(state, path, s) | |
| }); | |
| } | |
| fn apply_call_return_effect( | |
| &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( | |
| &self, | |
| block: mir::BasicBlock, | |
| targets: &mir::SwitchTargets, | |
| 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, targets, discr) | |
| } | |
| fn apply_switch_int_edge_effect( | |
| &self, | |
| state: &mut Self::Domain, | |
| data: &Self::SwitchIntData, | |
| target_idx: SwitchTargetIndex, | |
| ) { | |
| let inactive_variants = match target_idx { | |
| SwitchTargetIndex::Normal(target_idx) => { | |
| InactiveVariants::Active(data.variants[target_idx]) | |
| } | |
| SwitchTargetIndex::Otherwise if self.exclude_inactive_in_otherwise => { | |
| InactiveVariants::Inactives(data.variants.clone()) | |
| } | |
| _ => 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), | |
| ); | |
| } | |
| } | |
| /// `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; | |
| /// #[rustfmt::skip] | |
| /// fn foo(p: bool) { // maybe-uninit: | |
| /// // {a, b, c, d} | |
| /// let a = S; let mut b = S; let c; let d; // { c, d} | |
| /// | |
| /// if p { | |
| /// 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, | |
| 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, | |
| skip_unreachable_unwind: DenseBitSet::new_empty(body.basic_blocks.len()), | |
| } | |
| } | |
| } | |
| impl<'tcx> MaybeUninitializedPlaces<'_, 'tcx> { | |
| /// 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 | |
| } | |
| pub fn skipping_unreachable_unwind( | |
| mut self, | |
| unreachable_unwind: DenseBitSet<mir::BasicBlock>, | |
| ) -> Self { | |
| self.skip_unreachable_unwind = unreachable_unwind; | |
| self | |
| } | |
| 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> HasMoveData<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { | |
| fn move_data(&self) -> &MoveData<'tcx> { | |
| self.move_data | |
| } | |
| } | |
| /// 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| { | |
| debug_assert!(s == DropFlagState::Present); | |
| state.remove(path); | |
| }); | |
| } | |
| fn apply_primary_statement_effect( | |
| &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 get_terminator_edges<'mir>( | |
| &self, | |
| _state: &Self::Domain, | |
| terminator: &'mir mir::Terminator<'tcx>, | |
| location: Location, | |
| ) -> TerminatorEdges<'mir, 'tcx> { | |
| 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_primary_terminator_effect( | |
| &self, | |
| state: &mut Self::Domain, | |
| _terminator: &mir::Terminator<'tcx>, | |
| location: Location, | |
| ) { | |
| drop_flag_effects_for_location(self.body, self.move_data, location, |path, s| { | |
| Self::update_bits(state, path, s) | |
| }); | |
| } | |
| fn apply_call_return_effect( | |
| &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( | |
| &self, | |
| block: mir::BasicBlock, | |
| targets: &mir::SwitchTargets, | |
| 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, targets, discr) | |
| } | |
| fn apply_switch_int_edge_effect( | |
| &self, | |
| state: &mut Self::Domain, | |
| data: &Self::SwitchIntData, | |
| target_idx: SwitchTargetIndex, | |
| ) { | |
| let inactive_variants = match target_idx { | |
| SwitchTargetIndex::Normal(target_idx) => { | |
| InactiveVariants::Active(data.variants[target_idx]) | |
| } | |
| SwitchTargetIndex::Otherwise => InactiveVariants::Inactives(data.variants.clone()), | |
| }; | |
| // 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), | |
| ); | |
| } | |
| } | |
| /// `EverInitializedPlaces` tracks all initializations of locals that may have | |
| /// occurred 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; | |
| /// #[rustfmt::skip] | |
| /// fn foo(p: bool) { // ever-init: | |
| /// // {p, } | |
| /// let a = S; let mut b = S; let c; let d; // {p, a, b, } | |
| /// | |
| /// if p { | |
| /// drop(a); // {p, a, b, } | |
| /// b = S; // {p, a, b, } | |
| /// | |
| /// } else { | |
| /// drop(b); // {p, a, b, } | |
| /// d = S; // {p, a, b, d} | |
| /// | |
| /// } // {p, a, b, d} | |
| /// | |
| /// c = S; // {p, 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 EverInitializedPlaces<'_, '_> { | |
| /// Whether the init of `local` at `init` can reach `target` via a path that doesn't pass | |
| /// through a `StorageDead(local)`. Mirrors the gen/kill structure of `EverInitializedPlaces`. | |
| pub fn init_reaches_location( | |
| body: &Body<'_>, | |
| local: Local, | |
| init: Init, | |
| target: Location, | |
| ) -> bool { | |
| let init_loc = match init.location { | |
| // Arguments are initialized on entry, and `StorageDead` is never emitted for them, so | |
| // they reach every location. | |
| InitLocation::Argument(_) => return true, | |
| InitLocation::Statement(init_loc) => init_loc, | |
| }; | |
| // Worklist of locations to walk forward from, seeded with the location(s) following `init`. | |
| let mut queue = vec![]; | |
| let basic_blocks = &body.basic_blocks; | |
| let init_block_data = &basic_blocks[init_loc.block]; | |
| if init_loc.statement_index < init_block_data.statements.len() { | |
| // This case mirrors `apply_primary_statement_effect`. | |
| queue.push(init_loc.successor_within_block()); | |
| } else if init.kind == InitKind::NonPanicPathOnly { | |
| // This case mirrors `apply_call_return_effect`. | |
| let TerminatorEdges::AssignOnReturn { return_, .. } = | |
| init_block_data.terminator().edges() | |
| else { | |
| bug!("`NonPanicPathOnly` should only be seen on terminators with return edges"); | |
| }; | |
| queue.extend(return_.into_iter().map(BasicBlock::start_location)); | |
| } else { | |
| // This case mirrors `apply_primary_terminator_effect`. | |
| queue.extend(init_block_data.terminator().successors().map(BasicBlock::start_location)); | |
| } | |
| let mut visited = FxIndexSet::default(); | |
| 'outer: while let Some(loc) = queue.pop() { | |
| if !visited.insert(loc) { | |
| continue; | |
| } | |
| // Walk from `loc` to the end of its block, looking for `target` or a kill. | |
| let block_data = &basic_blocks[loc.block]; | |
| for statement_index in loc.statement_index..=block_data.statements.len() { | |
| if target == (Location { block: loc.block, statement_index }) { | |
| return true; | |
| } | |
| if let Some(stmt) = block_data.statements.get(statement_index) | |
| && let StatementKind::StorageDead(dead) = stmt.kind | |
| && dead == local | |
| { | |
| continue 'outer; | |
| } | |
| } | |
| queue.extend(block_data.terminator().successors().map(BasicBlock::start_location)); | |
| } | |
| false | |
| } | |
| } | |
| impl<'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'_, 'tcx> { | |
| fn move_data(&self) -> &MoveData<'tcx> { | |
| self.move_data | |
| } | |
| } | |
| pub type EverInitializedPlacesDomain = DenseBitSet<Local>; | |
| impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> { | |
| type Domain = EverInitializedPlacesDomain; | |
| const NAME: &'static str = "ever_init"; | |
| fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain { | |
| // bottom = no initialized locals by default | |
| DenseBitSet::new_empty(body.local_decls.len()) | |
| } | |
| fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) { | |
| for arg in body.args_iter() { | |
| state.insert(arg); | |
| } | |
| } | |
| #[instrument(skip(self, state), level = "debug")] | |
| fn apply_primary_statement_effect( | |
| &self, | |
| state: &mut Self::Domain, | |
| stmt: &mir::Statement<'tcx>, | |
| location: Location, | |
| ) { | |
| let move_data = self.move_data(); | |
| let init_loc_map = &move_data.init_loc_map; | |
| // Record inits of locals. Projections can be ignored. | |
| state.gen_all(init_loc_map[location].iter().copied().filter_map(|ii| { | |
| let init_mpi = move_data.inits[ii].path; | |
| move_data.move_paths[init_mpi].place.as_local() | |
| })); | |
| // Kill on StorageDead, so that an immutable variable can | |
| // be reinitialized on the next iteration of the loop. | |
| if let mir::StatementKind::StorageDead(local) = stmt.kind { | |
| state.kill(local); | |
| } | |
| } | |
| #[instrument(skip(self, state, _terminator), level = "debug")] | |
| fn apply_primary_terminator_effect( | |
| &self, | |
| state: &mut Self::Domain, | |
| _terminator: &mir::Terminator<'tcx>, | |
| location: Location, | |
| ) { | |
| let move_data = self.move_data(); | |
| let init_loc_map = &move_data.init_loc_map; | |
| // Record inits of locals. Projections can be ignored. | |
| state.gen_all(init_loc_map[location].iter().copied().filter_map(|ii| { | |
| let init = &move_data.inits[ii]; | |
| if init.kind != InitKind::NonPanicPathOnly { | |
| move_data.move_paths[init.path].place.as_local() | |
| } else { | |
| None | |
| } | |
| })); | |
| } | |
| fn apply_call_return_effect( | |
| &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; | |
| // Record inits of locals. Projections can be ignored. | |
| let call_loc = self.body.terminator_loc(block); | |
| state.gen_all(init_loc_map[call_loc].iter().copied().filter_map(|ii| { | |
| let init = &move_data.inits[ii]; | |
| if init.kind == InitKind::NonPanicPathOnly { | |
| move_data.move_paths[init.path].place.as_local() | |
| } else { | |
| None | |
| } | |
| })); | |
| } | |
| } |