Open
Description
This does not affect stabilization of async fn
unless #[thread_local]
is also stabilized
#![feature(generators, generator_trait, thread_local)]
use std::ops::Generator;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
#[thread_local]
static FOO: AtomicUsize = AtomicUsize::new(0);
fn foo() -> impl Generator<Yield = (String, usize), Return = ()> {
static || {
let x = &FOO;
loop {
let s = format!("{:p}", x);
yield (s, x.fetch_add(1, Ordering::SeqCst));
}
}
}
fn main() {
unsafe {
let mut g = thread::spawn(|| {
let mut g = foo();
println!("&FOO = {:p}; resume() = {:?}", &FOO, g.resume());
g
}).join().unwrap();
println!("&FOO = {:p}; resume() = {:?}", &FOO, g.resume());
thread::spawn(move || {
println!("&FOO = {:p}; resume() = {:?}", &FOO, g.resume());
}).join().unwrap();
}
}
Sample output:
&FOO = 0x7f48f9bff688; resume() = Yielded(("0x7f48f9bff688", 0))
&FOO = 0x7f48fafd37c8; resume() = Yielded(("0x7f48f9bff688", 1))
&FOO = 0x7f48f9bff688; resume() = Yielded(("0x7f48f9bff688", 0))
You can see by the pointer addresses and values inside FOO
that the same location was reused for the second child thread (it's a bit harder to show a crash) - this is clearly an use-after-free.
If we had in-language async
, the same problem could be demonstrated using those.
In non-generator functions, such references have function-local lifetimes and cannot escape.
With the stable thread_local!
from libstd
, user code gets access to the reference in a (non-generator/async) closure, which also doesn't allow escaping the reference.
Metadata
Metadata
Assignees
Labels
Area: The borrow checkerArea: CoroutinesCategory: An issue proposing an enhancement or a PR with one.`#![feature(coroutines)]``#![feature(thread_local)]`Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessMedium priorityStatus: This bug is tracked inside the repo by a `known-bug` test.Relevant to the compiler team, which will review and decide on the PR/issue.This issue requires a nightly compiler in some way.