Skip to content

Commit

Permalink
Fix spurious error when a Drop local has an assignment in a loop
Browse files Browse the repository at this point in the history
`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.
  • Loading branch information
Aaron1011 committed Sep 21, 2022
1 parent cba4a38 commit 3bb8c22
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 2 deletions.
6 changes: 6 additions & 0 deletions compiler/rustc_borrowck/src/def_use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ pub enum DefUse {
Def,
Use,
Drop,
/// This is only created for a `DropAndReplace` terminator.
/// It needs special handling because represents a `Drop` immediately followed
/// by a `Def`, at the same MIR location.
DropAndDef,
}

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

PlaceContext::MutatingUse(MutatingUseContext::DropAndReplace) => Some(DefUse::DropAndDef),

///////////////////////////////////////////////////////////////////////////
// REGULAR USES
//
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_borrowck/src/diagnostics/find_use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ impl<'cx, 'tcx> Visitor<'tcx> for DefUseVisitor<'cx, 'tcx> {
Some(DefUse::Def) => Some(DefUseResult::Def),
Some(DefUse::Use) => Some(DefUseResult::UseLive { local }),
Some(DefUse::Drop) => Some(DefUseResult::UseDrop { local }),
// This makes some diagnostics nicer - we want to indicate
// that a variable is being kept alive because it can be accessed
// by this drop.
Some(DefUse::DropAndDef) => Some(DefUseResult::UseDrop { local }),
None => None,
};
}
Expand Down
20 changes: 19 additions & 1 deletion compiler/rustc_borrowck/src/type_check/liveness/local_use_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ pub(crate) struct LocalUseMap {
/// defined in `x = y` but not `y`; that first def is the head of
/// a linked list that lets you enumerate all places the variable
/// is assigned.
///
/// Note that a Local can have *both* a definition and a drop
/// at the same point - this occurs with `DropAndReplace` terminators.
first_def_at: IndexVec<Local, Option<AppearanceIndex>>,

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

appearances: IndexVec<AppearanceIndex, Appearance>,
Expand Down Expand Up @@ -163,7 +169,19 @@ impl Visitor<'_> for LocalUseMapBuild<'_> {
Some(DefUse::Def) => self.insert_def(local, location),
Some(DefUse::Use) => self.insert_use(local, location),
Some(DefUse::Drop) => self.insert_drop(local, location),
_ => (),
// This counts as *both* a drop and a def.
//
// Treating it as a *drop* ensures that we consider the local to be
// "drop live" here, which keeps alive any data that the `Drop` impl
// could access.
//
// Treating it as a *def* ensures that we don't create an unnecessarily large "use live"
// set - we'll stop tracing backwards from a use when we hit this def.
Some(DefUse::DropAndDef) => {
self.insert_drop(local, location);
self.insert_def(local, location);
}
None => {}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_codegen_ssa/src/mir/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ impl<'mir, 'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> Visitor<'tcx>

PlaceContext::MutatingUse(
MutatingUseContext::Store
| MutatingUseContext::DropAndReplace
| MutatingUseContext::Deinit
| MutatingUseContext::SetDiscriminant
| MutatingUseContext::AsmOutput
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_middle/src/mir/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ macro_rules! make_mir_visitor {
} => {
self.visit_place(
place,
PlaceContext::MutatingUse(MutatingUseContext::Drop),
PlaceContext::MutatingUse(MutatingUseContext::DropAndReplace),
location
);
self.visit_operand(value, location);
Expand Down Expand Up @@ -1267,6 +1267,8 @@ pub enum MutatingUseContext {
Yield,
/// Being dropped.
Drop,
/// A `DropAndReplace` terminator
DropAndReplace,
/// Mutable borrow.
Borrow,
/// AddressOf for *mut pointer.
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_mir_dataflow/src/impls/liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ impl DefUse {
| PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {
unreachable!("A projection could be a def or a use and must be handled separately")
}

PlaceContext::MutatingUse(MutatingUseContext::DropAndReplace) => {
unreachable!(
"DropAndReplace terminators should have been removed by drop elaboration: place {:?}",
place
)
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_mir_transform/src/const_prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,7 @@ impl Visitor<'_> for CanConstProp {
// whether they'd be fine right now.
MutatingUse(MutatingUseContext::Yield)
| MutatingUse(MutatingUseContext::Drop)
| MutatingUse(MutatingUseContext::DropAndReplace)
| MutatingUse(MutatingUseContext::Retag)
// These can't ever be propagated under any scheme, as we can't reason about indirect
// mutation.
Expand Down
24 changes: 24 additions & 0 deletions src/test/ui/borrowck/drop-in-loop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// A version of `issue-70919-drop-in-loop`, but without
// the necessary `drop` call.
//
// This should fail to compile, since the `Drop` impl
// for `WrapperWithDrop` could observe the changed
// `base` value.

struct WrapperWithDrop<'a>(&'a mut bool);
impl<'a> Drop for WrapperWithDrop<'a> {
fn drop(&mut self) {
}
}

fn drop_in_loop() {
let mut base = true;
let mut wrapper = WrapperWithDrop(&mut base);
loop {
base = false; //~ ERROR: cannot assign to `base`
wrapper = WrapperWithDrop(&mut base);
}
}

fn main() {
}
14 changes: 14 additions & 0 deletions src/test/ui/borrowck/drop-in-loop.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0506]: cannot assign to `base` because it is borrowed
--> $DIR/drop-in-loop.rs:18:9
|
LL | let mut wrapper = WrapperWithDrop(&mut base);
| --------- borrow of `base` occurs here
LL | loop {
LL | base = false;
| ^^^^^^^^^^^^ assignment to borrowed `base` occurs here
LL | wrapper = WrapperWithDrop(&mut base);
| ------- borrow might be used here, when `wrapper` is dropped and runs the `Drop` code for type `WrapperWithDrop`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0506`.
25 changes: 25 additions & 0 deletions src/test/ui/borrowck/issue-70919-drop-in-loop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Regression test for issue #70919
// Tests that we don't emit a spurious "borrow might be used" error
// when we have an explicit `drop` in a loop

// check-pass

struct WrapperWithDrop<'a>(&'a mut bool);
impl<'a> Drop for WrapperWithDrop<'a> {
fn drop(&mut self) {
}
}

fn drop_in_loop() {
let mut base = true;
let mut wrapper = WrapperWithDrop(&mut base);
loop {
drop(wrapper);

base = false;
wrapper = WrapperWithDrop(&mut base);
}
}

fn main() {
}

0 comments on commit 3bb8c22

Please sign in to comment.