rustc_borrowck/
dataflow.rs

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