Make Generator safe in presence of destructors.

This commit is contained in:
whitequark 2016-08-11 02:16:13 +00:00 committed by edef
parent 23cf17865d
commit a5d3430e63
3 changed files with 44 additions and 14 deletions

View File

@ -18,10 +18,10 @@ use context;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum State { pub enum State {
/// Generator can be resumed. This is the initial state. /// Generator can be resumed. This is the initial state.
Suspended, Runnable,
/// Generator cannot be resumed. This is the state of the generator after /// Generator cannot be resumed. This is the state of the generator after
/// the generator function has returned. /// the generator function has returned or panicked.
Finished Unavailable
} }
/// Generator wraps a function and allows suspending its execution more than /// Generator wraps a function and allows suspending its execution more than
@ -38,8 +38,8 @@ pub enum State {
/// stack using `unwrap()`. /// stack using `unwrap()`.
/// ///
/// `state()` can be used to determine whether the generator function has returned; /// `state()` can be used to determine whether the generator function has returned;
/// the state is `State::Suspended` after creation and suspension, and `State::Finished` /// the state is `State::Runnable` after creation and suspension, and `State::Unavailable`
/// once the generator function returns. /// once the generator function returns or panics.
/// ///
/// # Example /// # Example
/// ///
@ -93,7 +93,7 @@ impl<Item, Stack> Generator<Item, Stack>
} }
let mut generator = Generator { let mut generator = Generator {
state: State::Suspended, state: State::Runnable,
context: context::Context::new(stack, generator_wrapper::<Item, Stack, F>), context: context::Context::new(stack, generator_wrapper::<Item, Stack, F>),
phantom: PhantomData phantom: PhantomData
}; };
@ -113,11 +113,11 @@ impl<Item, Stack> Generator<Item, Stack>
/// Extracts the stack from a generator when the generator function has returned. /// Extracts the stack from a generator when the generator function has returned.
/// If the generator function has not returned /// If the generator function has not returned
/// (i.e. `self.state() == State::Suspended`), panics. /// (i.e. `self.state() == State::Runnable`), panics.
pub fn unwrap(self) -> Stack { pub fn unwrap(self) -> Stack {
match self.state { match self.state {
State::Suspended => panic!("Argh! Bastard! Don't touch that!"), State::Runnable => panic!("Argh! Bastard! Don't touch that!"),
State::Finished => unsafe { self.context.unwrap() } State::Unavailable => unsafe { self.context.unwrap() }
} }
} }
} }
@ -171,16 +171,26 @@ impl<Item, Stack> Iterator for Generator<Item, Stack>
#[inline] #[inline]
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
match self.state { match self.state {
State::Suspended => { State::Runnable => {
// Set the state to Unavailable. Since we have exclusive access to the generator,
// the only case where this matters is the generator function panics, after which
// it must not be invocable again.
self.state = State::Unavailable;
// Switch to the generator function.
let new_context = &mut self.context as *mut context::Context<Stack> as usize; let new_context = &mut self.context as *mut context::Context<Stack> as usize;
let val = unsafe { let val = unsafe {
ptr::read(context::Context::swap(&mut self.context, &self.context, new_context) ptr::read(context::Context::swap(&mut self.context, &self.context, new_context)
as *mut Option<Item>) as *mut Option<Item>)
}; };
if let None = val { self.state = State::Finished }
// Unless the generator function has returned, it can be switched to again, so
// set the state to Runnable.
if val.is_some() { self.state = State::Runnable }
val val
} }
State::Finished => None State::Unavailable => None
} }
} }
} }

View File

@ -22,8 +22,6 @@
//! [Stack](struct.Stack.html); //! [Stack](struct.Stack.html);
//! * a stack allocator based on anonymous memory mappings with guard pages, //! * a stack allocator based on anonymous memory mappings with guard pages,
//! [OsStack](struct.OsStack.html). //! [OsStack](struct.OsStack.html).
//!
//! **FIXME:** not actually safe yet in presence of unwinding
#[cfg(test)] #[cfg(test)]
#[macro_use] #[macro_use]

View File

@ -38,3 +38,25 @@ fn move_after_new() {
} }
rest(gen); rest(gen);
} }
#[test]
#[should_panic]
fn panic_safety() {
struct Wrapper {
gen: Generator<u32, OsStack>
}
impl Drop for Wrapper {
fn drop(&mut self) {
self.gen.next();
}
}
let stack = OsStack::new(4 << 20).unwrap();
let gen = Generator::new(stack, move |_yielder| {
panic!("foo")
});
let mut wrapper = Wrapper { gen: gen };
wrapper.gen.next();
}