Rollup merge of #132151 - compiler-errors:coroutine-resume-outlives, r=spastorino
Ensure that resume arg outlives region bound for coroutines When proving that `{Coroutine}: 'region`, we must also prove that the coroutine's resume ty outlives that region as well. See the inline comment. Fixes #132104
This commit is contained in:
commit
5dc36391fe
@ -110,6 +110,18 @@ fn visit_ty(&mut self, ty: I::Ty) -> Self::Result {
|
||||
ty::Coroutine(_, args) => {
|
||||
args.as_coroutine().tupled_upvars_ty().visit_with(self);
|
||||
|
||||
// Coroutines may not outlive a region unless the resume
|
||||
// ty outlives a region. This is because the resume ty may
|
||||
// store data that lives shorter than this outlives region
|
||||
// across yield points, which may subsequently be accessed
|
||||
// after the coroutine is resumed again.
|
||||
//
|
||||
// Conceptually, you may think of the resume arg as an upvar
|
||||
// of `&mut Option<ResumeArgTy>`, since it is kinda like
|
||||
// storage shared between the callee of the coroutine and the
|
||||
// coroutine body.
|
||||
args.as_coroutine().resume_ty().visit_with(self);
|
||||
|
||||
// We ignore regions in the coroutine interior as we don't
|
||||
// want these to affect region inference
|
||||
}
|
||||
|
34
tests/ui/coroutine/resume-arg-outlives-2.rs
Normal file
34
tests/ui/coroutine/resume-arg-outlives-2.rs
Normal file
@ -0,0 +1,34 @@
|
||||
// Regression test for 132104
|
||||
|
||||
#![feature(coroutine_trait, coroutines)]
|
||||
|
||||
use std::ops::Coroutine;
|
||||
use std::{thread, time};
|
||||
|
||||
fn demo<'not_static>(s: &'not_static str) -> thread::JoinHandle<()> {
|
||||
let mut generator = Box::pin({
|
||||
#[coroutine]
|
||||
move |_ctx| {
|
||||
let ctx: &'not_static str = yield;
|
||||
yield;
|
||||
dbg!(ctx);
|
||||
}
|
||||
});
|
||||
|
||||
// exploit:
|
||||
generator.as_mut().resume("");
|
||||
generator.as_mut().resume(s); // <- generator hoards it as `let ctx`.
|
||||
//~^ ERROR borrowed data escapes outside of function
|
||||
thread::spawn(move || {
|
||||
thread::sleep(time::Duration::from_millis(200));
|
||||
generator.as_mut().resume(""); // <- resumes from the last `yield`, running `dbg!(ctx)`.
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let local = String::from("...");
|
||||
let thread = demo(&local);
|
||||
drop(local);
|
||||
let _unrelated = String::from("UAF");
|
||||
thread.join().unwrap();
|
||||
}
|
17
tests/ui/coroutine/resume-arg-outlives-2.stderr
Normal file
17
tests/ui/coroutine/resume-arg-outlives-2.stderr
Normal file
@ -0,0 +1,17 @@
|
||||
error[E0521]: borrowed data escapes outside of function
|
||||
--> $DIR/resume-arg-outlives-2.rs:20:5
|
||||
|
|
||||
LL | fn demo<'not_static>(s: &'not_static str) -> thread::JoinHandle<()> {
|
||||
| ----------- - `s` is a reference that is only valid in the function body
|
||||
| |
|
||||
| lifetime `'not_static` defined here
|
||||
...
|
||||
LL | generator.as_mut().resume(s); // <- generator hoards it as `let ctx`.
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| `s` escapes the function body here
|
||||
| argument requires that `'not_static` must outlive `'static`
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0521`.
|
27
tests/ui/coroutine/resume-arg-outlives.rs
Normal file
27
tests/ui/coroutine/resume-arg-outlives.rs
Normal file
@ -0,0 +1,27 @@
|
||||
// Regression test for 132104
|
||||
|
||||
#![feature(coroutine_trait, coroutines)]
|
||||
|
||||
use std::ops::Coroutine;
|
||||
use std::pin::Pin;
|
||||
|
||||
fn demo<'not_static>(s: &'not_static str) -> Pin<Box<impl Coroutine<&'not_static str> + 'static>> {
|
||||
let mut generator = Box::pin({
|
||||
#[coroutine]
|
||||
move |ctx: &'not_static str| {
|
||||
yield;
|
||||
dbg!(ctx);
|
||||
}
|
||||
});
|
||||
generator.as_mut().resume(s);
|
||||
generator
|
||||
//~^ ERROR lifetime may not live long enough
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let local = String::from("...");
|
||||
let mut coro = demo(&local);
|
||||
drop(local);
|
||||
let _unrelated = String::from("UAF");
|
||||
coro.as_mut().resume("");
|
||||
}
|
20
tests/ui/coroutine/resume-arg-outlives.stderr
Normal file
20
tests/ui/coroutine/resume-arg-outlives.stderr
Normal file
@ -0,0 +1,20 @@
|
||||
error: lifetime may not live long enough
|
||||
--> $DIR/resume-arg-outlives.rs:17:5
|
||||
|
|
||||
LL | fn demo<'not_static>(s: &'not_static str) -> Pin<Box<impl Coroutine<&'not_static str> + 'static>> {
|
||||
| ----------- lifetime `'not_static` defined here
|
||||
...
|
||||
LL | generator
|
||||
| ^^^^^^^^^ returning this value requires that `'not_static` must outlive `'static`
|
||||
|
|
||||
help: consider changing `impl Coroutine<&'not_static str> + 'static`'s explicit `'static` bound to the lifetime of argument `s`
|
||||
|
|
||||
LL | fn demo<'not_static>(s: &'not_static str) -> Pin<Box<impl Coroutine<&'not_static str> + 'not_static>> {
|
||||
| ~~~~~~~~~~~
|
||||
help: alternatively, add an explicit `'static` bound to this reference
|
||||
|
|
||||
LL | fn demo<'not_static>(s: &'static str) -> Pin<Box<impl Coroutine<&'not_static str> + 'static>> {
|
||||
| ~~~~~~~~~~~~
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
Loading…
Reference in New Issue
Block a user