Skip to content

Commit 4e87b74

Browse files
authored
Rollup merge of rust-lang#144156 - compiler-errors:dtorck-upvars, r=lcnr
Check coroutine upvars in dtorck constraint Fix rust-lang#144155. This PR fixes an unsoundness where we were not considering coroutine upvars as drop-live if the coroutine interior types (witness types) had nothing which required drop. In the case that the coroutine does not have any interior types that need to be dropped, then we don't need to treat all of the upvars as use-live; instead, this PR uses the same logic as closures, and descends into the upvar types to collect anything that must be drop-live. The rest of this PR is reworking the comment to explain the behavior here. r? `@lcnr` or reassign 😸 --- Just some thoughts --- a proper fix for this whole situation would be to consider `TypingMode` in the `needs_drop` function, and just calling `coroutine_ty.needs_drop(tcx, typing_env)` in the dtorck constraint check. During MIR building, we should probably use a typing mode that stalls the local coroutines and considers them to be unconditionally drop, or perhaps just stall *all* coroutines in analysis mode. Then in borrowck mode, we can re-check `needs_drop` but descend into witness types properly. rust-lang#144158 implements this experimentally. This is a pretty involved fix, and conflicts with some in-flight changes (rust-lang#144157) that I have around removing coroutine witnesses altogether. I'm happy to add a FIXME to rework this whole approach, but I don't want to block this quick fix since it's obviously more correct than the status-quo.
2 parents 64aea00 + 560e5dc commit 4e87b74

File tree

5 files changed

+150
-24
lines changed

5 files changed

+150
-24
lines changed

compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -319,39 +319,65 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
319319
}
320320

321321
ty::Coroutine(def_id, args) => {
322-
// rust-lang/rust#49918: types can be constructed, stored
323-
// in the interior, and sit idle when coroutine yields
324-
// (and is subsequently dropped).
322+
// rust-lang/rust#49918: Locals can be stored across await points in the coroutine,
323+
// called interior/witness types. Since we do not compute these witnesses until after
324+
// building MIR, we consider all coroutines to unconditionally require a drop during
325+
// MIR building. However, considering the coroutine to unconditionally require a drop
326+
// here may unnecessarily require its upvars' regions to be live when they don't need
327+
// to be, leading to borrowck errors: <https://github.com/rust-lang/rust/issues/116242>.
325328
//
326-
// It would be nice to descend into interior of a
327-
// coroutine to determine what effects dropping it might
328-
// have (by looking at any drop effects associated with
329-
// its interior).
329+
// Here, we implement a more precise approximation for the coroutine's dtorck constraint
330+
// by considering whether any of the interior types needs drop. Note that this is still
331+
// an approximation because the coroutine interior has its regions erased, so we must add
332+
// *all* of the upvars to live types set if we find that *any* interior type needs drop.
333+
// This is because any of the regions captured in the upvars may be stored in the interior,
334+
// which then has its regions replaced by a binder (conceptually erasing the regions),
335+
// so there's no way to enforce that the precise region in the interior type is live
336+
// since we've lost that information by this point.
330337
//
331-
// However, the interior's representation uses things like
332-
// CoroutineWitness that explicitly assume they are not
333-
// traversed in such a manner. So instead, we will
334-
// simplify things for now by treating all coroutines as
335-
// if they were like trait objects, where its upvars must
336-
// all be alive for the coroutine's (potential)
337-
// destructor.
338+
// Note also that this check requires that the coroutine's upvars are use-live, since
339+
// a region from a type that does not have a destructor that was captured in an upvar
340+
// may flow into an interior type with a destructor. This is stronger than requiring
341+
// the upvars are drop-live.
338342
//
339-
// In particular, skipping over `_interior` is safe
340-
// because any side-effects from dropping `_interior` can
341-
// only take place through references with lifetimes
342-
// derived from lifetimes attached to the upvars and resume
343-
// argument, and we *do* incorporate those here.
343+
// For example, if we capture two upvar references `&'1 (), &'2 ()` and have some type
344+
// in the interior, `for<'r> { NeedsDrop<'r> }`, we have no way to tell whether the
345+
// region `'r` came from the `'1` or `'2` region, so we require both are live. This
346+
// could even be unnecessary if `'r` was actually a `'static` region or some region
347+
// local to the coroutine! That's why it's an approximation.
344348
let args = args.as_coroutine();
345349

346-
// While we conservatively assume that all coroutines require drop
347-
// to avoid query cycles during MIR building, we can check the actual
348-
// witness during borrowck to avoid unnecessary liveness constraints.
350+
// Note that we don't care about whether the resume type has any drops since this is
351+
// redundant; there is no storage for the resume type, so if it is actually stored
352+
// in the interior, we'll already detect the need for a drop by checking the interior.
349353
let typing_env = tcx.erase_regions(typing_env);
350-
if tcx.mir_coroutine_witnesses(def_id).is_some_and(|witness| {
354+
let needs_drop = tcx.mir_coroutine_witnesses(def_id).is_some_and(|witness| {
351355
witness.field_tys.iter().any(|field| field.ty.needs_drop(tcx, typing_env))
352-
}) {
356+
});
357+
if needs_drop {
358+
// Pushing types directly to `constraints.outlives` is equivalent
359+
// to requiring them to be use-live, since if we were instead to
360+
// recurse on them like we do below, we only end up collecting the
361+
// types that are relevant for drop-liveness.
353362
constraints.outlives.extend(args.upvar_tys().iter().map(ty::GenericArg::from));
354363
constraints.outlives.push(args.resume_ty().into());
364+
} else {
365+
// Even if a witness type doesn't need a drop, we still require that
366+
// the upvars are drop-live. This is only needed if we aren't already
367+
// counting *all* of the upvars as use-live above, since use-liveness
368+
// is a *stronger requirement* than drop-liveness. Recursing here
369+
// unconditionally would just be collecting duplicated types for no
370+
// reason.
371+
for ty in args.upvar_tys() {
372+
dtorck_constraint_for_ty_inner(
373+
tcx,
374+
typing_env,
375+
span,
376+
depth + 1,
377+
ty,
378+
constraints,
379+
);
380+
}
355381
}
356382
}
357383

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error[E0597]: `y` does not live long enough
2+
--> $DIR/drop-live-upvar-2.rs:31:26
3+
|
4+
LL | let y = ();
5+
| - binding `y` declared here
6+
LL | drop_me = Droppy(&y);
7+
| ^^ borrowed value does not live long enough
8+
...
9+
LL | }
10+
| - `y` dropped here while still borrowed
11+
LL | }
12+
| - borrow might be used here, when `fut` is dropped and runs the destructor for coroutine
13+
|
14+
= note: values in a scope are dropped in the opposite order they are defined
15+
16+
error: aborting due to 1 previous error
17+
18+
For more information about this error, try `rustc --explain E0597`.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//@ revisions: may_dangle may_not_dangle
2+
//@[may_dangle] check-pass
3+
//@ edition: 2018
4+
5+
// Ensure that if a coroutine's interior has no drop types then we don't require the upvars to
6+
// be *use-live*, but instead require them to be *drop-live*. In this case, `Droppy<&'?0 ()>`
7+
// does not require that `'?0` is live for drops since the parameter is `#[may_dangle]` in
8+
// the may_dangle revision, but not in the may_not_dangle revision.
9+
10+
#![feature(dropck_eyepatch)]
11+
12+
struct Droppy<T>(T);
13+
14+
#[cfg(may_dangle)]
15+
unsafe impl<#[may_dangle] T> Drop for Droppy<T> {
16+
fn drop(&mut self) {
17+
// This does not use `T` of course.
18+
}
19+
}
20+
21+
#[cfg(may_not_dangle)]
22+
impl<T> Drop for Droppy<T> {
23+
fn drop(&mut self) {}
24+
}
25+
26+
fn main() {
27+
let drop_me;
28+
let fut;
29+
{
30+
let y = ();
31+
drop_me = Droppy(&y);
32+
//[may_not_dangle]~^ ERROR `y` does not live long enough
33+
fut = async {
34+
std::mem::drop(drop_me);
35+
};
36+
}
37+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//@ edition: 2018
2+
// Regression test for <https://github.com/rust-lang/rust/issues/144155>.
3+
4+
struct NeedsDrop<'a>(&'a Vec<i32>);
5+
6+
async fn await_point() {}
7+
8+
impl Drop for NeedsDrop<'_> {
9+
fn drop(&mut self) {}
10+
}
11+
12+
fn foo() {
13+
let v = vec![1, 2, 3];
14+
let x = NeedsDrop(&v);
15+
let c = async {
16+
std::future::ready(()).await;
17+
drop(x);
18+
};
19+
drop(v);
20+
//~^ ERROR cannot move out of `v` because it is borrowed
21+
}
22+
23+
fn main() {}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error[E0505]: cannot move out of `v` because it is borrowed
2+
--> $DIR/drop-live-upvar.rs:19:10
3+
|
4+
LL | let v = vec![1, 2, 3];
5+
| - binding `v` declared here
6+
LL | let x = NeedsDrop(&v);
7+
| -- borrow of `v` occurs here
8+
...
9+
LL | drop(v);
10+
| ^ move out of `v` occurs here
11+
LL |
12+
LL | }
13+
| - borrow might be used here, when `c` is dropped and runs the destructor for coroutine
14+
|
15+
help: consider cloning the value if the performance cost is acceptable
16+
|
17+
LL | let x = NeedsDrop(&v.clone());
18+
| ++++++++
19+
20+
error: aborting due to 1 previous error
21+
22+
For more information about this error, try `rustc --explain E0505`.

0 commit comments

Comments
 (0)