compiler/rustc_borrowck/src/dataflow.rs RUST 572 lines View on github.com → Search inside
1use rustc_data_structures::fx::FxIndexMap;2use rustc_index::bit_set::{DenseBitSet, MixedBitSet};3use rustc_middle::mir::{self, BasicBlock, Body, CallReturnPlaces, Location, Place};4use rustc_middle::ty::{RegionVid, TyCtxt};5use rustc_mir_dataflow::fmt::DebugWithContext;6use rustc_mir_dataflow::impls::{7    EverInitializedPlaces, EverInitializedPlacesDomain, MaybeUninitializedPlaces,8    MaybeUninitializedPlacesDomain,9};10use rustc_mir_dataflow::{Analysis, GenKill, JoinSemiLattice};11use tracing::debug;1213use crate::{BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext, places_conflict};1415// This analysis is different to most others. Its results aren't computed with16// `iterate_to_fixpoint`, but are instead composed from the results of three sub-analyses that are17// computed individually with `iterate_to_fixpoint`. Because it's faster that way than having a18// single analysis where the domain has three components.19pub(crate) struct Borrowck<'a, 'tcx> {20    pub(crate) borrows: Borrows<'a, 'tcx>,21    pub(crate) uninits: MaybeUninitializedPlaces<'a, 'tcx>,22    pub(crate) ever_inits: EverInitializedPlaces<'a, 'tcx>,23}2425impl<'a, 'tcx> Analysis<'tcx> for Borrowck<'a, 'tcx> {26    type Domain = BorrowckDomain;2728    const NAME: &'static str = "borrowck";2930    fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {31        BorrowckDomain {32            borrows: self.borrows.bottom_value(body),33            uninits: self.uninits.bottom_value(body),34            ever_inits: self.ever_inits.bottom_value(body),35        }36    }3738    fn initialize_start_block(&self, _body: &mir::Body<'tcx>, _state: &mut Self::Domain) {39        // This is only reachable from `iterate_to_fixpoint`, which this analysis doesn't use.40        unreachable!();41    }4243    fn apply_early_statement_effect(44        &self,45        state: &mut Self::Domain,46        stmt: &mir::Statement<'tcx>,47        loc: Location,48    ) {49        self.borrows.apply_early_statement_effect(&mut state.borrows, stmt, loc);50        self.uninits.apply_early_statement_effect(&mut state.uninits, stmt, loc);51        self.ever_inits.apply_early_statement_effect(&mut state.ever_inits, stmt, loc);52    }5354    fn apply_primary_statement_effect(55        &self,56        state: &mut Self::Domain,57        stmt: &mir::Statement<'tcx>,58        loc: Location,59    ) {60        self.borrows.apply_primary_statement_effect(&mut state.borrows, stmt, loc);61        self.uninits.apply_primary_statement_effect(&mut state.uninits, stmt, loc);62        self.ever_inits.apply_primary_statement_effect(&mut state.ever_inits, stmt, loc);63    }6465    fn apply_early_terminator_effect(66        &self,67        state: &mut Self::Domain,68        term: &mir::Terminator<'tcx>,69        loc: Location,70    ) {71        self.borrows.apply_early_terminator_effect(&mut state.borrows, term, loc);72        self.uninits.apply_early_terminator_effect(&mut state.uninits, term, loc);73        self.ever_inits.apply_early_terminator_effect(&mut state.ever_inits, term, loc);74    }7576    fn apply_primary_terminator_effect(77        &self,78        state: &mut Self::Domain,79        term: &mir::Terminator<'tcx>,80        loc: Location,81    ) {82        self.borrows.apply_primary_terminator_effect(&mut state.borrows, term, loc);83        self.uninits.apply_primary_terminator_effect(&mut state.uninits, term, loc);84        self.ever_inits.apply_primary_terminator_effect(&mut state.ever_inits, term, loc);85    }8687    fn apply_call_return_effect(88        &self,89        _state: &mut Self::Domain,90        _block: BasicBlock,91        _return_places: CallReturnPlaces<'_, 'tcx>,92    ) {93        // This is only reachable from `iterate_to_fixpoint`, which this analysis doesn't use.94        unreachable!();95    }96}9798impl JoinSemiLattice for BorrowckDomain {99    fn join(&mut self, _other: &Self) -> bool {100        // This is only reachable from `iterate_to_fixpoint`, which this analysis doesn't use.101        unreachable!();102    }103}104105/// The transient state of the dataflow analyses used by the borrow checker.106#[derive(Clone, Debug, PartialEq, Eq)]107pub(crate) struct BorrowckDomain {108    pub(crate) borrows: BorrowsDomain,109    pub(crate) uninits: MaybeUninitializedPlacesDomain,110    pub(crate) ever_inits: EverInitializedPlacesDomain,111}112113rustc_index::newtype_index! {114    #[orderable]115    #[debug_format = "bw{}"]116    pub struct BorrowIndex {}117}118119/// `Borrows` stores the data used in the analyses that track the flow120/// of borrows.121///122/// It uniquely identifies every borrow (`Rvalue::Ref`) by a123/// `BorrowIndex`, and maps each such index to a `BorrowData`124/// describing the borrow. These indexes are used for representing the125/// borrows in compact bitvectors.126pub struct Borrows<'a, 'tcx> {127    tcx: TyCtxt<'tcx>,128    body: &'a Body<'tcx>,129    borrow_set: &'a BorrowSet<'tcx>,130    borrows_out_of_scope_at_location: FxIndexMap<Location, Vec<BorrowIndex>>,131}132133struct OutOfScopePrecomputer<'a, 'tcx> {134    visited: DenseBitSet<mir::BasicBlock>,135    visit_stack: Vec<mir::BasicBlock>,136    body: &'a Body<'tcx>,137    regioncx: &'a RegionInferenceContext<'tcx>,138    borrows_out_of_scope_at_location: FxIndexMap<Location, Vec<BorrowIndex>>,139}140141impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> {142    fn compute(143        body: &Body<'tcx>,144        regioncx: &RegionInferenceContext<'tcx>,145        borrow_set: &BorrowSet<'tcx>,146    ) -> FxIndexMap<Location, Vec<BorrowIndex>> {147        let mut prec = OutOfScopePrecomputer {148            visited: DenseBitSet::new_empty(body.basic_blocks.len()),149            visit_stack: vec![],150            body,151            regioncx,152            borrows_out_of_scope_at_location: FxIndexMap::default(),153        };154        for (borrow_index, borrow_data) in borrow_set.iter_enumerated() {155            let borrow_region = borrow_data.region;156            let location = borrow_data.reserve_location;157            prec.precompute_borrows_out_of_scope(borrow_index, borrow_region, location);158        }159160        prec.borrows_out_of_scope_at_location161    }162163    fn precompute_borrows_out_of_scope(164        &mut self,165        borrow_index: BorrowIndex,166        borrow_region: RegionVid,167        first_location: Location,168    ) {169        let first_block = first_location.block;170        let first_bb_data = &self.body.basic_blocks[first_block];171172        // This is the first block, we only want to visit it from the creation of the borrow at173        // `first_location`.174        let first_lo = first_location.statement_index;175        let first_hi = first_bb_data.statements.len();176177        if let Some(kill_stmt) = self.regioncx.first_non_contained_inclusive(178            borrow_region,179            first_block,180            first_lo,181            first_hi,182        ) {183            let kill_location = Location { block: first_block, statement_index: kill_stmt };184            // If region does not contain a point at the location, then add to list and skip185            // successor locations.186            debug!("borrow {:?} gets killed at {:?}", borrow_index, kill_location);187            self.borrows_out_of_scope_at_location188                .entry(kill_location)189                .or_default()190                .push(borrow_index);191192            // The borrow is already dead, there is no need to visit other blocks.193            return;194        }195196        // The borrow is not dead. Add successor BBs to the work list, if necessary.197        for succ_bb in first_bb_data.terminator().successors() {198            if self.visited.insert(succ_bb) {199                self.visit_stack.push(succ_bb);200            }201        }202203        // We may end up visiting `first_block` again. This is not an issue: we know at this point204        // that it does not kill the borrow in the `first_lo..=first_hi` range, so checking the205        // `0..first_lo` range and the `0..first_hi` range give the same result.206        while let Some(block) = self.visit_stack.pop() {207            let bb_data = &self.body[block];208            let num_stmts = bb_data.statements.len();209            if let Some(kill_stmt) =210                self.regioncx.first_non_contained_inclusive(borrow_region, block, 0, num_stmts)211            {212                let kill_location = Location { block, statement_index: kill_stmt };213                // If region does not contain a point at the location, then add to list and skip214                // successor locations.215                debug!("borrow {:?} gets killed at {:?}", borrow_index, kill_location);216                self.borrows_out_of_scope_at_location217                    .entry(kill_location)218                    .or_default()219                    .push(borrow_index);220221                // We killed the borrow, so we do not visit this block's successors.222                continue;223            }224225            // Add successor BBs to the work list, if necessary.226            for succ_bb in bb_data.terminator().successors() {227                if self.visited.insert(succ_bb) {228                    self.visit_stack.push(succ_bb);229                }230            }231        }232233        self.visited.clear();234    }235}236237// This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`.238pub fn calculate_borrows_out_of_scope_at_location<'tcx>(239    body: &Body<'tcx>,240    regioncx: &RegionInferenceContext<'tcx>,241    borrow_set: &BorrowSet<'tcx>,242) -> FxIndexMap<Location, Vec<BorrowIndex>> {243    OutOfScopePrecomputer::compute(body, regioncx, borrow_set)244}245246struct PoloniusOutOfScopePrecomputer<'a, 'tcx> {247    visited: DenseBitSet<mir::BasicBlock>,248    visit_stack: Vec<mir::BasicBlock>,249    body: &'a Body<'tcx>,250    regioncx: &'a RegionInferenceContext<'tcx>,251252    loans_out_of_scope_at_location: FxIndexMap<Location, Vec<BorrowIndex>>,253}254255impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> {256    fn compute(257        body: &Body<'tcx>,258        regioncx: &RegionInferenceContext<'tcx>,259        borrow_set: &BorrowSet<'tcx>,260    ) -> FxIndexMap<Location, Vec<BorrowIndex>> {261        // The in-tree polonius analysis computes loans going out of scope using the262        // set-of-loans model.263        let mut prec = PoloniusOutOfScopePrecomputer {264            visited: DenseBitSet::new_empty(body.basic_blocks.len()),265            visit_stack: vec![],266            body,267            regioncx,268            loans_out_of_scope_at_location: FxIndexMap::default(),269        };270        for (loan_idx, loan_data) in borrow_set.iter_enumerated() {271            let loan_issued_at = loan_data.reserve_location;272            prec.precompute_loans_out_of_scope(loan_idx, loan_issued_at);273        }274275        prec.loans_out_of_scope_at_location276    }277278    /// Loans are in scope while they are live: whether they are contained within any live region.279    /// In the location-insensitive analysis, a loan will be contained in a region if the issuing280    /// region can reach it in the subset graph. So this is a reachability problem.281    fn precompute_loans_out_of_scope(&mut self, loan_idx: BorrowIndex, loan_issued_at: Location) {282        let first_block = loan_issued_at.block;283        let first_bb_data = &self.body.basic_blocks[first_block];284285        // The first block we visit is the one where the loan is issued, starting from the statement286        // where the loan is issued: at `loan_issued_at`.287        let first_lo = loan_issued_at.statement_index;288        let first_hi = first_bb_data.statements.len();289290        if let Some(kill_location) =291            self.loan_kill_location(loan_idx, loan_issued_at, first_block, first_lo, first_hi)292        {293            debug!("loan {:?} gets killed at {:?}", loan_idx, kill_location);294            self.loans_out_of_scope_at_location.entry(kill_location).or_default().push(loan_idx);295296            // The loan dies within the first block, we're done and can early return.297            return;298        }299300        // The loan is not dead. Add successor BBs to the work list, if necessary.301        for succ_bb in first_bb_data.terminator().successors() {302            if self.visited.insert(succ_bb) {303                self.visit_stack.push(succ_bb);304            }305        }306307        // We may end up visiting `first_block` again. This is not an issue: we know at this point308        // that the loan is not killed in the `first_lo..=first_hi` range, so checking the309        // `0..first_lo` range and the `0..first_hi` range gives the same result.310        while let Some(block) = self.visit_stack.pop() {311            let bb_data = &self.body[block];312            let num_stmts = bb_data.statements.len();313            if let Some(kill_location) =314                self.loan_kill_location(loan_idx, loan_issued_at, block, 0, num_stmts)315            {316                debug!("loan {:?} gets killed at {:?}", loan_idx, kill_location);317                self.loans_out_of_scope_at_location318                    .entry(kill_location)319                    .or_default()320                    .push(loan_idx);321322                // The loan dies within this block, so we don't need to visit its successors.323                continue;324            }325326            // Add successor BBs to the work list, if necessary.327            for succ_bb in bb_data.terminator().successors() {328                if self.visited.insert(succ_bb) {329                    self.visit_stack.push(succ_bb);330                }331            }332        }333334        self.visited.clear();335        assert!(self.visit_stack.is_empty(), "visit stack should be empty");336    }337338    /// Returns the lowest statement in `start..=end`, where the loan goes out of scope, if any.339    /// This is the statement where the issuing region can't reach any of the regions that are live340    /// at this point.341    fn loan_kill_location(342        &self,343        loan_idx: BorrowIndex,344        loan_issued_at: Location,345        block: BasicBlock,346        start: usize,347        end: usize,348    ) -> Option<Location> {349        for statement_index in start..=end {350            let location = Location { block, statement_index };351352            // Check whether the issuing region can reach local regions that are live at this point:353            // - a loan is always live at its issuing location because it can reach the issuing354            // region, which is always live at this location.355            if location == loan_issued_at {356                continue;357            }358359            // - the loan goes out of scope at `location` if it's not contained within any regions360            // live at this point.361            //362            // FIXME: if the issuing region `i` can reach a live region `r` at point `p`, and `r` is363            // live at point `q`, then it's guaranteed that `i` would reach `r` at point `q`.364            // Reachability is location-insensitive, and we could take advantage of that, by jumping365            // to a further point than just the next statement: we can jump to the furthest point366            // within the block where `r` is live.367            if self.regioncx.is_loan_live_at(loan_idx, location) {368                continue;369            }370371            // No live region is reachable from the issuing region: the loan is killed at this372            // point.373            return Some(location);374        }375376        None377    }378}379380impl<'a, 'tcx> Borrows<'a, 'tcx> {381    pub fn new(382        tcx: TyCtxt<'tcx>,383        body: &'a Body<'tcx>,384        regioncx: &RegionInferenceContext<'tcx>,385        borrow_set: &'a BorrowSet<'tcx>,386    ) -> Self {387        let borrows_out_of_scope_at_location =388            if !tcx.sess.opts.unstable_opts.polonius.is_next_enabled() {389                calculate_borrows_out_of_scope_at_location(body, regioncx, borrow_set)390            } else {391                PoloniusOutOfScopePrecomputer::compute(body, regioncx, borrow_set)392            };393        Borrows { tcx, body, borrow_set, borrows_out_of_scope_at_location }394    }395396    /// Add all borrows to the kill set, if those borrows are out of scope at `location`.397    /// That means they went out of a nonlexical scope398    fn kill_loans_out_of_scope_at_location(399        &self,400        state: &mut <Self as Analysis<'tcx>>::Domain,401        location: Location,402    ) {403        // NOTE: The state associated with a given `location`404        // reflects the dataflow on entry to the statement.405        // Iterate over each of the borrows that we've precomputed406        // to have went out of scope at this location and kill them.407        //408        // We are careful always to call this function *before* we409        // set up the gen-bits for the statement or410        // terminator. That way, if the effect of the statement or411        // terminator *does* introduce a new loan of the same412        // region, then setting that gen-bit will override any413        // potential kill introduced here.414        if let Some(indices) = self.borrows_out_of_scope_at_location.get(&location) {415            state.kill_all(indices.iter().copied());416        }417    }418419    /// Kill any borrows that conflict with `place`.420    fn kill_borrows_on_place(421        &self,422        state: &mut <Self as Analysis<'tcx>>::Domain,423        place: Place<'tcx>,424    ) {425        debug!("kill_borrows_on_place: place={:?}", place);426427        let other_borrows_of_local = self428            .borrow_set429            .borrows_on_local(place.local)430            .map(|bs| bs.iter().copied())431            .into_flat_iter();432433        // If the borrowed place is a local with no projections, all other borrows of this434        // local must conflict. This is purely an optimization so we don't have to call435        // `places_conflict` for every borrow.436        if place.projection.is_empty() {437            if !self.body.local_decls[place.local].is_ref_to_static() {438                state.kill_all(other_borrows_of_local);439            }440            return;441        }442443        // By passing `PlaceConflictBias::NoOverlap`, we conservatively assume that any given444        // pair of array indices are not equal, so that when `places_conflict` returns true, we445        // will be assured that two places being compared definitely denotes the same sets of446        // locations.447        let definitely_conflicting_borrows = other_borrows_of_local.filter(|&i| {448            places_conflict(449                self.tcx,450                self.body,451                self.borrow_set[i].borrowed_place,452                place,453                PlaceConflictBias::NoOverlap,454            )455        });456457        state.kill_all(definitely_conflicting_borrows);458    }459}460461type BorrowsDomain = MixedBitSet<BorrowIndex>;462463/// Forward dataflow computation of the set of borrows that are in scope at a particular location.464/// - we gen the introduced loans465/// - we kill loans on locals going out of (regular) scope466/// - we kill the loans going out of their region's NLL scope: in NLL terms, the frontier where a467///   region stops containing the CFG points reachable from the issuing location.468/// - we also kill loans of conflicting places when overwriting a shared path: e.g. borrows of469///   `a.b.c` when `a` is overwritten.470impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for Borrows<'_, 'tcx> {471    type Domain = BorrowsDomain;472473    const NAME: &'static str = "borrows";474475    fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {476        // bottom = nothing is reserved or activated yet;477        MixedBitSet::new_empty(self.borrow_set.len())478    }479480    fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {481        // no borrows of code region_scopes have been taken prior to482        // function execution, so this method has no effect.483    }484485    fn apply_early_statement_effect(486        &self,487        state: &mut Self::Domain,488        _statement: &mir::Statement<'tcx>,489        location: Location,490    ) {491        self.kill_loans_out_of_scope_at_location(state, location);492    }493494    fn apply_primary_statement_effect(495        &self,496        state: &mut Self::Domain,497        stmt: &mir::Statement<'tcx>,498        location: Location,499    ) {500        match &stmt.kind {501            mir::StatementKind::Assign((lhs, rhs)) => {502                if let mir::Rvalue::Ref(_, _, place) | mir::Rvalue::Reborrow(_, _, place) = rhs {503                    if place.ignore_borrow(504                        self.tcx,505                        self.body,506                        &self.borrow_set.locals_state_at_exit(),507                    ) {508                        return;509                    }510                    let idxs =511                        self.borrow_set.borrows_at_location(&location).unwrap_or_else(|| {512                            panic!("could not find BorrowIndex for location {location:?}");513                        });514515                    for index in idxs {516                        state.gen_(*index);517                    }518                }519520                // Make sure there are no remaining borrows for variables521                // that are assigned over.522                self.kill_borrows_on_place(state, *lhs);523            }524525            mir::StatementKind::StorageDead(local) => {526                // Make sure there are no remaining borrows for locals that527                // are gone out of scope.528                self.kill_borrows_on_place(state, Place::from(*local));529            }530531            mir::StatementKind::FakeRead(..)532            | mir::StatementKind::SetDiscriminant { .. }533            | mir::StatementKind::StorageLive(..)534            | mir::StatementKind::PlaceMention(..)535            | mir::StatementKind::AscribeUserType(..)536            | mir::StatementKind::Coverage(..)537            | mir::StatementKind::Intrinsic(..)538            | mir::StatementKind::ConstEvalCounter539            | mir::StatementKind::BackwardIncompatibleDropHint { .. }540            | mir::StatementKind::Nop => {}541        }542    }543544    fn apply_early_terminator_effect(545        &self,546        state: &mut Self::Domain,547        _terminator: &mir::Terminator<'tcx>,548        location: Location,549    ) {550        self.kill_loans_out_of_scope_at_location(state, location);551    }552553    fn apply_primary_terminator_effect(554        &self,555        state: &mut Self::Domain,556        terminator: &mir::Terminator<'tcx>,557        _location: Location,558    ) {559        if let mir::TerminatorKind::InlineAsm { operands, .. } = &terminator.kind {560            for op in operands {561                if let mir::InlineAsmOperand::Out { place: Some(place), .. }562                | mir::InlineAsmOperand::InOut { out_place: Some(place), .. } = *op563                {564                    self.kill_borrows_on_place(state, place);565                }566            }567        }568    }569}570571impl<C> DebugWithContext<C> for BorrowIndex {}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.