Skip to content

Commit d2a1455

Browse files
Auto merge of #138961 - meithecatte:expr-use-visitor, r=<try>
Make closure capturing have consistent and correct behaviour around patterns
2 parents 235a4c0 + 74f5d5d commit d2a1455

33 files changed

+728
-305
lines changed

compiler/rustc_hir_typeck/src/expr_use_visitor.rs

Lines changed: 113 additions & 158 deletions
Large diffs are not rendered by default.

compiler/rustc_hir_typeck/src/upvar.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
761761
/// ],
762762
/// }
763763
/// ```
764+
#[instrument(level = "debug", skip(self))]
764765
fn compute_min_captures(
765766
&self,
766767
closure_def_id: LocalDefId,
@@ -2030,6 +2031,7 @@ struct InferBorrowKind<'tcx> {
20302031
}
20312032

20322033
impl<'tcx> euv::Delegate<'tcx> for InferBorrowKind<'tcx> {
2034+
#[instrument(skip(self), level = "debug")]
20332035
fn fake_read(
20342036
&mut self,
20352037
place_with_id: &PlaceWithHirId<'tcx>,
@@ -2120,6 +2122,7 @@ impl<'tcx> euv::Delegate<'tcx> for InferBorrowKind<'tcx> {
21202122
}
21212123

21222124
/// Rust doesn't permit moving fields out of a type that implements drop
2125+
#[instrument(skip(fcx), ret, level = "debug")]
21232126
fn restrict_precision_for_drop_types<'a, 'tcx>(
21242127
fcx: &'a FnCtxt<'a, 'tcx>,
21252128
mut place: Place<'tcx>,
@@ -2180,6 +2183,7 @@ fn restrict_precision_for_unsafe(
21802183
/// - No unsafe block is required to capture `place`.
21812184
///
21822185
/// Returns the truncated place and updated capture mode.
2186+
#[instrument(ret, level = "debug")]
21832187
fn restrict_capture_precision(
21842188
place: Place<'_>,
21852189
curr_mode: ty::UpvarCapture,
@@ -2209,6 +2213,7 @@ fn restrict_capture_precision(
22092213
}
22102214

22112215
/// Truncate deref of any reference.
2216+
#[instrument(ret, level = "debug")]
22122217
fn adjust_for_move_closure(
22132218
mut place: Place<'_>,
22142219
mut kind: ty::UpvarCapture,
@@ -2223,6 +2228,7 @@ fn adjust_for_move_closure(
22232228
}
22242229

22252230
/// Truncate deref of any reference.
2231+
#[instrument(ret, level = "debug")]
22262232
fn adjust_for_use_closure(
22272233
mut place: Place<'_>,
22282234
mut kind: ty::UpvarCapture,
@@ -2238,6 +2244,7 @@ fn adjust_for_use_closure(
22382244

22392245
/// Adjust closure capture just that if taking ownership of data, only move data
22402246
/// from enclosing stack frame.
2247+
#[instrument(ret, level = "debug")]
22412248
fn adjust_for_non_move_closure(
22422249
mut place: Place<'_>,
22432250
mut kind: ty::UpvarCapture,
@@ -2560,6 +2567,7 @@ fn determine_place_ancestry_relation<'tcx>(
25602567
/// // it is constrained to `'a`
25612568
/// }
25622569
/// ```
2570+
#[instrument(ret, level = "debug")]
25632571
fn truncate_capture_for_optimization(
25642572
mut place: Place<'_>,
25652573
mut curr_mode: ty::UpvarCapture,

compiler/rustc_mir_build/src/builder/matches/match_pair.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,12 @@ impl<'tcx> MatchPairTree<'tcx> {
311311

312312
if let Some(test_case) = test_case {
313313
// This pattern is refutable, so push a new match-pair node.
314+
//
315+
// Note: unless test_case is TestCase::Or, place must not be None.
316+
// This means that the closure capture analysis in
317+
// rustc_hir_typeck::upvar, and in particular the pattern handling
318+
// code of ExprUseVisitor, must capture all of the places we'll use.
319+
// Make sure to keep these two parts in sync!
314320
match_pairs.push(MatchPairTree {
315321
place,
316322
test_case,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This test serves to document the change in semantics introduced by
2+
// rust-lang/rust#138961.
3+
//
4+
// A corollary of partial-pattern.rs: while the tuple access testcase makes
5+
// it clear why these semantics are useful, it is actually the dereference
6+
// being performed by the pattern that matters.
7+
8+
fn main() {
9+
// the inner reference is dangling
10+
let x: &&u32 = unsafe {
11+
let x: u32 = 42;
12+
&&* &raw const x
13+
};
14+
15+
let _ = || { //~ ERROR: encountered a dangling reference
16+
match x {
17+
&&_y => {},
18+
}
19+
};
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: constructing invalid value: encountered a dangling reference (use-after-free)
2+
--> tests/fail/closures/deref-in-pattern.rs:LL:CC
3+
|
4+
LL | let _ = || {
5+
| _____________^
6+
LL | | match x {
7+
LL | | &&_y => {},
8+
LL | | }
9+
LL | | };
10+
| |_____^ constructing invalid value: encountered a dangling reference (use-after-free)
11+
|
12+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
13+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
14+
= note: BACKTRACE:
15+
= note: inside `main` at tests/fail/closures/deref-in-pattern.rs:LL:CC
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to 1 previous error
20+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// This test serves to document the change in semantics introduced by
2+
// rust-lang/rust#138961.
3+
//
4+
// Previously, the closure would capture the entirety of x, and access *(*x).0
5+
// when called. Now, the closure only captures *(*x).0, which means that
6+
// a &*(*x).0 reborrow happens when the closure is constructed.
7+
//
8+
// Hence, if one of the references is dangling, this constitutes newly introduced UB
9+
// in the case where the closure doesn't get called. This isn't a big deal,
10+
// because while opsem only now considers this to be UB, the unsafe code
11+
// guidelines have long recommended against any handling of dangling references.
12+
13+
fn main() {
14+
// the inner references are dangling
15+
let x: &(&u32, &u32) = unsafe {
16+
let a = 21;
17+
let b = 37;
18+
let ra = &* &raw const a;
19+
let rb = &* &raw const b;
20+
&(ra, rb)
21+
};
22+
23+
let _ = || { //~ ERROR: encountered a dangling reference
24+
match x {
25+
(&_y, _) => {},
26+
}
27+
};
28+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: constructing invalid value: encountered a dangling reference (use-after-free)
2+
--> tests/fail/closures/partial-pattern.rs:LL:CC
3+
|
4+
LL | let _ = || {
5+
| _____________^
6+
LL | | match x {
7+
LL | | (&_y, _) => {},
8+
LL | | }
9+
LL | | };
10+
| |_____^ constructing invalid value: encountered a dangling reference (use-after-free)
11+
|
12+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
13+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
14+
= note: BACKTRACE:
15+
= note: inside `main` at tests/fail/closures/partial-pattern.rs:LL:CC
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to 1 previous error
20+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Motivated by rust-lang/rust#138961, this shows how invalid discriminants interact with
2+
// closure captures.
3+
#![feature(never_type)]
4+
5+
#[repr(C)]
6+
#[allow(dead_code)]
7+
enum E {
8+
V0, // discriminant: 0
9+
V1, // 1
10+
V2(!), // 2
11+
}
12+
13+
fn main() {
14+
assert_eq!(std::mem::size_of::<E>(), 4);
15+
16+
let val = 2u32;
17+
let ptr = (&raw const val).cast::<E>();
18+
let r = unsafe { &*ptr };
19+
let f = || {
20+
// After rust-lang/rust#138961, constructing the closure performs a reborrow of r.
21+
// Nevertheless, the discriminant is only actually inspected when the closure
22+
// is called.
23+
match r { //~ ERROR: read discriminant of an uninhabited enum variant
24+
E::V0 => {}
25+
E::V1 => {}
26+
E::V2(_) => {}
27+
}
28+
};
29+
30+
f();
31+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: read discriminant of an uninhabited enum variant
2+
--> tests/fail/closures/uninhabited-variant.rs:LL:CC
3+
|
4+
LL | match r {
5+
| ^ read discriminant of an uninhabited enum variant
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside closure at tests/fail/closures/uninhabited-variant.rs:LL:CC
11+
note: inside `main`
12+
--> tests/fail/closures/uninhabited-variant.rs:LL:CC
13+
|
14+
LL | f();
15+
| ^^^
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to 1 previous error
20+
File renamed without changes.

0 commit comments

Comments
 (0)