rustc_borrowck/path_utils.rs
1use std::ops::ControlFlow;
2
3use rustc_abi::FieldIdx;
4use rustc_data_structures::graph::dominators::Dominators;
5use rustc_middle::mir::{BasicBlock, Body, Location, Place, PlaceRef, ProjectionElem};
6use rustc_middle::ty::TyCtxt;
7use tracing::debug;
8
9use crate::borrow_set::{BorrowData, BorrowSet, TwoPhaseActivation};
10use crate::{AccessDepth, BorrowIndex, places_conflict};
11
12/// Encapsulates the idea of iterating over every borrow that involves a particular path
13pub(super) fn each_borrow_involving_path<'tcx, F, I, S>(
14 s: &mut S,
15 tcx: TyCtxt<'tcx>,
16 body: &Body<'tcx>,
17 access_place: (AccessDepth, Place<'tcx>),
18 borrow_set: &BorrowSet<'tcx>,
19 is_candidate: I,
20 mut op: F,
21) where
22 F: FnMut(&mut S, BorrowIndex, &BorrowData<'tcx>) -> ControlFlow<()>,
23 I: Fn(BorrowIndex) -> bool,
24{
25 let (access, place) = access_place;
26
27 // The number of candidates can be large, but borrows for different locals cannot conflict with
28 // each other, so we restrict the working set a priori.
29 let Some(borrows_for_place_base) = borrow_set.local_map.get(&place.local) else { return };
30
31 // check for loan restricting path P being used. Accounts for
32 // borrows of P, P.a.b, etc.
33 for &i in borrows_for_place_base {
34 if !is_candidate(i) {
35 continue;
36 }
37 let borrowed = &borrow_set[i];
38
39 if places_conflict::borrow_conflicts_with_place(
40 tcx,
41 body,
42 borrowed.borrowed_place,
43 borrowed.kind,
44 place.as_ref(),
45 access,
46 places_conflict::PlaceConflictBias::Overlap,
47 ) {
48 debug!(
49 "each_borrow_involving_path: {:?} @ {:?} vs. {:?}/{:?}",
50 i, borrowed, place, access
51 );
52 let ctrl = op(s, i, borrowed);
53 if matches!(ctrl, ControlFlow::Break(_)) {
54 return;
55 }
56 }
57 }
58}
59
60pub(super) fn is_active<'tcx>(
61 dominators: &Dominators<BasicBlock>,
62 borrow_data: &BorrowData<'tcx>,
63 location: Location,
64) -> bool {
65 debug!("is_active(borrow_data={:?}, location={:?})", borrow_data, location);
66
67 let activation_location = match borrow_data.activation_location {
68 // If this is not a 2-phase borrow, it is always active.
69 TwoPhaseActivation::NotTwoPhase => return true,
70 // And if the unique 2-phase use is not an activation, then it is *never* active.
71 TwoPhaseActivation::NotActivated => return false,
72 // Otherwise, we derive info from the activation point `loc`:
73 TwoPhaseActivation::ActivatedAt(loc) => loc,
74 };
75
76 // Otherwise, it is active for every location *except* in between
77 // the reservation and the activation:
78 //
79 // X
80 // /
81 // R <--+ Except for this
82 // / \ | diamond
83 // \ / |
84 // A <------+
85 // |
86 // Z
87 //
88 // Note that we assume that:
89 // - the reservation R dominates the activation A
90 // - the activation A post-dominates the reservation R (ignoring unwinding edges).
91 //
92 // This means that there can't be an edge that leaves A and
93 // comes back into that diamond unless it passes through R.
94 //
95 // Suboptimal: In some cases, this code walks the dominator
96 // tree twice when it only has to be walked once. I am
97 // lazy. -nmatsakis
98
99 // If dominated by the activation A, then it is active. The
100 // activation occurs upon entering the point A, so this is
101 // also true if location == activation_location.
102 if activation_location.dominates(location, dominators) {
103 return true;
104 }
105
106 // The reservation starts *on exiting* the reservation block,
107 // so check if the location is dominated by R.successor. If so,
108 // this point falls in between the reservation and location.
109 let reserve_location = borrow_data.reserve_location.successor_within_block();
110 if reserve_location.dominates(location, dominators) {
111 false
112 } else {
113 // Otherwise, this point is outside the diamond, so
114 // consider the borrow active. This could happen for
115 // example if the borrow remains active around a loop (in
116 // which case it would be active also for the point R,
117 // which would generate an error).
118 true
119 }
120}
121
122/// Determines if a given borrow is borrowing local data
123/// This is called for all Yield expressions on movable coroutines
124pub(super) fn borrow_of_local_data(place: Place<'_>) -> bool {
125 // Reborrow of already borrowed data is ignored
126 // Any errors will be caught on the initial borrow
127 !place.is_indirect()
128}
129
130/// If `place` is a field projection, and the field is being projected from a closure type,
131/// then returns the index of the field being projected. Note that this closure will always
132/// be `self` in the current MIR, because that is the only time we directly access the fields
133/// of a closure type.
134pub(crate) fn is_upvar_field_projection<'tcx>(
135 tcx: TyCtxt<'tcx>,
136 upvars: &[&rustc_middle::ty::CapturedPlace<'tcx>],
137 place_ref: PlaceRef<'tcx>,
138 body: &Body<'tcx>,
139) -> Option<FieldIdx> {
140 let mut place_ref = place_ref;
141 let mut by_ref = false;
142
143 if let Some((place_base, ProjectionElem::Deref)) = place_ref.last_projection() {
144 place_ref = place_base;
145 by_ref = true;
146 }
147
148 match place_ref.last_projection() {
149 Some((place_base, ProjectionElem::Field(field, _ty))) => {
150 let base_ty = place_base.ty(body, tcx).ty;
151 if (base_ty.is_closure() || base_ty.is_coroutine() || base_ty.is_coroutine_closure())
152 && (!by_ref || upvars[field.index()].is_by_ref())
153 {
154 Some(field)
155 } else {
156 None
157 }
158 }
159 _ => None,
160 }
161}