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