Skip to content

Commit 34faed0

Browse files
committed
Do not consider async task_context to be alive across yields
When lowering async constructs to generators, the resume argument is guaranteed not to be alive across yield points. However the simple `generator_interior` analysis thinks it is. Because of that, the resume ty was part of the `GeneratorWitness` and considered to be part of the generator type, even though it is not really. This prevented async blocks from being `UnwindSafe`, and possibly `Send` in some cases. The code now special cases the fact that the `task_context` of async blocks is never alive across yield points.
1 parent 309c469 commit 34faed0

9 files changed

+135
-7
lines changed

compiler/rustc_hir_typeck/src/generator_interior/mod.rs

+60-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ struct InteriorVisitor<'a, 'tcx> {
3333
prev_unresolved_span: Option<Span>,
3434
linted_values: HirIdSet,
3535
drop_ranges: DropRanges,
36+
task_context_hir_id: Option<HirId>,
37+
in_lowered_await: bool,
3638
}
3739

3840
impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
@@ -53,7 +55,7 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
5355
ty, hir_id, scope, expr, source_span, self.expr_count,
5456
);
5557

56-
let live_across_yield = scope
58+
let mut live_across_yield = scope
5759
.map(|s| {
5860
self.region_scope_tree.yield_in_scope(s).and_then(|yield_data| {
5961
// If we are recording an expression that is the last yield
@@ -92,6 +94,34 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
9294
Some(YieldData { span: DUMMY_SP, expr_and_pat_count: 0, source: self.kind.into() })
9395
});
9496

97+
// If this is the `&mut Context<'_>` async resume argument, or we are
98+
// just visiting a `_task_context = yield ()` expression from async
99+
// lowering, we do *not* consider this type to be live across yields.
100+
if Some(hir_id) == self.task_context_hir_id || self.in_lowered_await {
101+
#[cfg(debug_assertions)]
102+
{
103+
// As `record` is being invoked for multiple parts / types of the
104+
// lowered `.await`, the `ty` has to either be the `()` going *into*
105+
// the `yield`, or a `&mut Context<'_>` coming *out* of it.
106+
let tcx = self.fcx.tcx;
107+
if ty == tcx.types.unit {
108+
// all good
109+
} else if let ty::Ref(_, adt, hir::Mutability::Mut) = ty.kind() && let ty::Adt(adt, _) = adt.kind()
110+
{
111+
let context_def_id = tcx.lang_items().context();
112+
assert_eq!(
113+
Some(adt.did()),
114+
context_def_id,
115+
"expected `&mut Context<'_>, found `{:?}` instead`",
116+
ty
117+
);
118+
} else {
119+
panic!("expected `()` or `&mut Context<'_>`, found `{:?}` instead", ty);
120+
}
121+
}
122+
live_across_yield = None;
123+
}
124+
95125
if let Some(yield_data) = live_across_yield {
96126
debug!(
97127
"type in expr = {:?}, scope = {:?}, type = {:?}, count = {}, yield_span = {:?}",
@@ -183,6 +213,17 @@ pub fn resolve_interior<'a, 'tcx>(
183213
kind: hir::GeneratorKind,
184214
) {
185215
let body = fcx.tcx.hir().body(body_id);
216+
217+
// In case we are in an async block, this is the Param/Pat HirId of the
218+
// `&mut Context<'_>` resume type. We can use this to explicitly prevent it
219+
// from being considered as `live_across_yield`, which it is not, but the
220+
// simple scope-based analysis can't tell.
221+
let task_context_hir_id = if matches!(kind, hir::GeneratorKind::Async(_)) {
222+
Some(body.params[0].pat.hir_id)
223+
} else {
224+
None
225+
};
226+
186227
let typeck_results = fcx.inh.typeck_results.borrow();
187228
let mut visitor = InteriorVisitor {
188229
fcx,
@@ -194,6 +235,8 @@ pub fn resolve_interior<'a, 'tcx>(
194235
prev_unresolved_span: None,
195236
linted_values: <_>::default(),
196237
drop_ranges: drop_ranges::compute_drop_ranges(fcx, def_id, body),
238+
task_context_hir_id,
239+
in_lowered_await: false,
197240
};
198241
intravisit::walk_body(&mut visitor, body);
199242

@@ -426,6 +469,22 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
426469
}
427470
_ => intravisit::walk_expr(self, expr),
428471
},
472+
ExprKind::Assign(_, rhs, _) => {
473+
// An `.await` expression will be lowered to `_task_context = yield ()`.
474+
// In that case, other forms of `yield` are considered errors in lowering.
475+
if self.task_context_hir_id.is_some()
476+
&& matches!(rhs.kind, hir::ExprKind::Yield(..))
477+
{
478+
assert!(!self.in_lowered_await);
479+
self.in_lowered_await = true;
480+
}
481+
// We are still walking the whole expression including its types.
482+
// First, we need to keep `expr_count` in sync as it is asserted
483+
// at the very end, and to keep all the other computations in
484+
// place just in case they are causing other side effects.
485+
intravisit::walk_expr(self, expr);
486+
self.in_lowered_await = false;
487+
}
429488
_ => intravisit::walk_expr(self, expr),
430489
}
431490

src/test/ui/async-await/async-await-let-else.drop-tracking.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ LL | async fn bar2<T>(_: T) -> ! {
4040
LL | | panic!()
4141
LL | | }
4242
| |_^
43-
= note: required because it captures the following types: `&mut Context<'_>`, `Option<bool>`, `impl Future<Output = !>`, `()`
43+
= note: required because it captures the following types: `Option<bool>`, `impl Future<Output = !>`, `()`
4444
note: required because it's used within this `async fn` body
4545
--> $DIR/async-await-let-else.rs:21:32
4646
|
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// edition:2018
2+
3+
fn is_unwindsafe(_: impl std::panic::UnwindSafe) {}
4+
5+
fn main() {
6+
// a normal async block that uses `&mut Context<'_>` implicitly via async lowering is fine
7+
// as we should not consider that to be alive across an await point
8+
is_unwindsafe(async {
9+
async {}.await; // this needs an inner await point
10+
});
11+
12+
is_unwindsafe(async {
13+
//~^ ERROR the type `&mut Context<'_>` may not be safely transferred across an unwind boundary
14+
use std::ptr::null;
15+
use std::task::{Context, RawWaker, RawWakerVTable, Waker};
16+
let waker = unsafe {
17+
Waker::from_raw(RawWaker::new(
18+
null(),
19+
&RawWakerVTable::new(|_| todo!(), |_| todo!(), |_| todo!(), |_| todo!()),
20+
))
21+
};
22+
let mut cx = Context::from_waker(&waker);
23+
let cx_ref = &mut cx;
24+
25+
async {}.await; // this needs an inner await point
26+
27+
// in this case, `&mut Context<'_>` is *truely* alive across an await point
28+
drop(cx_ref);
29+
drop(cx);
30+
});
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
error[E0277]: the type `&mut Context<'_>` may not be safely transferred across an unwind boundary
2+
--> $DIR/async-is-unwindsafe.rs:12:19
3+
|
4+
LL | is_unwindsafe(async {
5+
| ___________________^
6+
LL | |
7+
LL | | use std::ptr::null;
8+
LL | | use std::task::{Context, RawWaker, RawWakerVTable, Waker};
9+
... |
10+
LL | | drop(cx);
11+
LL | | });
12+
| | ^
13+
| | |
14+
| |_____`&mut Context<'_>` may not be safely transferred across an unwind boundary
15+
| within this `[async block@$DIR/async-is-unwindsafe.rs:12:19: 30:6]`
16+
|
17+
= help: within `[async block@$DIR/async-is-unwindsafe.rs:12:19: 30:6]`, the trait `UnwindSafe` is not implemented for `&mut Context<'_>`
18+
= note: `UnwindSafe` is implemented for `&std::task::Context<'_>`, but not for `&mut std::task::Context<'_>`
19+
note: future does not implement `UnwindSafe` as this value is used across an await
20+
--> $DIR/async-is-unwindsafe.rs:25:17
21+
|
22+
LL | let cx_ref = &mut cx;
23+
| ------ has type `&mut Context<'_>` which does not implement `UnwindSafe`
24+
LL |
25+
LL | async {}.await; // this needs an inner await point
26+
| ^^^^^^ await occurs here, with `cx_ref` maybe used later
27+
...
28+
LL | });
29+
| - `cx_ref` is later dropped here
30+
note: required by a bound in `is_unwindsafe`
31+
--> $DIR/async-is-unwindsafe.rs:3:26
32+
|
33+
LL | fn is_unwindsafe(_: impl std::panic::UnwindSafe) {}
34+
| ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `is_unwindsafe`
35+
36+
error: aborting due to previous error
37+
38+
For more information about this error, try `rustc --explain E0277`.

src/test/ui/async-await/issue-68112.drop_tracking.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ note: required because it appears within the type `impl Future<Output = Arc<RefC
5757
|
5858
LL | fn make_non_send_future2() -> impl Future<Output = Arc<RefCell<i32>>> {
5959
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
60-
= note: required because it captures the following types: `&mut Context<'_>`, `impl Future<Output = Arc<RefCell<i32>>>`, `()`, `Ready<i32>`
60+
= note: required because it captures the following types: `impl Future<Output = Arc<RefCell<i32>>>`, `()`, `Ready<i32>`
6161
note: required because it's used within this `async` block
6262
--> $DIR/issue-68112.rs:60:20
6363
|

src/test/ui/async-await/issue-68112.no_drop_tracking.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ note: required because it appears within the type `impl Future<Output = Arc<RefC
5757
|
5858
LL | fn make_non_send_future2() -> impl Future<Output = Arc<RefCell<i32>>> {
5959
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
60-
= note: required because it captures the following types: `&mut Context<'_>`, `impl Future<Output = Arc<RefCell<i32>>>`, `()`, `i32`, `Ready<i32>`
60+
= note: required because it captures the following types: `impl Future<Output = Arc<RefCell<i32>>>`, `i32`, `Ready<i32>`
6161
note: required because it's used within this `async` block
6262
--> $DIR/issue-68112.rs:60:20
6363
|

src/test/ui/async-await/issue-70935-complex-spans.drop_tracking.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ LL | async fn baz<T>(_c: impl FnMut() -> T) where T: Future<Output=()> {
1818
| ___________________________________________________________________^
1919
LL | | }
2020
| |_^
21-
= note: required because it captures the following types: `&mut Context<'_>`, `impl Future<Output = ()>`, `()`
21+
= note: required because it captures the following types: `impl Future<Output = ()>`, `()`
2222
note: required because it's used within this `async` block
2323
--> $DIR/issue-70935-complex-spans.rs:16:5
2424
|

src/test/ui/async-await/partial-drop-partial-reinit.drop_tracking.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ LL | async fn foo() {
1111
|
1212
= help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `NotSend`
1313
= note: required because it appears within the type `(NotSend,)`
14-
= note: required because it captures the following types: `&mut Context<'_>`, `(NotSend,)`, `()`, `impl Future<Output = ()>`
14+
= note: required because it captures the following types: `(NotSend,)`, `()`, `impl Future<Output = ()>`
1515
note: required because it's used within this `async fn` body
1616
--> $DIR/partial-drop-partial-reinit.rs:31:16
1717
|

src/test/ui/async-await/partial-drop-partial-reinit.no_drop_tracking.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ LL | async fn foo() {
1111
|
1212
= help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `NotSend`
1313
= note: required because it appears within the type `(NotSend,)`
14-
= note: required because it captures the following types: `&mut Context<'_>`, `(NotSend,)`, `impl Future<Output = ()>`, `()`
14+
= note: required because it captures the following types: `(NotSend,)`, `impl Future<Output = ()>`
1515
note: required because it's used within this `async fn` body
1616
--> $DIR/partial-drop-partial-reinit.rs:31:16
1717
|

0 commit comments

Comments
 (0)