Skip to content

Commit 3bb8c22

Browse files
committed
Fix spurious error when a Drop local has an assignment in a loop
`DropAndReplace` terminators are special - unlike all other terminators, they perform two distinct actions (a drop and a store) to the same `Place`. This complicates various analysis passes that we do, which expect at most one of 'Drop'/'Def'/'Use' for a local at a given MIR location. Previously, we categorized a `DropAndReplace` terminator's `Local` access as `MutatingUseContext::Drop`. As a result, livness computation would not consider the `DropAndReplace` as a store ('Def') of a local. The "use live" set would end up being too large (a use would be seen to extend back to an earlier store, rather than the closure `DropAndReplace`). We now explicitly propagate information about `DropAndReplace` terminators via new enum variants `MutatingUseContext:DropAndReplace` and `DefUse::DropAndReplace`. We use this information in liveness computation to record *both* a Drop and a Def. This prevents creating an extra-large "use live" set, while ensuring that te local is still considered "drop live" at the required locations.
1 parent cba4a38 commit 3bb8c22

File tree

10 files changed

+104
-2
lines changed

10 files changed

+104
-2
lines changed

compiler/rustc_borrowck/src/def_use.rs

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ pub enum DefUse {
77
Def,
88
Use,
99
Drop,
10+
/// This is only created for a `DropAndReplace` terminator.
11+
/// It needs special handling because represents a `Drop` immediately followed
12+
/// by a `Def`, at the same MIR location.
13+
DropAndDef,
1014
}
1115

1216
pub fn categorize(context: PlaceContext) -> Option<DefUse> {
@@ -31,6 +35,8 @@ pub fn categorize(context: PlaceContext) -> Option<DefUse> {
3135
PlaceContext::NonUse(NonUseContext::StorageLive) |
3236
PlaceContext::NonUse(NonUseContext::StorageDead) => Some(DefUse::Def),
3337

38+
PlaceContext::MutatingUse(MutatingUseContext::DropAndReplace) => Some(DefUse::DropAndDef),
39+
3440
///////////////////////////////////////////////////////////////////////////
3541
// REGULAR USES
3642
//

compiler/rustc_borrowck/src/diagnostics/find_use.rs

+4
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ impl<'cx, 'tcx> Visitor<'tcx> for DefUseVisitor<'cx, 'tcx> {
121121
Some(DefUse::Def) => Some(DefUseResult::Def),
122122
Some(DefUse::Use) => Some(DefUseResult::UseLive { local }),
123123
Some(DefUse::Drop) => Some(DefUseResult::UseDrop { local }),
124+
// This makes some diagnostics nicer - we want to indicate
125+
// that a variable is being kept alive because it can be accessed
126+
// by this drop.
127+
Some(DefUse::DropAndDef) => Some(DefUseResult::UseDrop { local }),
124128
None => None,
125129
};
126130
}

compiler/rustc_borrowck/src/type_check/liveness/local_use_map.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ pub(crate) struct LocalUseMap {
2424
/// defined in `x = y` but not `y`; that first def is the head of
2525
/// a linked list that lets you enumerate all places the variable
2626
/// is assigned.
27+
///
28+
/// Note that a Local can have *both* a definition and a drop
29+
/// at the same point - this occurs with `DropAndReplace` terminators.
2730
first_def_at: IndexVec<Local, Option<AppearanceIndex>>,
2831

2932
/// Head of a linked list of **uses** of each variable -- use in
@@ -35,6 +38,9 @@ pub(crate) struct LocalUseMap {
3538
/// Head of a linked list of **drops** of each variable -- these
3639
/// are a special category of uses corresponding to the drop that
3740
/// we add for each local variable.
41+
///
42+
/// Note that a Local can have *both* a definition and a drop
43+
/// at the same point - this occurs with `DropAndReplace` terminators.
3844
first_drop_at: IndexVec<Local, Option<AppearanceIndex>>,
3945

4046
appearances: IndexVec<AppearanceIndex, Appearance>,
@@ -163,7 +169,19 @@ impl Visitor<'_> for LocalUseMapBuild<'_> {
163169
Some(DefUse::Def) => self.insert_def(local, location),
164170
Some(DefUse::Use) => self.insert_use(local, location),
165171
Some(DefUse::Drop) => self.insert_drop(local, location),
166-
_ => (),
172+
// This counts as *both* a drop and a def.
173+
//
174+
// Treating it as a *drop* ensures that we consider the local to be
175+
// "drop live" here, which keeps alive any data that the `Drop` impl
176+
// could access.
177+
//
178+
// Treating it as a *def* ensures that we don't create an unnecessarily large "use live"
179+
// set - we'll stop tracing backwards from a use when we hit this def.
180+
Some(DefUse::DropAndDef) => {
181+
self.insert_drop(local, location);
182+
self.insert_def(local, location);
183+
}
184+
None => {}
167185
}
168186
}
169187
}

compiler/rustc_codegen_ssa/src/mir/analyze.rs

+1
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ impl<'mir, 'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> Visitor<'tcx>
211211

212212
PlaceContext::MutatingUse(
213213
MutatingUseContext::Store
214+
| MutatingUseContext::DropAndReplace
214215
| MutatingUseContext::Deinit
215216
| MutatingUseContext::SetDiscriminant
216217
| MutatingUseContext::AsmOutput

compiler/rustc_middle/src/mir/visit.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ macro_rules! make_mir_visitor {
504504
} => {
505505
self.visit_place(
506506
place,
507-
PlaceContext::MutatingUse(MutatingUseContext::Drop),
507+
PlaceContext::MutatingUse(MutatingUseContext::DropAndReplace),
508508
location
509509
);
510510
self.visit_operand(value, location);
@@ -1267,6 +1267,8 @@ pub enum MutatingUseContext {
12671267
Yield,
12681268
/// Being dropped.
12691269
Drop,
1270+
/// A `DropAndReplace` terminator
1271+
DropAndReplace,
12701272
/// Mutable borrow.
12711273
Borrow,
12721274
/// AddressOf for *mut pointer.

compiler/rustc_mir_dataflow/src/impls/liveness.rs

+7
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ impl DefUse {
206206
| PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {
207207
unreachable!("A projection could be a def or a use and must be handled separately")
208208
}
209+
210+
PlaceContext::MutatingUse(MutatingUseContext::DropAndReplace) => {
211+
unreachable!(
212+
"DropAndReplace terminators should have been removed by drop elaboration: place {:?}",
213+
place
214+
)
215+
}
209216
}
210217
}
211218
}

compiler/rustc_mir_transform/src/const_prop.rs

+1
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,7 @@ impl Visitor<'_> for CanConstProp {
932932
// whether they'd be fine right now.
933933
MutatingUse(MutatingUseContext::Yield)
934934
| MutatingUse(MutatingUseContext::Drop)
935+
| MutatingUse(MutatingUseContext::DropAndReplace)
935936
| MutatingUse(MutatingUseContext::Retag)
936937
// These can't ever be propagated under any scheme, as we can't reason about indirect
937938
// mutation.

src/test/ui/borrowck/drop-in-loop.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// A version of `issue-70919-drop-in-loop`, but without
2+
// the necessary `drop` call.
3+
//
4+
// This should fail to compile, since the `Drop` impl
5+
// for `WrapperWithDrop` could observe the changed
6+
// `base` value.
7+
8+
struct WrapperWithDrop<'a>(&'a mut bool);
9+
impl<'a> Drop for WrapperWithDrop<'a> {
10+
fn drop(&mut self) {
11+
}
12+
}
13+
14+
fn drop_in_loop() {
15+
let mut base = true;
16+
let mut wrapper = WrapperWithDrop(&mut base);
17+
loop {
18+
base = false; //~ ERROR: cannot assign to `base`
19+
wrapper = WrapperWithDrop(&mut base);
20+
}
21+
}
22+
23+
fn main() {
24+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error[E0506]: cannot assign to `base` because it is borrowed
2+
--> $DIR/drop-in-loop.rs:18:9
3+
|
4+
LL | let mut wrapper = WrapperWithDrop(&mut base);
5+
| --------- borrow of `base` occurs here
6+
LL | loop {
7+
LL | base = false;
8+
| ^^^^^^^^^^^^ assignment to borrowed `base` occurs here
9+
LL | wrapper = WrapperWithDrop(&mut base);
10+
| ------- borrow might be used here, when `wrapper` is dropped and runs the `Drop` code for type `WrapperWithDrop`
11+
12+
error: aborting due to previous error
13+
14+
For more information about this error, try `rustc --explain E0506`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Regression test for issue #70919
2+
// Tests that we don't emit a spurious "borrow might be used" error
3+
// when we have an explicit `drop` in a loop
4+
5+
// check-pass
6+
7+
struct WrapperWithDrop<'a>(&'a mut bool);
8+
impl<'a> Drop for WrapperWithDrop<'a> {
9+
fn drop(&mut self) {
10+
}
11+
}
12+
13+
fn drop_in_loop() {
14+
let mut base = true;
15+
let mut wrapper = WrapperWithDrop(&mut base);
16+
loop {
17+
drop(wrapper);
18+
19+
base = false;
20+
wrapper = WrapperWithDrop(&mut base);
21+
}
22+
}
23+
24+
fn main() {
25+
}

0 commit comments

Comments
 (0)