Skip to content

Commit 4719aad

Browse files
committed
Rework fake borrow calculation
1 parent 890ce91 commit 4719aad

10 files changed

+206
-179
lines changed

compiler/rustc_middle/src/mir/statement.rs

+5
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,11 @@ impl<'tcx> PlaceRef<'tcx> {
236236
}
237237
}
238238

239+
#[inline]
240+
pub fn to_place(&self, tcx: TyCtxt<'tcx>) -> Place<'tcx> {
241+
Place { local: self.local, projection: tcx.mk_place_elems(self.projection) }
242+
}
243+
239244
#[inline]
240245
pub fn last_projection(&self) -> Option<(PlaceRef<'tcx>, PlaceElem<'tcx>)> {
241246
if let &[ref proj_base @ .., elem] = self.projection {

compiler/rustc_mir_build/src/build/matches/mod.rs

+16-101
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@
55
//! This also includes code for pattern bindings in `let` statements and
66
//! function parameters.
77
8-
use crate::build::expr::as_place::{PlaceBase, PlaceBuilder};
8+
use crate::build::expr::as_place::PlaceBuilder;
99
use crate::build::scope::DropKind;
1010
use crate::build::ForGuard::{self, OutsideGuard, RefWithinGuard};
1111
use crate::build::{BlockAnd, BlockAndExtension, Builder};
1212
use crate::build::{GuardFrame, GuardFrameLocal, LocalsForNode};
13-
use rustc_data_structures::{
14-
fx::{FxHashSet, FxIndexMap, FxIndexSet},
15-
stack::ensure_sufficient_stack,
16-
};
13+
use rustc_data_structures::{fx::FxIndexMap, stack::ensure_sufficient_stack};
1714
use rustc_hir::{BindingAnnotation, ByRef};
1815
use rustc_middle::middle::region;
1916
use rustc_middle::mir::{self, *};
@@ -209,7 +206,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
209206
/// 2. Create the decision tree ([Builder::lower_match_tree]).
210207
/// 3. Determine the fake borrows that are needed from the places that were
211208
/// matched against and create the required temporaries for them
212-
/// ([Builder::calculate_fake_borrows]).
209+
/// ([util::collect_fake_borrows]).
213210
/// 4. Create everything else: the guards and the arms ([Builder::lower_match_arms]).
214211
///
215212
/// ## False edges
@@ -379,11 +376,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
379376
match_has_guard: bool,
380377
candidates: &mut [&mut Candidate<'pat, 'tcx>],
381378
) -> Vec<(Place<'tcx>, Local)> {
382-
// The set of places that we are creating fake borrows of. If there are
383-
// no match guards then we don't need any fake borrows, so don't track
384-
// them.
385-
let fake_borrows = match_has_guard
386-
.then(|| util::FakeBorrowCollector::collect_fake_borrows(self, candidates));
379+
// The set of places that we are creating fake borrows of. If there are no match guards then
380+
// we don't need any fake borrows, so don't track them.
381+
let fake_borrows: Vec<(Place<'tcx>, Local)> = if match_has_guard {
382+
util::collect_fake_borrows(
383+
self,
384+
candidates,
385+
scrutinee_span,
386+
scrutinee_place_builder.base(),
387+
)
388+
} else {
389+
Vec::new()
390+
};
387391

388392
let otherwise_block = self.cfg.start_new_block();
389393

@@ -437,11 +441,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
437441
});
438442
}
439443

440-
if let Some(ref borrows) = fake_borrows {
441-
self.calculate_fake_borrows(borrows, scrutinee_place_builder.base(), scrutinee_span)
442-
} else {
443-
Vec::new()
444-
}
444+
fake_borrows
445445
}
446446

447447
/// Lower the bindings, guards and arm bodies of a `match` expression.
@@ -1916,91 +1916,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
19161916
target_blocks,
19171917
);
19181918
}
1919-
1920-
/// Determine the fake borrows that are needed from a set of places that
1921-
/// have to be stable across match guards.
1922-
///
1923-
/// Returns a list of places that need a fake borrow and the temporary
1924-
/// that's used to store the fake borrow.
1925-
///
1926-
/// Match exhaustiveness checking is not able to handle the case where the
1927-
/// place being matched on is mutated in the guards. We add "fake borrows"
1928-
/// to the guards that prevent any mutation of the place being matched.
1929-
/// There are a some subtleties:
1930-
///
1931-
/// 1. Borrowing `*x` doesn't prevent assigning to `x`. If `x` is a shared
1932-
/// reference, the borrow isn't even tracked. As such we have to add fake
1933-
/// borrows of any prefixes of a place
1934-
/// 2. We don't want `match x { _ => (), }` to conflict with mutable
1935-
/// borrows of `x`, so we only add fake borrows for places which are
1936-
/// bound or tested by the match.
1937-
/// 3. We don't want the fake borrows to conflict with `ref mut` bindings,
1938-
/// so we use a special BorrowKind for them.
1939-
/// 4. The fake borrows may be of places in inactive variants, so it would
1940-
/// be UB to generate code for them. They therefore have to be removed
1941-
/// by a MIR pass run after borrow checking.
1942-
fn calculate_fake_borrows<'b>(
1943-
&mut self,
1944-
fake_borrows: &'b FxIndexSet<Place<'tcx>>,
1945-
scrutinee_base: PlaceBase,
1946-
temp_span: Span,
1947-
) -> Vec<(Place<'tcx>, Local)> {
1948-
let tcx = self.tcx;
1949-
1950-
debug!("add_fake_borrows fake_borrows = {:?}", fake_borrows);
1951-
1952-
let mut all_fake_borrows = Vec::with_capacity(fake_borrows.len());
1953-
1954-
// Insert a Shallow borrow of the prefixes of any fake borrows.
1955-
for place in fake_borrows {
1956-
if let PlaceBase::Local(l) = scrutinee_base
1957-
&& l != place.local
1958-
{
1959-
// The base of this place is a temporary created for deref patterns. We don't emit
1960-
// fake borrows for these as they are not initialized in all branches.
1961-
// FIXME(deref_patterns): is this sound?
1962-
continue;
1963-
}
1964-
1965-
let mut cursor = place.projection.as_ref();
1966-
while let [proj_base @ .., elem] = cursor {
1967-
cursor = proj_base;
1968-
1969-
if let ProjectionElem::Deref = elem {
1970-
// Insert a shallow borrow after a deref. For other
1971-
// projections the borrow of prefix_cursor will
1972-
// conflict with any mutation of base.
1973-
all_fake_borrows.push(PlaceRef { local: place.local, projection: proj_base });
1974-
}
1975-
}
1976-
1977-
all_fake_borrows.push(place.as_ref());
1978-
}
1979-
1980-
// Deduplicate
1981-
let mut dedup = FxHashSet::default();
1982-
all_fake_borrows.retain(|b| dedup.insert(*b));
1983-
1984-
debug!("add_fake_borrows all_fake_borrows = {:?}", all_fake_borrows);
1985-
1986-
all_fake_borrows
1987-
.into_iter()
1988-
.map(|matched_place_ref| {
1989-
let matched_place = Place {
1990-
local: matched_place_ref.local,
1991-
projection: tcx.mk_place_elems(matched_place_ref.projection),
1992-
};
1993-
let fake_borrow_deref_ty = matched_place.ty(&self.local_decls, tcx).ty;
1994-
let fake_borrow_ty =
1995-
Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, fake_borrow_deref_ty);
1996-
let mut fake_borrow_temp = LocalDecl::new(fake_borrow_ty, temp_span);
1997-
fake_borrow_temp.local_info = ClearCrossCrate::Set(Box::new(LocalInfo::FakeBorrow));
1998-
let fake_borrow_temp = self.local_decls.push(fake_borrow_temp);
1999-
2000-
(matched_place, fake_borrow_temp)
2001-
})
2002-
.collect()
2003-
}
20041919
}
20051920

20061921
///////////////////////////////////////////////////////////////////////////

compiler/rustc_mir_build/src/build/matches/util.rs

+123-16
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use rustc_middle::mir::*;
77
use rustc_middle::thir::{self, *};
88
use rustc_middle::ty::TypeVisitableExt;
99
use rustc_middle::ty::{self, Ty};
10+
use rustc_span::Span;
1011

1112
impl<'a, 'tcx> Builder<'a, 'tcx> {
1213
pub(crate) fn field_match_pairs<'pat>(
@@ -268,19 +269,99 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> {
268269

269270
pub(super) struct FakeBorrowCollector<'a, 'b, 'tcx> {
270271
cx: &'a mut Builder<'b, 'tcx>,
272+
/// Base of the scrutinee place. Used to distinguish bindings inside the scrutinee place from
273+
/// bindings inside deref patterns.
274+
scrutinee_base: PlaceBase,
271275
fake_borrows: FxIndexSet<Place<'tcx>>,
272276
}
273277

278+
/// Determine the set of places that have to be stable across match guards.
279+
///
280+
/// Returns a list of places that need a fake borrow along with a local to store it.
281+
///
282+
/// Match exhaustiveness checking is not able to handle the case where the place being matched on is
283+
/// mutated in the guards. We add "fake borrows" to the guards that prevent any mutation of the
284+
/// place being matched. There are a some subtleties:
285+
///
286+
/// 1. Borrowing `*x` doesn't prevent assigning to `x`. If `x` is a shared reference, the borrow
287+
/// isn't even tracked. As such we have to add fake borrows of any prefixes of a place.
288+
/// 2. We don't want `match x { (Some(_), _) => (), .. }` to conflict with mutable borrows of `x.1`, so we
289+
/// only add fake borrows for places which are bound or tested by the match.
290+
/// 3. We don't want `match x { Some(_) => (), .. }` to conflict with mutable borrows of `(x as
291+
/// Some).0`, so the borrows are a special shallow borrow that only affects the place and not its
292+
/// projections.
293+
/// ```rust
294+
/// let mut x = (Some(0), true);
295+
/// match x {
296+
/// (Some(_), false) => {}
297+
/// _ if { if let Some(ref mut y) = x.0 { *y += 1 }; true } => {}
298+
/// _ => {}
299+
/// }
300+
/// ```
301+
/// 4. The fake borrows may be of places in inactive variants, e.g. here we need to fake borrow `x`
302+
/// and `(x as Some).0`, but when we reach the guard `x` may not be `Some`.
303+
/// ```rust
304+
/// let mut x = (Some(Some(0)), true);
305+
/// match x {
306+
/// (Some(Some(_)), false) => {}
307+
/// _ if { if let Some(Some(ref mut y)) = x.0 { *y += 1 }; true } => {}
308+
/// _ => {}
309+
/// }
310+
/// ```
311+
/// So it would be UB to generate code for the fake borrows. They therefore have to be removed by
312+
/// a MIR pass run after borrow checking.
313+
pub(super) fn collect_fake_borrows<'tcx>(
314+
cx: &mut Builder<'_, 'tcx>,
315+
candidates: &[&mut Candidate<'_, 'tcx>],
316+
temp_span: Span,
317+
scrutinee_base: PlaceBase,
318+
) -> Vec<(Place<'tcx>, Local)> {
319+
let mut collector =
320+
FakeBorrowCollector { cx, scrutinee_base, fake_borrows: FxIndexSet::default() };
321+
for candidate in candidates.iter() {
322+
collector.visit_candidate(candidate);
323+
}
324+
let fake_borrows = collector.fake_borrows;
325+
debug!("add_fake_borrows fake_borrows = {:?}", fake_borrows);
326+
let tcx = cx.tcx;
327+
fake_borrows
328+
.iter()
329+
.copied()
330+
.map(|matched_place| {
331+
let fake_borrow_deref_ty = matched_place.ty(&cx.local_decls, tcx).ty;
332+
let fake_borrow_ty =
333+
Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, fake_borrow_deref_ty);
334+
let mut fake_borrow_temp = LocalDecl::new(fake_borrow_ty, temp_span);
335+
fake_borrow_temp.local_info = ClearCrossCrate::Set(Box::new(LocalInfo::FakeBorrow));
336+
let fake_borrow_temp = cx.local_decls.push(fake_borrow_temp);
337+
(matched_place, fake_borrow_temp)
338+
})
339+
.collect()
340+
}
341+
274342
impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> {
275-
pub(super) fn collect_fake_borrows(
276-
cx: &'a mut Builder<'b, 'tcx>,
277-
candidates: &[&mut Candidate<'_, 'tcx>],
278-
) -> FxIndexSet<Place<'tcx>> {
279-
let mut collector = Self { cx, fake_borrows: FxIndexSet::default() };
280-
for candidate in candidates.iter() {
281-
collector.visit_candidate(candidate);
343+
// Fake borrow this place and its dereference prefixes.
344+
fn fake_borrow(&mut self, place: Place<'tcx>) {
345+
let new = self.fake_borrows.insert(place);
346+
if !new {
347+
return;
348+
}
349+
// Also fake borrow the prefixes of any fake borrow.
350+
self.fake_borrow_deref_prefixes(place);
351+
}
352+
353+
// Fake borrow the prefixes of this place that are dereferences.
354+
fn fake_borrow_deref_prefixes(&mut self, place: Place<'tcx>) {
355+
for (place_ref, elem) in place.as_ref().iter_projections().rev() {
356+
if let ProjectionElem::Deref = elem {
357+
// Insert a shallow borrow after a deref. For other projections the borrow of
358+
// `place_ref` will conflict with any mutation of `place.base`.
359+
let new = self.fake_borrows.insert(place_ref.to_place(self.cx.tcx));
360+
if !new {
361+
return;
362+
}
363+
}
282364
}
283-
collector.fake_borrows
284365
}
285366

286367
fn visit_candidate(&mut self, candidate: &Candidate<'_, 'tcx>) {
@@ -306,10 +387,28 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> {
306387
for flat_pat in pats.iter() {
307388
self.visit_flat_pat(flat_pat)
308389
}
390+
} else if matches!(match_pair.test_case, TestCase::Deref { .. }) {
391+
// The subpairs of a deref pattern are all places relative to the deref temporary, so we
392+
// don't fake borrow them. Problem is, if we only shallowly fake-borrowed
393+
// `match_pair.place`, this would allow:
394+
// ```
395+
// let mut b = Box::new(false);
396+
// match b {
397+
// deref!(true) => {} // not reached because `*b == false`
398+
// _ if { *b = true; false } => {} // not reached because the guard is `false`
399+
// deref!(false) => {} // not reached because the guard changed it
400+
// // UB because we reached the unreachable.
401+
// }
402+
// ```
403+
// FIXME(deref_patterns): Hence we fake borrow using a non-shallow borrow.
404+
if let Some(place) = match_pair.place {
405+
// FIXME(deref_patterns): use a non-shallow borrow.
406+
self.fake_borrow(place);
407+
}
309408
} else {
310409
// Insert a Shallow borrow of any place that is switched on.
311410
if let Some(place) = match_pair.place {
312-
self.fake_borrows.insert(place);
411+
self.fake_borrow(place);
313412
}
314413

315414
for subpair in &match_pair.subpairs {
@@ -319,6 +418,14 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> {
319418
}
320419

321420
fn visit_binding(&mut self, Binding { source, .. }: &Binding<'tcx>) {
421+
if let PlaceBase::Local(l) = self.scrutinee_base
422+
&& l != source.local
423+
{
424+
// The base of this place is a temporary created for deref patterns. We don't emit fake
425+
// borrows for these as they are not initialized in all branches.
426+
return;
427+
}
428+
322429
// Insert a borrows of prefixes of places that are bound and are
323430
// behind a dereference projection.
324431
//
@@ -335,13 +442,13 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> {
335442
// y if { y == 1 && (x = &2) == () } => y,
336443
// _ => 3,
337444
// }
338-
if let Some(i) = source.projection.iter().rposition(|elem| elem == ProjectionElem::Deref) {
339-
let proj_base = &source.projection[..i];
340-
self.fake_borrows.insert(Place {
341-
local: source.local,
342-
projection: self.cx.tcx.mk_place_elems(proj_base),
343-
});
344-
}
445+
//
446+
// We don't just fake borrow the whole place because this is allowed:
447+
// match u {
448+
// _ if { u = true; false } => (),
449+
// x => (),
450+
// }
451+
self.fake_borrow_deref_prefixes(*source);
345452
}
346453
}
347454

tests/mir-opt/building/match/match_false_edges.full_tested_match.built.after.mir

+6-6
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ fn full_tested_match() -> () {
44
let mut _0: ();
55
let mut _1: (i32, i32);
66
let mut _2: std::option::Option<i32>;
7-
let mut _3: isize;
8-
let mut _4: &std::option::Option<i32>;
7+
let mut _3: &std::option::Option<i32>;
8+
let mut _4: isize;
99
let _5: i32;
1010
let _6: &i32;
1111
let mut _7: bool;
@@ -27,8 +27,8 @@ fn full_tested_match() -> () {
2727
StorageLive(_2);
2828
_2 = Option::<i32>::Some(const 42_i32);
2929
PlaceMention(_2);
30-
_3 = discriminant(_2);
31-
switchInt(move _3) -> [0: bb5, 1: bb2, otherwise: bb1];
30+
_4 = discriminant(_2);
31+
switchInt(move _4) -> [0: bb5, 1: bb2, otherwise: bb1];
3232
}
3333

3434
bb1: {
@@ -60,7 +60,7 @@ fn full_tested_match() -> () {
6060
bb7: {
6161
StorageLive(_6);
6262
_6 = &((_2 as Some).0: i32);
63-
_4 = &fake _2;
63+
_3 = &fake _2;
6464
StorageLive(_7);
6565
_7 = guard() -> [return: bb8, unwind: bb16];
6666
}
@@ -71,7 +71,7 @@ fn full_tested_match() -> () {
7171

7272
bb9: {
7373
StorageDead(_7);
74-
FakeRead(ForMatchGuard, _4);
74+
FakeRead(ForMatchGuard, _3);
7575
FakeRead(ForGuardBinding, _6);
7676
StorageLive(_5);
7777
_5 = ((_2 as Some).0: i32);

0 commit comments

Comments
 (0)