Skip to content

Commit 2a908aa

Browse files
authored
Unrolled build for rust-lang#123324
Rollup merge of rust-lang#123324 - Nadrieril:false-edges2, r=matthewjasper match lowering: make false edges more precise When lowering match expressions, we add false edges to hide details of the lowering from borrowck. Morally we pretend we're testing the patterns (and guards) one after the other in order. See the tests for examples. Problem is, the way we implement this today is too coarse for deref patterns. In deref patterns, a pattern like `deref [1, x]` matches on a `Vec` by creating a temporary to store the output of the call to `deref()` and then uses that to continue matching. Here the pattern has a binding, which we set up after the pre-binding block. Problem is, currently the false edges tell borrowck that the pre-binding block can be reached from a previous arm as well, so the `deref()` temporary may not be initialized. This triggers an error when we try to use the binding `x`. We could call `deref()` a second time, but this opens the door to soundness issues if the deref impl is weird. Instead in this PR I rework false edges a little bit. What we need from false edges is a (fake) path from each candidate to the next, specifically from candidate C's pre-binding block to next candidate D's pre-binding block. Today, we link the pre-binding blocks directly. In this PR, I link them indirectly by choosing an earlier node on D's success path. Specifically, I choose the earliest block on D's success path that doesn't make a loop (if I chose e.g. the start block of the whole match (which is on the success path of all candidates), that would make a loop). This turns out to be rather straightforward to implement. r? `@matthewjasper` if you have the bandwidth, otherwise let me know
2 parents ca7d34e + e2ebaa1 commit 2a908aa

10 files changed

+334
-84
lines changed

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

+94-12
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,77 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
214214
///
215215
/// ## False edges
216216
///
217-
/// We don't want to have the exact structure of the decision tree be
218-
/// visible through borrow checking. False edges ensure that the CFG as
219-
/// seen by borrow checking doesn't encode this. False edges are added:
217+
/// We don't want to have the exact structure of the decision tree be visible through borrow
218+
/// checking. Specifically we want borrowck to think that:
219+
/// - at any point, any or none of the patterns and guards seen so far may have been tested;
220+
/// - after the match, any of the patterns may have matched.
220221
///
221-
/// * From each pre-binding block to the next pre-binding block.
222-
/// * From each otherwise block to the next pre-binding block.
222+
/// For example, all of these would fail to error if borrowck could see the real CFG (examples
223+
/// taken from `tests/ui/nll/match-cfg-fake-edges.rs`):
224+
/// ```ignore (too many errors, this is already in the test suite)
225+
/// let x = String::new();
226+
/// let _ = match true {
227+
/// _ => {},
228+
/// _ => drop(x),
229+
/// };
230+
/// // Borrowck must not know the second arm is never run.
231+
/// drop(x); //~ ERROR use of moved value
232+
///
233+
/// let x;
234+
/// # let y = true;
235+
/// match y {
236+
/// _ if { x = 2; true } => {},
237+
/// // Borrowck must not know the guard is always run.
238+
/// _ => drop(x), //~ ERROR used binding `x` is possibly-uninitialized
239+
/// };
240+
///
241+
/// let x = String::new();
242+
/// # let y = true;
243+
/// match y {
244+
/// false if { drop(x); true } => {},
245+
/// // Borrowck must not know the guard is not run in the `true` case.
246+
/// true => drop(x), //~ ERROR use of moved value: `x`
247+
/// false => {},
248+
/// };
249+
///
250+
/// # let mut y = (true, true);
251+
/// let r = &mut y.1;
252+
/// match y {
253+
/// //~^ ERROR cannot use `y.1` because it was mutably borrowed
254+
/// (false, true) => {}
255+
/// // Borrowck must not know we don't test `y.1` when `y.0` is `true`.
256+
/// (true, _) => drop(r),
257+
/// (false, _) => {}
258+
/// };
259+
/// ```
260+
///
261+
/// We add false edges to act as if we were naively matching each arm in order. What we need is
262+
/// a (fake) path from each candidate to the next, specifically from candidate C's pre-binding
263+
/// block to next candidate D's pre-binding block. For maximum precision (needed for deref
264+
/// patterns), we choose the earliest node on D's success path that doesn't also lead to C (to
265+
/// avoid loops).
266+
///
267+
/// This turns out to be easy to compute: that block is the `start_block` of the first call to
268+
/// `match_candidates` where D is the first candidate in the list.
269+
///
270+
/// For example:
271+
/// ```rust
272+
/// # let (x, y) = (true, true);
273+
/// match (x, y) {
274+
/// (true, true) => 1,
275+
/// (false, true) => 2,
276+
/// (true, false) => 3,
277+
/// _ => 4,
278+
/// }
279+
/// # ;
280+
/// ```
281+
/// In this example, the pre-binding block of arm 1 has a false edge to the block for result
282+
/// `false` of the first test on `x`. The other arms have false edges to the pre-binding blocks
283+
/// of the next arm.
284+
///
285+
/// On top of this, we also add a false edge from the otherwise_block of each guard to the
286+
/// aforementioned start block of the next candidate, to ensure borrock doesn't rely on which
287+
/// guards may have run.
223288
#[instrument(level = "debug", skip(self, arms))]
224289
pub(crate) fn match_expr(
225290
&mut self,
@@ -365,7 +430,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
365430
for candidate in candidates {
366431
candidate.visit_leaves(|leaf_candidate| {
367432
if let Some(ref mut prev) = previous_candidate {
368-
prev.next_candidate_pre_binding_block = leaf_candidate.pre_binding_block;
433+
assert!(leaf_candidate.false_edge_start_block.is_some());
434+
prev.next_candidate_start_block = leaf_candidate.false_edge_start_block;
369435
}
370436
previous_candidate = Some(leaf_candidate);
371437
});
@@ -1010,8 +1076,12 @@ struct Candidate<'pat, 'tcx> {
10101076

10111077
/// The block before the `bindings` have been established.
10121078
pre_binding_block: Option<BasicBlock>,
1013-
/// The pre-binding block of the next candidate.
1014-
next_candidate_pre_binding_block: Option<BasicBlock>,
1079+
1080+
/// The earliest block that has only candidates >= this one as descendents. Used for false
1081+
/// edges, see the doc for [`Builder::match_expr`].
1082+
false_edge_start_block: Option<BasicBlock>,
1083+
/// The `false_edge_start_block` of the next candidate.
1084+
next_candidate_start_block: Option<BasicBlock>,
10151085
}
10161086

10171087
impl<'tcx, 'pat> Candidate<'pat, 'tcx> {
@@ -1033,7 +1103,8 @@ impl<'tcx, 'pat> Candidate<'pat, 'tcx> {
10331103
or_span: None,
10341104
otherwise_block: None,
10351105
pre_binding_block: None,
1036-
next_candidate_pre_binding_block: None,
1106+
false_edge_start_block: None,
1107+
next_candidate_start_block: None,
10371108
}
10381109
}
10391110

@@ -1325,6 +1396,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
13251396
otherwise_block: BasicBlock,
13261397
candidates: &mut [&mut Candidate<'_, 'tcx>],
13271398
) {
1399+
if let [first, ..] = candidates {
1400+
if first.false_edge_start_block.is_none() {
1401+
first.false_edge_start_block = Some(start_block);
1402+
}
1403+
}
1404+
13281405
match candidates {
13291406
[] => {
13301407
// If there are no candidates that still need testing, we're done. Since all matches are
@@ -1545,6 +1622,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
15451622
.into_iter()
15461623
.map(|flat_pat| Candidate::from_flat_pat(flat_pat, candidate.has_guard))
15471624
.collect();
1625+
candidate.subcandidates[0].false_edge_start_block = candidate.false_edge_start_block;
15481626
}
15491627

15501628
/// Try to merge all of the subcandidates of the given candidate into one. This avoids
@@ -1564,6 +1642,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
15641642
let any_matches = self.cfg.start_new_block();
15651643
let or_span = candidate.or_span.take().unwrap();
15661644
let source_info = self.source_info(or_span);
1645+
if candidate.false_edge_start_block.is_none() {
1646+
candidate.false_edge_start_block =
1647+
candidate.subcandidates[0].false_edge_start_block;
1648+
}
15671649
for subcandidate in mem::take(&mut candidate.subcandidates) {
15681650
let or_block = subcandidate.pre_binding_block.unwrap();
15691651
self.cfg.goto(or_block, source_info, any_matches);
@@ -1979,12 +2061,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
19792061

19802062
let mut block = candidate.pre_binding_block.unwrap();
19812063

1982-
if candidate.next_candidate_pre_binding_block.is_some() {
2064+
if candidate.next_candidate_start_block.is_some() {
19832065
let fresh_block = self.cfg.start_new_block();
19842066
self.false_edges(
19852067
block,
19862068
fresh_block,
1987-
candidate.next_candidate_pre_binding_block,
2069+
candidate.next_candidate_start_block,
19882070
candidate_source_info,
19892071
);
19902072
block = fresh_block;
@@ -2132,7 +2214,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
21322214
self.false_edges(
21332215
otherwise_post_guard_block,
21342216
otherwise_block,
2135-
candidate.next_candidate_pre_binding_block,
2217+
candidate.next_candidate_start_block,
21362218
source_info,
21372219
);
21382220

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ fn main() -> () {
4848
}
4949

5050
bb2: {
51-
falseEdge -> [real: bb15, imaginary: bb6];
51+
falseEdge -> [real: bb15, imaginary: bb3];
5252
}
5353

5454
bb3: {

tests/mir-opt/building/match/sort_candidates.constant_eq.SimplifyCfg-initial.after.mir

+4-4
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,15 @@ fn constant_eq(_1: &str, _2: bool) -> u32 {
4040
}
4141

4242
bb4: {
43-
falseEdge -> [real: bb12, imaginary: bb9];
43+
falseEdge -> [real: bb12, imaginary: bb7];
4444
}
4545

4646
bb5: {
4747
switchInt((_3.1: bool)) -> [0: bb1, otherwise: bb6];
4848
}
4949

5050
bb6: {
51-
falseEdge -> [real: bb16, imaginary: bb3];
51+
falseEdge -> [real: bb16, imaginary: bb1];
5252
}
5353

5454
bb7: {
@@ -60,7 +60,7 @@ fn constant_eq(_1: &str, _2: bool) -> u32 {
6060
}
6161

6262
bb9: {
63-
falseEdge -> [real: bb15, imaginary: bb6];
63+
falseEdge -> [real: bb15, imaginary: bb5];
6464
}
6565

6666
bb10: {
@@ -89,7 +89,7 @@ fn constant_eq(_1: &str, _2: bool) -> u32 {
8989

9090
bb14: {
9191
StorageDead(_10);
92-
falseEdge -> [real: bb5, imaginary: bb9];
92+
falseEdge -> [real: bb5, imaginary: bb7];
9393
}
9494

9595
bb15: {

tests/mir-opt/building/match/sort_candidates.disjoint_ranges.SimplifyCfg-initial.after.mir

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn disjoint_ranges(_1: i32, _2: bool) -> u32 {
2323
}
2424

2525
bb2: {
26-
falseEdge -> [real: bb9, imaginary: bb4];
26+
falseEdge -> [real: bb9, imaginary: bb3];
2727
}
2828

2929
bb3: {
@@ -32,7 +32,7 @@ fn disjoint_ranges(_1: i32, _2: bool) -> u32 {
3232
}
3333

3434
bb4: {
35-
falseEdge -> [real: bb12, imaginary: bb6];
35+
falseEdge -> [real: bb12, imaginary: bb5];
3636
}
3737

3838
bb5: {
@@ -69,7 +69,7 @@ fn disjoint_ranges(_1: i32, _2: bool) -> u32 {
6969

7070
bb11: {
7171
StorageDead(_8);
72-
falseEdge -> [real: bb1, imaginary: bb4];
72+
falseEdge -> [real: bb1, imaginary: bb3];
7373
}
7474

7575
bb12: {

tests/mir-opt/match_arm_scopes.complicated_match.panic-abort.SimplifyCfg-initial.after-ElaborateDrops.after.diff

+4-4
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@
6060
}
6161

6262
- bb5: {
63-
- falseEdge -> [real: bb13, imaginary: bb3];
63+
- falseEdge -> [real: bb13, imaginary: bb2];
6464
- }
6565
-
6666
- bb6: {
67-
- falseEdge -> [real: bb8, imaginary: bb5];
67+
- falseEdge -> [real: bb8, imaginary: bb1];
6868
- }
6969
-
7070
- bb7: {
@@ -127,7 +127,7 @@
127127
StorageDead(_9);
128128
StorageDead(_8);
129129
StorageDead(_6);
130-
- falseEdge -> [real: bb1, imaginary: bb5];
130+
- falseEdge -> [real: bb1, imaginary: bb1];
131131
+ goto -> bb1;
132132
}
133133

@@ -184,7 +184,7 @@
184184
StorageDead(_12);
185185
StorageDead(_8);
186186
StorageDead(_6);
187-
- falseEdge -> [real: bb2, imaginary: bb3];
187+
- falseEdge -> [real: bb2, imaginary: bb2];
188188
+ goto -> bb2;
189189
}
190190

tests/mir-opt/match_arm_scopes.complicated_match.panic-unwind.SimplifyCfg-initial.after-ElaborateDrops.after.diff

+4-4
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@
6060
}
6161

6262
- bb5: {
63-
- falseEdge -> [real: bb13, imaginary: bb3];
63+
- falseEdge -> [real: bb13, imaginary: bb2];
6464
- }
6565
-
6666
- bb6: {
67-
- falseEdge -> [real: bb8, imaginary: bb5];
67+
- falseEdge -> [real: bb8, imaginary: bb1];
6868
- }
6969
-
7070
- bb7: {
@@ -127,7 +127,7 @@
127127
StorageDead(_9);
128128
StorageDead(_8);
129129
StorageDead(_6);
130-
- falseEdge -> [real: bb1, imaginary: bb5];
130+
- falseEdge -> [real: bb1, imaginary: bb1];
131131
+ goto -> bb1;
132132
}
133133

@@ -184,7 +184,7 @@
184184
StorageDead(_12);
185185
StorageDead(_8);
186186
StorageDead(_6);
187-
- falseEdge -> [real: bb2, imaginary: bb3];
187+
- falseEdge -> [real: bb2, imaginary: bb2];
188188
+ goto -> bb2;
189189
}
190190

0 commit comments

Comments
 (0)