rustc_borrowck/
dataflow.rs

1use std::fmt;
2
3use rustc_data_structures::fx::FxIndexMap;
4use rustc_index::bit_set::{DenseBitSet, MixedBitSet};
5use rustc_middle::mir::{
6    self, BasicBlock, Body, CallReturnPlaces, Location, Place, TerminatorEdges,
7};
8use rustc_middle::ty::{RegionVid, TyCtxt};
9use rustc_mir_dataflow::fmt::DebugWithContext;
10use rustc_mir_dataflow::impls::{
11    EverInitializedPlaces, EverInitializedPlacesDomain, MaybeUninitializedPlaces,
12    MaybeUninitializedPlacesDomain,
13};
14use rustc_mir_dataflow::{Analysis, GenKill, JoinSemiLattice};
15use tracing::debug;
16
17use crate::{BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext, places_conflict};
18
19// This analysis is different to most others. Its results aren't computed with
20// `iterate_to_fixpoint`, but are instead composed from the results of three sub-analyses that are
21// computed individually with `iterate_to_fixpoint`.
22pub(crate) struct Borrowck<'a, 'tcx> {
23    pub(crate) borrows: Borrows<'a, 'tcx>,
24    pub(crate) uninits: MaybeUninitializedPlaces<'a, 'tcx>,
25    pub(crate) ever_inits: EverInitializedPlaces<'a, 'tcx>,
26}
27
28impl<'a, 'tcx> Analysis<'tcx> for Borrowck<'a, 'tcx> {
29    type Domain = BorrowckDomain;
30
31    const NAME: &'static str = "borrowck";
32
33    fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
34        BorrowckDomain {
35            borrows: self.borrows.bottom_value(body),
36            uninits: self.uninits.bottom_value(body),
37            ever_inits: self.ever_inits.bottom_value(body),
38        }
39    }
40
41    fn initialize_start_block(&self, _body: &mir::Body<'tcx>, _state: &mut Self::Domain) {
42        // This is only reachable from `iterate_to_fixpoint`, which this analysis doesn't use.
43        unreachable!();
44    }
45
46    fn apply_early_statement_effect(
47        &mut self,
48        state: &mut Self::Domain,
49        stmt: &mir::Statement<'tcx>,
50        loc: Location,
51    ) {
52        self.borrows.apply_early_statement_effect(&mut state.borrows, stmt, loc);
53        self.uninits.apply_early_statement_effect(&mut state.uninits, stmt, loc);
54        self.ever_inits.apply_early_statement_effect(&mut state.ever_inits, stmt, loc);
55    }
56
57    fn apply_primary_statement_effect(
58        &mut self,
59        state: &mut Self::Domain,
60        stmt: &mir::Statement<'tcx>,
61        loc: Location,
62    ) {
63        self.borrows.apply_primary_statement_effect(&mut state.borrows, stmt, loc);
64        self.uninits.apply_primary_statement_effect(&mut state.uninits, stmt, loc);
65        self.ever_inits.apply_primary_statement_effect(&mut state.ever_inits, stmt, loc);
66    }
67
68    fn apply_early_terminator_effect(
69        &mut self,
70        state: &mut Self::Domain,
71        term: &mir::Terminator<'tcx>,
72        loc: Location,
73    ) {
74        self.borrows.apply_early_terminator_effect(&mut state.borrows, term, loc);
75        self.uninits.apply_early_terminator_effect(&mut state.uninits, term, loc);
76        self.ever_inits.apply_early_terminator_effect(&mut state.ever_inits, term, loc);
77    }
78
79    fn apply_primary_terminator_effect<'mir>(
80        &mut self,
81        state: &mut Self::Domain,
82        term: &'mir mir::Terminator<'tcx>,
83        loc: Location,
84    ) -> TerminatorEdges<'mir, 'tcx> {
85        self.borrows.apply_primary_terminator_effect(&mut state.borrows, term, loc);
86        self.uninits.apply_primary_terminator_effect(&mut state.uninits, term, loc);
87        self.ever_inits.apply_primary_terminator_effect(&mut state.ever_inits, term, loc);
88
89        // This return value doesn't matter. It's only used by `iterate_to_fixpoint`, which this
90        // analysis doesn't use.
91        TerminatorEdges::None
92    }
93
94    fn apply_call_return_effect(
95        &mut self,
96        _state: &mut Self::Domain,
97        _block: BasicBlock,
98        _return_places: CallReturnPlaces<'_, 'tcx>,
99    ) {
100        // This is only reachable from `iterate_to_fixpoint`, which this analysis doesn't use.
101        unreachable!();
102    }
103}
104
105impl JoinSemiLattice for BorrowckDomain {
106    fn join(&mut self, _other: &Self) -> bool {
107        // This is only reachable from `iterate_to_fixpoint`, which this analysis doesn't use.
108        unreachable!();
109    }
110}
111
112impl<'tcx, C> DebugWithContext<C> for BorrowckDomain
113where
114    C: rustc_mir_dataflow::move_paths::HasMoveData<'tcx>,
115{
116    fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        f.write_str("borrows: ")?;
118        self.borrows.fmt_with(ctxt, f)?;
119        f.write_str(" uninits: ")?;
120        self.uninits.fmt_with(ctxt, f)?;
121        f.write_str(" ever_inits: ")?;
122        self.ever_inits.fmt_with(ctxt, f)?;
123        Ok(())
124    }
125
126    fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        if self == old {
128            return Ok(());
129        }
130
131        if self.borrows != old.borrows {
132            f.write_str("borrows: ")?;
133            self.borrows.fmt_diff_with(&old.borrows, ctxt, f)?;
134            f.write_str("\n")?;
135        }
136
137        if self.uninits != old.uninits {
138            f.write_str("uninits: ")?;
139            self.uninits.fmt_diff_with(&old.uninits, ctxt, f)?;
140            f.write_str("\n")?;
141        }
142
143        if self.ever_inits != old.ever_inits {
144            f.write_str("ever_inits: ")?;
145            self.ever_inits.fmt_diff_with(&old.ever_inits, ctxt, f)?;
146            f.write_str("\n")?;
147        }
148
149        Ok(())
150    }
151}
152
153/// The transient state of the dataflow analyses used by the borrow checker.
154#[derive(Clone, Debug, PartialEq, Eq)]
155pub(crate) struct BorrowckDomain {
156    pub(crate) borrows: BorrowsDomain,
157    pub(crate) uninits: MaybeUninitializedPlacesDomain,
158    pub(crate) ever_inits: EverInitializedPlacesDomain,
159}
160
161rustc_index::newtype_index! {
162    #[orderable]
163    #[debug_format = "bw{}"]
164    pub struct BorrowIndex {}
165}
166
167/// `Borrows` stores the data used in the analyses that track the flow
168/// of borrows.
169///
170/// It uniquely identifies every borrow (`Rvalue::Ref`) by a
171/// `BorrowIndex`, and maps each such index to a `BorrowData`
172/// describing the borrow. These indexes are used for representing the
173/// borrows in compact bitvectors.
174pub struct Borrows<'a, 'tcx> {
175    tcx: TyCtxt<'tcx>,
176    body: &'a Body<'tcx>,
177    borrow_set: &'a BorrowSet<'tcx>,
178    borrows_out_of_scope_at_location: FxIndexMap<Location, Vec<BorrowIndex>>,
179}
180
181struct OutOfScopePrecomputer<'a, 'tcx> {
182    visited: DenseBitSet<mir::BasicBlock>,
183    visit_stack: Vec<mir::BasicBlock>,
184    body: &'a Body<'tcx>,
185    regioncx: &'a RegionInferenceContext<'tcx>,
186    borrows_out_of_scope_at_location: FxIndexMap<Location, Vec<BorrowIndex>>,
187}
188
189impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> {
190    fn compute(
191        body: &Body<'tcx>,
192        regioncx: &RegionInferenceContext<'tcx>,
193        borrow_set: &BorrowSet<'tcx>,
194    ) -> FxIndexMap<Location, Vec<BorrowIndex>> {
195        let mut prec = OutOfScopePrecomputer {
196            visited: DenseBitSet::new_empty(body.basic_blocks.len()),
197            visit_stack: vec![],
198            body,
199            regioncx,
200            borrows_out_of_scope_at_location: FxIndexMap::default(),
201        };
202        for (borrow_index, borrow_data) in borrow_set.iter_enumerated() {
203            let borrow_region = borrow_data.region;
204            let location = borrow_data.reserve_location;
205            prec.precompute_borrows_out_of_scope(borrow_index, borrow_region, location);
206        }
207
208        prec.borrows_out_of_scope_at_location
209    }
210
211    fn precompute_borrows_out_of_scope(
212        &mut self,
213        borrow_index: BorrowIndex,
214        borrow_region: RegionVid,
215        first_location: Location,
216    ) {
217        let first_block = first_location.block;
218        let first_bb_data = &self.body.basic_blocks[first_block];
219
220        // This is the first block, we only want to visit it from the creation of the borrow at
221        // `first_location`.
222        let first_lo = first_location.statement_index;
223        let first_hi = first_bb_data.statements.len();
224
225        if let Some(kill_stmt) = self.regioncx.first_non_contained_inclusive(
226            borrow_region,
227            first_block,
228            first_lo,
229            first_hi,
230        ) {
231            let kill_location = Location { block: first_block, statement_index: kill_stmt };
232            // If region does not contain a point at the location, then add to list and skip
233            // successor locations.
234            debug!("borrow {:?} gets killed at {:?}", borrow_index, kill_location);
235            self.borrows_out_of_scope_at_location
236                .entry(kill_location)
237                .or_default()
238                .push(borrow_index);
239
240            // The borrow is already dead, there is no need to visit other blocks.
241            return;
242        }
243
244        // The borrow is not dead. Add successor BBs to the work list, if necessary.
245        for succ_bb in first_bb_data.terminator().successors() {
246            if self.visited.insert(succ_bb) {
247                self.visit_stack.push(succ_bb);
248            }
249        }
250
251        // We may end up visiting `first_block` again. This is not an issue: we know at this point
252        // that it does not kill the borrow in the `first_lo..=first_hi` range, so checking the
253        // `0..first_lo` range and the `0..first_hi` range give the same result.
254        while let Some(block) = self.visit_stack.pop() {
255            let bb_data = &self.body[block];
256            let num_stmts = bb_data.statements.len();
257            if let Some(kill_stmt) =
258                self.regioncx.first_non_contained_inclusive(borrow_region, block, 0, num_stmts)
259            {
260                let kill_location = Location { block, statement_index: kill_stmt };
261                // If region does not contain a point at the location, then add to list and skip
262                // successor locations.
263                debug!("borrow {:?} gets killed at {:?}", borrow_index, kill_location);
264                self.borrows_out_of_scope_at_location
265                    .entry(kill_location)
266                    .or_default()
267                    .push(borrow_index);
268
269                // We killed the borrow, so we do not visit this block's successors.
270                continue;
271            }
272
273            // Add successor BBs to the work list, if necessary.
274            for succ_bb in bb_data.terminator().successors() {
275                if self.visited.insert(succ_bb) {
276                    self.visit_stack.push(succ_bb);
277                }
278            }
279        }
280
281        self.visited.clear();
282    }
283}
284
285// This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`.
286pub fn calculate_borrows_out_of_scope_at_location<'tcx>(
287    body: &Body<'tcx>,
288    regioncx: &RegionInferenceContext<'tcx>,
289    borrow_set: &BorrowSet<'tcx>,
290) -> FxIndexMap<Location, Vec<BorrowIndex>> {
291    OutOfScopePrecomputer::compute(body, regioncx, borrow_set)
292}
293
294struct PoloniusOutOfScopePrecomputer<'a, 'tcx> {
295    visited: DenseBitSet<mir::BasicBlock>,
296    visit_stack: Vec<mir::BasicBlock>,
297    body: &'a Body<'tcx>,
298    regioncx: &'a RegionInferenceContext<'tcx>,
299
300    loans_out_of_scope_at_location: FxIndexMap<Location, Vec<BorrowIndex>>,
301}
302
303impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> {
304    fn compute(
305        body: &Body<'tcx>,
306        regioncx: &RegionInferenceContext<'tcx>,
307        borrow_set: &BorrowSet<'tcx>,
308    ) -> FxIndexMap<Location, Vec<BorrowIndex>> {
309        // The in-tree polonius analysis computes loans going out of scope using the
310        // set-of-loans model.
311        let mut prec = PoloniusOutOfScopePrecomputer {
312            visited: DenseBitSet::new_empty(body.basic_blocks.len()),
313            visit_stack: vec![],
314            body,
315            regioncx,
316            loans_out_of_scope_at_location: FxIndexMap::default(),
317        };
318        for (loan_idx, loan_data) in borrow_set.iter_enumerated() {
319            let loan_issued_at = loan_data.reserve_location;
320            prec.precompute_loans_out_of_scope(loan_idx, loan_issued_at);
321        }
322
323        prec.loans_out_of_scope_at_location
324    }
325
326    /// Loans are in scope while they are live: whether they are contained within any live region.
327    /// In the location-insensitive analysis, a loan will be contained in a region if the issuing
328    /// region can reach it in the subset graph. So this is a reachability problem.
329    fn precompute_loans_out_of_scope(&mut self, loan_idx: BorrowIndex, loan_issued_at: Location) {
330        let first_block = loan_issued_at.block;
331        let first_bb_data = &self.body.basic_blocks[first_block];
332
333        // The first block we visit is the one where the loan is issued, starting from the statement
334        // where the loan is issued: at `loan_issued_at`.
335        let first_lo = loan_issued_at.statement_index;
336        let first_hi = first_bb_data.statements.len();
337
338        if let Some(kill_location) =
339            self.loan_kill_location(loan_idx, loan_issued_at, first_block, first_lo, first_hi)
340        {
341            debug!("loan {:?} gets killed at {:?}", loan_idx, kill_location);
342            self.loans_out_of_scope_at_location.entry(kill_location).or_default().push(loan_idx);
343
344            // The loan dies within the first block, we're done and can early return.
345            return;
346        }
347
348        // The loan is not dead. Add successor BBs to the work list, if necessary.
349        for succ_bb in first_bb_data.terminator().successors() {
350            if self.visited.insert(succ_bb) {
351                self.visit_stack.push(succ_bb);
352            }
353        }
354
355        // We may end up visiting `first_block` again. This is not an issue: we know at this point
356        // that the loan is not killed in the `first_lo..=first_hi` range, so checking the
357        // `0..first_lo` range and the `0..first_hi` range gives the same result.
358        while let Some(block) = self.visit_stack.pop() {
359            let bb_data = &self.body[block];
360            let num_stmts = bb_data.statements.len();
361            if let Some(kill_location) =
362                self.loan_kill_location(loan_idx, loan_issued_at, block, 0, num_stmts)
363            {
364                debug!("loan {:?} gets killed at {:?}", loan_idx, kill_location);
365                self.loans_out_of_scope_at_location
366                    .entry(kill_location)
367                    .or_default()
368                    .push(loan_idx);
369
370                // The loan dies within this block, so we don't need to visit its successors.
371                continue;
372            }
373
374            // Add successor BBs to the work list, if necessary.
375            for succ_bb in bb_data.terminator().successors() {
376                if self.visited.insert(succ_bb) {
377                    self.visit_stack.push(succ_bb);
378                }
379            }
380        }
381
382        self.visited.clear();
383        assert!(self.visit_stack.is_empty(), "visit stack should be empty");
384    }
385
386    /// Returns the lowest statement in `start..=end`, where the loan goes out of scope, if any.
387    /// This is the statement where the issuing region can't reach any of the regions that are live
388    /// at this point.
389    fn loan_kill_location(
390        &self,
391        loan_idx: BorrowIndex,
392        loan_issued_at: Location,
393        block: BasicBlock,
394        start: usize,
395        end: usize,
396    ) -> Option<Location> {
397        for statement_index in start..=end {
398            let location = Location { block, statement_index };
399
400            // Check whether the issuing region can reach local regions that are live at this point:
401            // - a loan is always live at its issuing location because it can reach the issuing
402            // region, which is always live at this location.
403            if location == loan_issued_at {
404                continue;
405            }
406
407            // - the loan goes out of scope at `location` if it's not contained within any regions
408            // live at this point.
409            //
410            // FIXME: if the issuing region `i` can reach a live region `r` at point `p`, and `r` is
411            // live at point `q`, then it's guaranteed that `i` would reach `r` at point `q`.
412            // Reachability is location-insensitive, and we could take advantage of that, by jumping
413            // to a further point than just the next statement: we can jump to the furthest point
414            // within the block where `r` is live.
415            if self.regioncx.is_loan_live_at(loan_idx, location) {
416                continue;
417            }
418
419            // No live region is reachable from the issuing region: the loan is killed at this
420            // point.
421            return Some(location);
422        }
423
424        None
425    }
426}
427
428impl<'a, 'tcx> Borrows<'a, 'tcx> {
429    pub fn new(
430        tcx: TyCtxt<'tcx>,
431        body: &'a Body<'tcx>,
432        regioncx: &RegionInferenceContext<'tcx>,
433        borrow_set: &'a BorrowSet<'tcx>,
434    ) -> Self {
435        let borrows_out_of_scope_at_location =
436            if !tcx.sess.opts.unstable_opts.polonius.is_next_enabled() {
437                calculate_borrows_out_of_scope_at_location(body, regioncx, borrow_set)
438            } else {
439                PoloniusOutOfScopePrecomputer::compute(body, regioncx, borrow_set)
440            };
441        Borrows { tcx, body, borrow_set, borrows_out_of_scope_at_location }
442    }
443
444    /// Add all borrows to the kill set, if those borrows are out of scope at `location`.
445    /// That means they went out of a nonlexical scope
446    fn kill_loans_out_of_scope_at_location(
447        &self,
448        state: &mut <Self as Analysis<'tcx>>::Domain,
449        location: Location,
450    ) {
451        // NOTE: The state associated with a given `location`
452        // reflects the dataflow on entry to the statement.
453        // Iterate over each of the borrows that we've precomputed
454        // to have went out of scope at this location and kill them.
455        //
456        // We are careful always to call this function *before* we
457        // set up the gen-bits for the statement or
458        // terminator. That way, if the effect of the statement or
459        // terminator *does* introduce a new loan of the same
460        // region, then setting that gen-bit will override any
461        // potential kill introduced here.
462        if let Some(indices) = self.borrows_out_of_scope_at_location.get(&location) {
463            state.kill_all(indices.iter().copied());
464        }
465    }
466
467    /// Kill any borrows that conflict with `place`.
468    fn kill_borrows_on_place(
469        &self,
470        state: &mut <Self as Analysis<'tcx>>::Domain,
471        place: Place<'tcx>,
472    ) {
473        debug!("kill_borrows_on_place: place={:?}", place);
474
475        let other_borrows_of_local = self
476            .borrow_set
477            .local_map
478            .get(&place.local)
479            .into_iter()
480            .flat_map(|bs| bs.iter())
481            .copied();
482
483        // If the borrowed place is a local with no projections, all other borrows of this
484        // local must conflict. This is purely an optimization so we don't have to call
485        // `places_conflict` for every borrow.
486        if place.projection.is_empty() {
487            if !self.body.local_decls[place.local].is_ref_to_static() {
488                state.kill_all(other_borrows_of_local);
489            }
490            return;
491        }
492
493        // By passing `PlaceConflictBias::NoOverlap`, we conservatively assume that any given
494        // pair of array indices are not equal, so that when `places_conflict` returns true, we
495        // will be assured that two places being compared definitely denotes the same sets of
496        // locations.
497        let definitely_conflicting_borrows = other_borrows_of_local.filter(|&i| {
498            places_conflict(
499                self.tcx,
500                self.body,
501                self.borrow_set[i].borrowed_place,
502                place,
503                PlaceConflictBias::NoOverlap,
504            )
505        });
506
507        state.kill_all(definitely_conflicting_borrows);
508    }
509}
510
511type BorrowsDomain = MixedBitSet<BorrowIndex>;
512
513/// Forward dataflow computation of the set of borrows that are in scope at a particular location.
514/// - we gen the introduced loans
515/// - we kill loans on locals going out of (regular) scope
516/// - we kill the loans going out of their region's NLL scope: in NLL terms, the frontier where a
517///   region stops containing the CFG points reachable from the issuing location.
518/// - we also kill loans of conflicting places when overwriting a shared path: e.g. borrows of
519///   `a.b.c` when `a` is overwritten.
520impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for Borrows<'_, 'tcx> {
521    type Domain = BorrowsDomain;
522
523    const NAME: &'static str = "borrows";
524
525    fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
526        // bottom = nothing is reserved or activated yet;
527        MixedBitSet::new_empty(self.borrow_set.len())
528    }
529
530    fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
531        // no borrows of code region_scopes have been taken prior to
532        // function execution, so this method has no effect.
533    }
534
535    fn apply_early_statement_effect(
536        &mut self,
537        state: &mut Self::Domain,
538        _statement: &mir::Statement<'tcx>,
539        location: Location,
540    ) {
541        self.kill_loans_out_of_scope_at_location(state, location);
542    }
543
544    fn apply_primary_statement_effect(
545        &mut self,
546        state: &mut Self::Domain,
547        stmt: &mir::Statement<'tcx>,
548        location: Location,
549    ) {
550        match &stmt.kind {
551            mir::StatementKind::Assign(box (lhs, rhs)) => {
552                if let mir::Rvalue::Ref(_, _, place) = rhs {
553                    if place.ignore_borrow(
554                        self.tcx,
555                        self.body,
556                        &self.borrow_set.locals_state_at_exit,
557                    ) {
558                        return;
559                    }
560                    let index = self.borrow_set.get_index_of(&location).unwrap_or_else(|| {
561                        panic!("could not find BorrowIndex for location {location:?}");
562                    });
563
564                    state.gen_(index);
565                }
566
567                // Make sure there are no remaining borrows for variables
568                // that are assigned over.
569                self.kill_borrows_on_place(state, *lhs);
570            }
571
572            mir::StatementKind::StorageDead(local) => {
573                // Make sure there are no remaining borrows for locals that
574                // are gone out of scope.
575                self.kill_borrows_on_place(state, Place::from(*local));
576            }
577
578            mir::StatementKind::FakeRead(..)
579            | mir::StatementKind::SetDiscriminant { .. }
580            | mir::StatementKind::Deinit(..)
581            | mir::StatementKind::StorageLive(..)
582            | mir::StatementKind::Retag { .. }
583            | mir::StatementKind::PlaceMention(..)
584            | mir::StatementKind::AscribeUserType(..)
585            | mir::StatementKind::Coverage(..)
586            | mir::StatementKind::Intrinsic(..)
587            | mir::StatementKind::ConstEvalCounter
588            | mir::StatementKind::BackwardIncompatibleDropHint { .. }
589            | mir::StatementKind::Nop => {}
590        }
591    }
592
593    fn apply_early_terminator_effect(
594        &mut self,
595        state: &mut Self::Domain,
596        _terminator: &mir::Terminator<'tcx>,
597        location: Location,
598    ) {
599        self.kill_loans_out_of_scope_at_location(state, location);
600    }
601
602    fn apply_primary_terminator_effect<'mir>(
603        &mut self,
604        state: &mut Self::Domain,
605        terminator: &'mir mir::Terminator<'tcx>,
606        _location: Location,
607    ) -> TerminatorEdges<'mir, 'tcx> {
608        if let mir::TerminatorKind::InlineAsm { operands, .. } = &terminator.kind {
609            for op in operands {
610                if let mir::InlineAsmOperand::Out { place: Some(place), .. }
611                | mir::InlineAsmOperand::InOut { out_place: Some(place), .. } = *op
612                {
613                    self.kill_borrows_on_place(state, place);
614                }
615            }
616        }
617        terminator.edges()
618    }
619}
620
621impl<C> DebugWithContext<C> for BorrowIndex {}