diff --git a/README.md b/README.md index 96d2f55..6c4b632 100644 --- a/README.md +++ b/README.md @@ -32,22 +32,25 @@ impl<'a> nolife::Family<'a> for MyParsedDataFamily { // you generally want to replace all lifetimes in the struct with the one of the trait. } -// 2. Define an async function that setups the data and its borrowed representation: -async fn my_scope(mut time_capsule: nolife::TimeCapsule, - data_source: Vec /* 👈 all parameters that allow to build a `MyData` */) --> nolife::Never /* 👈 will be returned from loop */ { - let mut data = MyData(data_source); - let mut parsed_data = MyParsedData(&mut data); // imagine that this step is costly... - time_capsule.freeze_forever(&mut parsed_data).await // gives access to the parsed data to the outside. - /* 👆 reference to the borrowed data */ +// 2. Define a function that setups the data and its borrowed representation: +fn my_scope( + data_source: Vec, // 👈 all parameters that allow to build a `MyData` +) -> impl nolife::TopScope // 👈 use the helper type we declared +{ + nolife::scope!({ + let mut data = MyData(data_source); + let mut parsed_data = MyParsedData(&mut data); // imagine that this step is costly... + freeze_forever!(&mut parsed_data) // gives access to the parsed data to the outside. + /* 👆 reference to the borrowed data */ + }) } // 3. Open a `BoxScope` using the previously written async function: -let mut scope = nolife::DynBoxScope::pin(|time_capsule| my_scope(time_capsule, vec![0, 1, 2])); +let mut scope = nolife::BoxScope::::new_erased(my_scope(vec![0, 1, 2])); // 4. Store the `BoxScope` anywhere you want struct ContainsScope { - scope: nolife::DynBoxScope + scope: nolife::BoxScope, /* other data */ } @@ -65,12 +68,6 @@ This crate only provide a single kind of scope at the moment An `RcScope` or `MutexScope` could be future extensions -# Inner async support - -At the moment, although the functions passed to [`BoxScope::new`] are asynchronous, they should not `await` futures other than the [`FrozenFuture`]. Attempting to do so **will result in a panic** if the future does not resolve immediately. - -Future versions of this crate could provide async version of [`BoxScope::enter`] to handle the asynchronous use case. - # License Licensed under either of [Apache License](./LICENSE-APACHE), Version 2.0 or [MIT license](./LICENSE-MIT) at your option. diff --git a/src/box_scope.rs b/src/box_scope.rs index ef4a2fa..b30889e 100644 --- a/src/box_scope.rs +++ b/src/box_scope.rs @@ -1,86 +1,63 @@ -use std::{future::Future, mem::ManuallyDrop}; +use std::{ + future::Future, + mem::{self, MaybeUninit}, + ptr::NonNull, +}; -use crate::{scope::Scope, Family, Never, TimeCapsule}; +use crate::{raw_scope::RawScope, Family, Never, TopScope}; /// A dynamic scope tied to a Box. /// /// This kind of scopes uses a dynamic allocation. /// In exchange, it is fully `'static` and can be moved after creation. #[repr(transparent)] -pub struct BoxScope(std::ptr::NonNull>) +pub struct BoxScope + 'static>( + std::ptr::NonNull>, +) where T: for<'a> Family<'a>, F: Future; -/// An unopened, dynamic scope tied to a Box. -/// -/// This kind of scopes uses a dynamic allocation. -/// In exchange, it is fully `'static` and can be moved after creation. -struct ClosedBoxScope(std::ptr::NonNull>) -where - T: for<'a> Family<'a>, - F: Future; - -impl Drop for ClosedBoxScope +impl Drop for BoxScope where T: for<'a> Family<'a>, F: Future, { fn drop(&mut self) { - unsafe { drop(Box::from_raw(self.0.as_ptr())) } + // SAFETY: this `Box::from_raw` pairs with a `Box::into_raw` + // in the `new_typed` constructor. The type `F` is not the same, + // but `MaybeUninit` and `F` are repr(transparent)-compatible + // and RawScope is repr(C), so the Box frees the same memory. + // Furthermore, the `new_typed` constructor ensured that F is properly + // initialized so it may be dropped. + // + // Finally, the drop order of implicitly first dropping self.0.state + // and THEN self.0.active_fut goes a bit against the typical self-referencing + // structs assumptions, however self.0.state is a pointer and has no drop glue. + drop(unsafe { Box::from_raw(self.0.as_ptr()) }) } } -impl ClosedBoxScope +impl BoxScope where T: for<'a> Family<'a>, - F: Future, { - /// Creates a new unopened scope. - fn new() -> Self { - let b = Box::new(Scope::new()); - let b = Box::leak(b); - Self(b.into()) - } - - /// Opens this scope, making it possible to call [`BoxScope::enter`] on the scope. + /// Ties the passed scope to the heap. + /// + /// This function erased the `Future` generic type of the [`TopScope`], at the cost + /// of using a dynamic function call to poll the future. + /// + /// If the `Future` generic type can be inferred, it can be more efficient to use [`BoxScope::new_typed`]. /// /// # Panics /// - /// - If `producer` panics. - fn open

(self, producer: P) -> BoxScope + /// - If `scope` panics. + pub fn new_erased>(scope: S) -> Self where - P: FnOnce(TimeCapsule) -> F, + S::Future: 'static, { - // SAFETY: `self.0` is dereference-able due to coming from a `Box`. - unsafe { Scope::open(self.0, producer) } - - let open_scope = BoxScope(self.0); - - // SAFETY: don't call drop on self to avoid double-free since the resource of self was moved to `open_scope` - std::mem::forget(self); - - open_scope - } -} - -impl Drop for BoxScope -where - T: for<'a> Family<'a>, - F: Future, -{ - fn drop(&mut self) { - // SAFETY: created from a Box in the constructor, so dereference-able. - let this = unsafe { self.0.as_ref() }; - // SAFETY: we MUST release the `RefMut` before calling drop on the `Box` otherwise we'll call its - // destructor after releasing its backing memory, causing uaf - { - let mut fut = this.active_fut.borrow_mut(); - // unwrap: fut was set in open - let fut = fut.as_mut().unwrap(); - unsafe { ManuallyDrop::drop(fut) }; - } - unsafe { drop(Box::from_raw(self.0.as_ptr())) } + let this = mem::ManuallyDrop::new(BoxScope::new_typed(scope)); + Self(this.0) } } @@ -89,19 +66,59 @@ where T: for<'a> Family<'a>, F: Future, { - /// Creates a new scope from a producer. + /// Ties the passed scope to the heap. + /// + /// This function retains the `Future` generic type from the [`TopScope`]. + /// To store the [`BoxScope`] in a struct, it can be easier to use [`BoxScope::new_erased`]. /// /// # Panics /// - /// - If `producer` panics. - pub fn new

(producer: P) -> BoxScope + /// - If `scope` panics. + pub fn new_typed>(scope: S) -> BoxScope where - P: FnOnce(TimeCapsule) -> F, + S: TopScope, { - let scope = ClosedBoxScope::new(); - scope.open(producer) + let raw_scope = Box::new(RawScope::::new_uninit()); + let raw_scope: *mut RawScope> = Box::into_raw(raw_scope); + struct Guard { + raw_scope: *mut Sc, + } + // guard ensures Box is freed on panic (i.e. if scope.run panics) + let panic_guard = Guard { raw_scope }; + impl Drop for Guard { + fn drop(&mut self) { + // SAFETY: defuse below makes sure this only happens on panic, + // in this case, self.raw_scope is still in the same uninitialized state + // and not otherwise being cleaned up, so this `Box::from_raw` pairs with + // `Box::into_raw` above + drop(unsafe { Box::from_raw(self.raw_scope) }) + } + } + + let raw_scope: *mut RawScope = raw_scope.cast(); + + // SAFETY: + // 1. `raw_scope` allocated by the `Box` so is valid memory, although the future is not yet initialized + // 2. `raw_scope` was created from a valid `RawScope::>`, so `state` is fully initialized. + // + // Note: as a post-condition of `RawScope`, `raw_scope` is fully initialized. + unsafe { + RawScope::open(raw_scope, scope); + } + + mem::forget(panic_guard); // defuse guard + // (guard field has no drop glue, so this does not leak anything, it just skips the above `Drop` impl) + + // SAFETY: `raw_scope` allocated by the `Box` so is non-null. + BoxScope(unsafe { NonNull::new_unchecked(raw_scope) }) } +} +impl BoxScope +where + T: for<'a> Family<'a>, + F: Future, +{ /// Enters the scope, making it possible to access the data frozen inside of the scope. /// /// # Panics @@ -113,21 +130,10 @@ where where G: for<'a> FnOnce(&'a mut >::Family) -> Output, { - // SAFETY: `self.0` is dereference-able due to coming from a `Box`. - unsafe { Scope::enter(self.0, f) } - } -} - -impl crate::DynBoxScope -where - T: for<'a> Family<'a>, -{ - /// Convenient function to create a new pinned scope from a producer. - pub fn pin(producer: P) -> Self - where - P: FnOnce(TimeCapsule) -> F, - F: Future + 'static, - { - Self::new(|time_capsule| Box::pin(producer(time_capsule))) + // SAFETY: + // 1. `self.0` is valid as a post-condition of `new_typed`. + // 2. The object pointed to by `self.0` did not move and won't before deallocation. + // 3. `BoxScope::enter` takes an exclusive reference and the reference passed to `f` cannot escape `f`. + unsafe { RawScope::enter(self.0, f) } } } diff --git a/src/counterexamples.rs b/src/counterexamples.rs index e7f9c87..2ba22f7 100644 --- a/src/counterexamples.rs +++ b/src/counterexamples.rs @@ -7,7 +7,7 @@ //! ## Covariant escapes to inner //! //! ```compile_fail,E0597 -//! use nolife::{Family, BoxScope, TimeCapsule}; +//! use nolife::{scope, BoxScope, Family}; //! //! struct Covariant<'a> { //! x: &'a str, @@ -21,15 +21,13 @@ //! //! fn covariant_inner() { //! { -//! let mut scope = BoxScope::new( -//! |mut time_capsule: TimeCapsule| async move { -//! let mut f = Covariant { x: "bbb" }; -//! loop { -//! time_capsule.freeze(&mut f).await; -//! println!("Called {}", f.x) -//! } -//! }, -//! ); +//! let mut scope = BoxScope::::new_typed(scope!({ +//! let mut f = Covariant { x: "bbb" }; +//! loop { +//! freeze!(&mut f); +//! println!("Called {}", f.x) +//! } +//! })); //! //! { //! let s = String::from("foodog"); @@ -44,8 +42,8 @@ //! ## Covariant escapes to outer //! //! ```compile_fail,E0521 +//! use nolife::{scope, BoxScope, Family}; //! use std::cell::Cell; -//! use nolife::{Family, BoxScope, TimeCapsule}; //! //! struct Covariant<'a> { //! x: &'a str, @@ -60,15 +58,13 @@ //! fn covariant_outer() { //! let output = Cell::new("foo"); //! { -//! let mut scope = BoxScope::new( -//! |mut time_capsule: TimeCapsule| async move { -//! let mut f = Covariant { x: "bbb" }; -//! loop { -//! time_capsule.freeze(&mut f).await; -//! println!("Called {}", f.x) -//! } -//! }, -//! ); +//! let mut scope = BoxScope::::new_erased(scope!({ +//! let mut f = Covariant { x: "bbb" }; +//! loop { +//! freeze!(&mut f); +//! println!("Called {}", f.x) +//! } +//! })); //! //! { //! scope.enter(|f| { @@ -83,8 +79,7 @@ //! ## Covariant escapes to inner 2 //! //! ```compile_fail,E0597 -//! use nolife::{BoxScope, Family, TimeCapsule}; -//! +//! use nolife::{scope, BoxScope, Family}; //! //! struct Covariant<'a> { //! x: &'a str, @@ -98,16 +93,14 @@ //! //! fn box_covariant_inner() { //! { -//! let mut scope = BoxScope::new( -//! |mut time_capsule: TimeCapsule| async move { -//! let x = String::from("aaaaa"); -//! let mut f = Covariant { x: &x }; -//! loop { -//! time_capsule.freeze(&mut f).await; -//! println!("Called {}", f.x) -//! } -//! }, -//! ); +//! let mut scope = BoxScope::::new_typed(scope!({ +//! let x = String::from("aaaaa"); +//! let mut f = Covariant { x: &x }; +//! loop { +//! freeze!(&mut f); +//! println!("Called {}", f.x) +//! } +//! })); //! //! { //! let s = String::from("dangling"); @@ -115,13 +108,12 @@ //! } //! }; //! } - //! ``` //! //! ## Covariant escapes to outer 2 //! //! ```compile_fail -//! use nolife::{BoxScope, Family, TimeCapsule}; +//! use nolife::{scope, BoxScope, Family}; //! use std::cell::Cell; //! //! struct Covariant<'a> { @@ -137,16 +129,14 @@ //! fn box_covariant_outer() { //! let outer = Cell::new("foo"); //! { -//! let mut scope = BoxScope::new( -//! |mut time_capsule: TimeCapsule| async move { -//! let x = String::from("aaaaa"); -//! let mut f = Covariant { x: &x }; -//! loop { -//! time_capsule.freeze(&mut f).await; -//! println!("Called {}", f.x) -//! } -//! }, -//! ); +//! let mut scope = BoxScope::::new_typed(scope!({ +//! let x = String::from("aaaaa"); +//! let mut f = Covariant { x: &x }; +//! loop { +//! freeze!(&mut f); +//! println!("Called {}", f.x) +//! } +//! })); //! //! let inner = scope.enter(|f| f.x); //! outer.set(inner); @@ -158,7 +148,7 @@ //! ## Covariant with `Drop` //! //! ```compile_fail,E0597 -//! use nolife::{Family, BoxScope, TimeCapsule}; +//! use nolife::{scope, BoxScope, Family}; //! //! struct CovariantDrop<'a> { //! x: &'a str, @@ -177,19 +167,16 @@ //! //! fn covariant_drop() { //! { -//! let mut scope = BoxScope::new( -//! |mut time_capsule: TimeCapsule| async move { -//! let mut f = CovariantDrop { x: "inner" }; -//! loop { -//! println!("Called {}", f.x); -//! time_capsule.freeze(&mut f).await; -//! } -//! }, -//! ); +//! let mut scope = BoxScope::::new_typed(scope!({ +//! let mut f = CovariantDrop { x: "inner" }; +//! loop { +//! println!("Called {}", f.x); +//! freeze!(&mut f); +//! } +//! })); //! //! let outer = String::from("outer"); //! -//! //! { //! scope.enter(|f| { //! f.x = &outer; @@ -218,19 +205,17 @@ //! let outer: Cell<&str> = Cell::new("toto"); //! //! { -//! let mut scope = nolife::BoxScope::new( -//! |mut time_capsule: nolife::TimeCapsule| async move { -//! loop { -//! let mut x = String::from("inner"); -//! -//! let mut f = Contravariant { -//! f: Box::new(|_| {}), -//! }; -//! time_capsule.freeze(&mut f).await; -//! (f.f)(&mut x); -//! } -//! }, -//! ); +//! let mut scope = nolife::BoxScope::::new_typed(nolife::scope!({ +//! loop { +//! let mut x = String::from("inner"); +//! +//! let mut f = Contravariant { +//! f: Box::new(|_| {}), +//! }; +//! freeze!(&mut f); +//! (f.f)(&mut x); +//! } +//! })); //! //! scope.enter(|f| { //! f.f = Box::new(|inner| outer.set(inner)); @@ -243,7 +228,7 @@ //! ## Covariant coming from a previous scope //! //! ```compile_fail,E0597 -//! use nolife::{Family, BoxScope, TimeCapsule}; +//! use nolife::{scope, BoxScope, Family}; //! //! struct Covariant<'a> { //! x: &'a str, @@ -255,18 +240,15 @@ //! type Family = Covariant<'a>; //! } //! -//! //! fn covariant_inner() { //! { -//! let mut scope = BoxScope::new( -//! |mut time_capsule: TimeCapsule| async move { -//! let mut f = Covariant { x: "bbb" }; -//! loop { -//! time_capsule.freeze(&mut f).await; -//! println!("Called {}", f.x) -//! } -//! }, -//! ); +//! let mut scope = BoxScope::::new_erased(scope!({ +//! let mut f = Covariant { x: "bbb" }; +//! loop { +//! freeze!(&mut f); +//! println!("Called {}", f.x) +//! } +//! })); //! { //! let s = String::from("foodog"); //! @@ -276,7 +258,84 @@ //! }); //! } //! } -//! scope.enter(|f| ()); +//! scope.enter(|_f| ()); //! } //! } //! ``` +//! +//! # Recursion is not allowed +//! +//! ```compile_fail,E0733 +//! use nolife::{scope, SingleFamily, TopScope}; +//! +//! fn recursive_sub_scope() { +//! fn some_scope(x: u32) -> impl TopScope> { +//! scope!({ +//! if x == 0 { +//! freeze_forever!(&mut 0) +//! } else { +//! sub_scope!(some_scope(x - 1)); +//! freeze_forever!(&mut 0) +//! } +//! }) +//! } +//! } +//! ``` +//! +//! # Attempting to save the frozen future in an async block +//! +//! ```compile_fail,E0767,E0267 +//! use nolife::{scope, SingleFamily, TopScope}; +//! fn forcing_inner_async() { +//! fn some_scope(x: u32) -> impl TopScope> { +//! scope!({ +//! let fut = async { +//! freeze!(&mut 0); +//! }; +//! // poll future +//! // bang! +//! panic!() +//! }) +//! } +//! } +//! ``` +//! +//! # Dropping a borrowed input to a scope. +//! +//! ```compile_fail,E505 +//! use nolife::{scope, BoxScope, SingleFamily, TopScope}; +//! +//! fn ref_scope() { +//! fn scope_with_ref<'scope, 'a: 'scope>( +//! s: &'a str, +//! ) -> impl TopScope> + 'scope { +//! scope!({ freeze_forever!(&mut s.len()) }) +//! } +//! let x = "Intel the Beagle".to_string(); +//! let mut scope = BoxScope::, _>::new_typed(scope_with_ref(&x)); +//! +//! drop(x); +//! +//! scope.enter(|x| assert_eq!(*x, 16)); +//! } +//! ``` +//! +//! # Dropping a borrowed input to a scope, erased version +//! +//! ```compile_fail,E597,E505 +//! use nolife::{scope, BoxScope, SingleFamily, TopScope}; +//! +//! fn ref_scope() { +//! fn scope_with_ref<'scope, 'a: 'scope>( +//! s: &'a str, +//! ) -> impl TopScope> + 'scope { +//! scope!({ freeze_forever!(&mut s.len()) }) +//! } +//! let x = "Intel the Beagle".to_string(); +//! let mut scope = BoxScope::, _>::new_erased(scope_with_ref(&x)); +//! +//! drop(x); +//! +//! scope.enter(|x| assert_eq!(*x, 16)); +//! } +//! ``` diff --git a/src/lib.rs b/src/lib.rs index b220c63..a2d90a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ #![warn(rustdoc::broken_intra_doc_links)] #![warn(missing_docs)] +#![deny(elided_lifetimes_in_paths)] +#![deny(unsafe_op_in_unsafe_fn)] #![doc = include_str!("../README.md")] #![doc( html_favicon_url = "https://raw.githubusercontent.com/dureuill/nolife/main/assets/nolife-tr.png?raw=true" @@ -9,17 +11,19 @@ )] mod box_scope; +#[cfg(not(miri))] pub mod counterexamples; -mod scope; -pub use scope::{FrozenFuture, TimeCapsule}; +mod raw_scope; +pub mod scope; +#[doc(hidden)] +pub use raw_scope::{FrozenFuture, TimeCapsule}; /// From , originally from /// [genawaiter](https://lib.rs/crates/genawaiter). mod waker; pub use box_scope::BoxScope; - -/// Convenient type alias for a [`BoxScope`] whose future is an erased boxed future. -pub type DynBoxScope = BoxScope>>>; +pub use scope::Scope; +pub use scope::TopScope; use std::marker::PhantomData; @@ -27,7 +31,7 @@ use std::marker::PhantomData; /// /// Since this enum has no variant, a value of this type can never actually exist. /// This type is similar to [`std::convert::Infallible`] and used as a technicality to ensure that -/// functions passed to [`BoxScope::new`] never return. +/// functions passed to [`BoxScope::new_erased`] never return. /// /// ## Future compatibility /// @@ -50,8 +54,8 @@ pub trait Family<'a> { /// Types that don't contain a lifetime are `'static`, and have one obvious family. /// /// The usefulness of using `'static` types in the scopes of this crate is dubious, but should you want to do this, -/// for any `T : 'static` pass a `TimeCapsule>` to your async function. -struct SingleFamily(PhantomData); +/// for any `T : 'static` you can use this family. +pub struct SingleFamily(PhantomData); impl<'a, T: 'static> Family<'a> for SingleFamily { type Family = T; } @@ -61,15 +65,13 @@ mod test { use super::*; #[test] fn produce_output() { - let mut scope = BoxScope::new( - |mut time_capsule: TimeCapsule>| async move { - let mut x = 0u32; - loop { - time_capsule.freeze(&mut x).await; - x += 1; - } - }, - ); + let mut scope = BoxScope::, _>::new_typed(scope!({ + let mut x = 0u32; + loop { + freeze!(&mut x); + x += 1; + } + })); assert_eq!(scope.enter(|x| *x + 42), 42); assert_eq!(scope.enter(|x| *x + 42), 43); @@ -78,127 +80,121 @@ mod test { } #[test] - fn panicking_future() { - let mut scope = BoxScope::new(|_: TimeCapsule>| async move { panic!() }); + fn produce_output_erased() { + let mut scope = BoxScope::>::new_erased(scope!({ + let mut x = 0u32; + loop { + freeze!(&mut x); + x += 1; + } + })); - assert!(matches!( - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - scope.enter(|x| println!("{x}")) - })), - Err(_) - )); + assert_eq!(scope.enter(|x| *x + 42), 42); + assert_eq!(scope.enter(|x| *x + 42), 43); + scope.enter(|x| *x += 100); + assert_eq!(scope.enter(|x| *x + 42), 145); + } + fn must_panic(f: F) + where + F: FnOnce() -> R, + { assert!(matches!( - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - scope.enter(|x| println!("{x}")) - })), + std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)), Err(_) )); } + #[test] + fn panicking_producer() { + must_panic(|| { + BoxScope::, _>::new_typed(unsafe { + crate::scope::new_scope(|_time_capsule| { + panic!("panicking producer"); + #[allow(unreachable_code)] + async { + loop {} + } + }) + }) + }); + } + + #[test] + fn panicking_future() { + let mut scope = BoxScope::, _>::new_typed(scope!({ panic!() })); + + must_panic(|| scope.enter(|x| println!("{x}"))); + must_panic(|| scope.enter(|x| println!("{x}"))); + } + #[test] fn panicking_future_after_once() { - let mut scope = BoxScope::new( - |mut time_capsule: TimeCapsule>| async move { - let mut x = 0u32; - time_capsule.freeze(&mut x).await; - panic!() - }, - ); + let mut scope = BoxScope::, _>::new_typed(scope!({ + let mut x = 0u32; + freeze!(&mut x); + panic!() + })); scope.enter(|x| println!("{x}")); - assert!(matches!( - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - scope.enter(|x| println!("{x}")) - })), - Err(_) - )); - - assert!(matches!( - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - scope.enter(|x| println!("{x}")) - })), - Err(_) - )) + must_panic(|| scope.enter(|x| println!("{x}"))); + must_panic(|| scope.enter(|x| println!("{x}"))); } #[test] fn panicking_enter() { - let mut scope = BoxScope::new( - |mut time_capsule: TimeCapsule>| async move { - let mut x = 0u32; - loop { - time_capsule.freeze(&mut x).await; - x += 1; - } - }, - ); + let mut scope = BoxScope::, _>::new_typed(scope!({ + let mut x = 0u32; + loop { + freeze!(&mut x); + x += 1; + } + })); scope.enter(|x| assert_eq!(*x, 0)); - assert!(matches!( - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - scope.enter(|_| panic!()) - })), - Err(_) - )); + must_panic(|| scope.enter(|_| panic!())); // '1' skipped due to panic scope.enter(|x| assert_eq!(*x, 2)); } #[test] - fn cursed_time_capsule_inception() { - struct TimeCapsuleFamily; - impl<'a> Family<'a> for TimeCapsuleFamily { - // Yo dawg I heard you like time capsules, so I put time capsules in your time capsules - type Family = TimeCapsule; + fn ref_scope() { + fn scope_with_ref<'scope, 'a: 'scope>( + s: &'a str, + ) -> impl TopScope> + 'scope { + scope!({ freeze_forever!(&mut s.len()) }) } + let x = "Intel the Beagle".to_string(); + let mut scope = BoxScope::, _>::new_typed(scope_with_ref(&x)); + + scope.enter(|x| assert_eq!(*x, 16)); + } + + #[test] + fn awaiting_in_scope_ready() { + let mut scope = BoxScope::>::new_erased(scope!({ + freeze!(&mut 40); + std::future::ready(()).await; + freeze_forever!(&mut 42) + })); + + scope.enter(|x| assert_eq!(*x, 40)); + scope.enter(|x| assert_eq!(*x, 42)); + } + + #[test] + fn awaiting_in_scope_panics() { + let mut scope = BoxScope::>::new_erased(scope!({ + freeze!(&mut 40); + let () = std::future::pending().await; + freeze_forever!(&mut 42) + })); + + scope.enter(|x| assert_eq!(*x, 40)); - // we'll use this to check we panicked at the correct location, RTTI-style - struct ReachedTheEnd; - - let mut outer_scope = BoxScope::new( - |mut time_capsule: TimeCapsule| async move { - let mut inner_scope = BoxScope::new( - |mut inner_time_capsule: TimeCapsule| async move { - loop { - // very cursed - time_capsule.freeze(&mut inner_time_capsule).await - } - }, - ); - - // we're expecting a panic here; let's catch it and check we're still safe - assert!(matches!( - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - inner_scope.enter(|_exchanged_time_capsule| {}); - })), - Err(_) - )); - - // we can try again - assert!(matches!( - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - inner_scope.enter(|_exchanged_time_capsule| {}); - })), - Err(_) - )); - - // we can't loop here because we relinquished our time capsule to the lambda - std::panic::panic_any(ReachedTheEnd) - }, - ); - - // will panic with the panic at the end - match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - outer_scope.enter(|_time_capsule| {}); - })) { - Ok(_) => panic!("did not panic as expected"), - Err(panic) => panic - .downcast::() - .expect("panicked at the wrong location"), - }; + must_panic(|| scope.enter(|x| assert_eq!(*x, 42))); } } diff --git a/src/raw_scope.rs b/src/raw_scope.rs new file mode 100644 index 0000000..6c3520b --- /dev/null +++ b/src/raw_scope.rs @@ -0,0 +1,246 @@ +use crate::{waker, Family, Never, TopScope}; +use std::{ + future::Future, + marker::PhantomData, + mem::MaybeUninit, + pin::Pin, + ptr::{addr_of_mut, NonNull}, + task::Poll, +}; + +/// The future resulting from using a time capsule to freeze some scope. +pub struct FrozenFuture<'a, 'b, T> +where + T: for<'c> Family<'c>, + 'b: 'a, +{ + // Using a pointer here helps ensure that while RawScope is dropped, + // dropping of F can't assert unique access to the .state field by + // operations that "touch" the FrozenFuture such moving it or passing it to a function. + // (This probably wasn't exploitable with the scope! macro, but it still seems + // more correct this way.) + mut_ref: State, + state: *mut State, + marker: PhantomData<&'a mut >::Family>, +} + +/// Passed to the closures of a scope so that they can freeze the scope. +pub struct TimeCapsule +where + T: for<'a> Family<'a>, +{ + pub(crate) state: *mut State, +} + +impl Clone for TimeCapsule +where + T: for<'a> Family<'a>, +{ + fn clone(&self) -> Self { + *self + } +} + +impl Copy for TimeCapsule where T: for<'a> Family<'a> {} + +impl TimeCapsule +where + T: for<'a> Family<'a>, +{ + /// Freeze a scope, making the data it has borrowed available to the outside. + /// + /// Once a scope is frozen, its borrowed data can be accessed through [`crate::BoxScope::enter`]. + /// + /// For simple cases where you don't need to execute code in the scope between two calls to `enter`, + /// use [`Self::freeze_forever`]. + pub fn freeze<'a, 'b>( + &'a mut self, + t: &'a mut >::Family, + ) -> FrozenFuture<'a, 'b, T> + where + 'b: 'a, + { + FrozenFuture { + mut_ref: Some(NonNull::from(t).cast()), + state: self.state, + marker: PhantomData, + } + } + + /// Freeze a scope forever, making the data it has borrowed available to the outside. + /// + /// Once a scope is frozen, its borrowed data can be accessed through [`crate::BoxScope::enter`]. + /// + /// If you need to execute code between two calls to [`crate::BoxScope::enter`], use [`Self::freeze`]. + pub async fn freeze_forever<'a, 'b>( + &'a mut self, + t: &'a mut >::Family, + ) -> Never { + loop { + self.freeze(t).await + } + } +} + +// This type is a pointer-type and lifetime-erased equivalent of +// Option<&'a mut >::Family>. +// +// NonNull differs in variance, which would typically be corrected +// with a `PhantomData` marker, however a projection like +// `>::Family>` has T invariant already anyway. +pub(crate) type State = Option>::Family>>; + +/// Underlying representation of a scope. +// SAFETY: repr C to ensure conversion between RawScope> and RawScope +// does not rely on unstable memory layout. +#[repr(C)] +pub(crate) struct RawScope +where + T: for<'a> Family<'a>, +{ + state: State, + active_fut: F, +} + +impl RawScope +where + T: for<'a> Family<'a>, +{ + /// Creates a new closed scope. + pub fn new_uninit() -> RawScope> { + RawScope { + state: None, + active_fut: MaybeUninit::uninit(), + } + } +} + +struct RawScopeFields +where + T: for<'a> Family<'a>, +{ + state: *mut State, + active_fut: *mut F, +} +impl RawScope +where + T: for<'a> Family<'a>, +{ + /// SAFETY: + /// + /// 1. `this` points to an allocation that can hold a `RawScope`, + /// not necessarily initialized or properly aligned. + unsafe fn fields(this: *mut Self) -> RawScopeFields { + RawScopeFields { + // SAFETY: precondition (1) + state: unsafe { addr_of_mut!((*this).state) }, + // SAFETY: precondition (1) + active_fut: unsafe { addr_of_mut!((*this).active_fut) }, + } + } +} + +impl RawScope +where + T: for<'a> Family<'a>, + F: Future, +{ + /// # Safety + /// + /// 1. `this` points to a properly aligned allocation that can hold a `RawScope`, where `active_fut` is not necessarily initialized. + /// 2. `this.state` is initialized. + /// + /// # Post-condition + /// + /// 1. `this.active_fut` is fully initialized + pub(crate) unsafe fn open>(this: *mut Self, scope: S) + where + T: for<'a> Family<'a>, + F: Future, + S: TopScope, + { + // SAFETY: precondition (1) + let RawScopeFields { state, active_fut } = unsafe { Self::fields(this) }; + + let time_capsule = TimeCapsule { state }; + + // SAFETY: + // - precondition (1) + // - using `scope.run` from the executor + unsafe { + active_fut.write(scope.run(time_capsule)); + } + } +} + +impl RawScope +where + T: for<'a> Family<'a>, + F: Future, +{ + /// # Safety + /// + /// 1. `this` points to a properly aligned, fully initialized `RawScope`. + /// 2. `this` verifies the guarantees of `Pin` (one of its fields is pinned in this function) + /// 3. No other exclusive reference to the frozen value. In particular, no concurrent calls to this function. + #[allow(unused_unsafe)] + pub(crate) unsafe fn enter<'borrow, Output: 'borrow, G>(this: NonNull, f: G) -> Output + where + G: for<'a> FnOnce(&'a mut >::Family) -> Output, + { + // SAFETY: precondition (1) + let RawScopeFields { state, active_fut } = unsafe { Self::fields(this.as_ptr()) }; + + // SAFETY: precondition (2) + let active_fut: Pin<&mut F> = unsafe { Pin::new_unchecked(&mut *active_fut) }; + + match active_fut.poll(&mut std::task::Context::from_waker(&waker::create())) { + Poll::Ready(never) => match never {}, + Poll::Pending => {} + } + + // SAFETY: + // - dereferenceable: precondition (1) + // - drop: reading a reference (no drop glue) + // - aliasing: precondition (3) + `mut_ref` cannot escape this function via `f` + // - lifetime: the value is still live due to the precondition on `Scope::run`, + // preventing + let mut_ref = unsafe { + state + .read() + .expect("The scope's future did not fill the value") + .as_mut() + }; + + f(mut_ref) + } +} + +impl<'a, 'b, T> Future for FrozenFuture<'a, 'b, T> +where + T: for<'c> Family<'c>, +{ + type Output = (); + + fn poll( + mut self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> Poll { + // SAFETY: + // - state was set to a valid value in [`TimeCapsule::freeze`] + // - the value is still 'live', due to the lifetime in `FrozenFuture` + let state: &mut State = unsafe { &mut *self.state }; + if state.is_none() { + let mut_ref = self + .mut_ref + .take() + .expect("poll called several times on the same future"); + + *state = Some(mut_ref); + Poll::Pending + } else { + *state = None; + Poll::Ready(()) + } + } +} diff --git a/src/scope.rs b/src/scope.rs index 3229192..1ae5f55 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,200 +1,223 @@ -use crate::{waker, Family, Never}; -use std::{ - cell::{Cell, RefCell}, - future::Future, - marker::PhantomData, - mem::ManuallyDrop, - ops::DerefMut, - pin::Pin, - task::Poll, -}; +//! Defines a generic `Scope` as a trait that can be instantiated as a [`crate::BoxScope`]. +use std::{future::Future, marker::PhantomData}; -/// The future resulting from using a time capsule to freeze some scope. -pub struct FrozenFuture<'a, 'b, T> -where - T: for<'c> Family<'c>, - 'b: 'a, -{ - mut_ref: Cell>::Family>>, - state: *const State, -} +use crate::{Family, Never, TimeCapsule}; + +/// Trait sealed for safety. +/// +/// The trait is only implemented on [`crate::scope::Wrapper`] +pub(crate) trait Sealed {} -/// Passed to the closures of a scope so that they can freeze the scope. -pub struct TimeCapsule +impl Sealed for Wrapper where - T: for<'a> Family<'a>, + P: FnOnce(super::TimeCapsule) -> Future, + Family: for<'a> crate::Family<'a>, + Future: std::future::Future, { - state: *const State, } -impl TimeCapsule -where - T: for<'a> Family<'a>, -{ - /// Freeze a scope, making the data it has borrowed available to the outside. - /// - /// Once a scope is frozen, its borrowed data can be accessed through [`crate::BoxScope::enter`]. - /// - /// For simple cases where you don't need to execute code in the scope between two calls to `enter`, - /// use [`Self::freeze_forever`]. - pub fn freeze<'a, 'b>( - &'a mut self, - t: &'a mut >::Family, - ) -> FrozenFuture<'a, 'b, T> - where - 'b: 'a, - { - FrozenFuture { - mut_ref: Cell::new(Some(t)), - state: self.state, - } - } +/// A scope that can be frozen in time. +/// +/// To get a `Scope`, use the [`crate::scope!`] macro. +#[allow(private_bounds)] +pub trait Scope: Sealed { + /// The helper struct that serves to define the reference type. + type Family: for<'a> Family<'a>; + /// The output type of this scope. + type Output; + /// The underlying future that serves as a coroutine to freeze the scope. + type Future: Future; - /// Freeze a scope forever, making the data it has borrowed available to the outside. + /// Runs a scope by injecting a [`TimeCapsule`]. + /// + /// # Safety /// - /// Once a scope is frozen, its borrowed data can be accessed through [`crate::BoxScope::enter`]. + /// - This function is only safe if the produced future is awaited immediately. /// - /// If you need to execute code between two calls to [`crate::BoxScope::enter`], use [`Self::freeze`]. - pub async fn freeze_forever<'a, 'b>( - &'a mut self, - t: &'a mut >::Family, - ) -> Never { - loop { - self.freeze(t).await - } - } + /// Using the `sub_scope` macro inside a [`crate::scope!`] always verifies this condition and is therefore always safe. + unsafe fn run(self, time_capsule: TimeCapsule) -> Self::Future; } -struct State(Cell<*mut >::Family>) -where - T: for<'a> Family<'a>; +/// A top-level [`Scope`], always returning [`crate::Never`]. +/// +/// Create one using the [`crate::scope!`] macro. +pub trait TopScope: Scope {} -impl Default for State -where - T: for<'a> Family<'a>, -{ - fn default() -> Self { - Self(Cell::new(std::ptr::null_mut())) - } -} +impl TopScope for S where S: Scope {} -/// Underlying representation of a scope. -pub(crate) struct Scope +/// A wrapper for a producer. +/// +/// See [`Scope`] for more information. +struct Wrapper(P, PhantomData<*const Family>) where - T: for<'a> Family<'a>, - F: Future, -{ - pub(crate) active_fut: RefCell>>, - phantom: PhantomData<*const fn(TimeCapsule) -> F>, - state: State, -} + P: FnOnce(TimeCapsule) -> Future, + Family: for<'a> crate::Family<'a>, + Future: std::future::Future; -impl Scope +impl Scope for Wrapper where - T: for<'a> Family<'a>, - F: Future, + P: FnOnce(TimeCapsule) -> Future, + Family: for<'a> crate::Family<'a>, + Future: std::future::Future, { - /// Creates a new closed scope. - pub fn new() -> Self { - Self { - active_fut: RefCell::new(None), - phantom: PhantomData, - state: Default::default(), - } - } + type Family = Family; + type Output = Output; + type Future = Future; - /// # Safety - /// - /// The `this` parameter is *dereference-able*. - #[allow(unused_unsafe)] - pub(crate) unsafe fn open

(this: std::ptr::NonNull, producer: P) - where - P: FnOnce(TimeCapsule) -> F, - { - // SAFETY: `this` is dereference-able as per precondition. - let this = unsafe { this.as_ref() }; - let mut active_fut = this.active_fut.borrow_mut(); - if active_fut.is_some() { - panic!("Multiple calls to open") - } - let state: *const State = &this.state; - let time_capsule = TimeCapsule { state }; - let fut = producer(time_capsule); - *active_fut = Some(ManuallyDrop::new(fut)); - } - - /// # Safety - /// - /// The `this` parameter is *dereference-able*. - #[allow(unused_unsafe)] - pub(crate) unsafe fn enter<'borrow, Output: 'borrow, G>( - this: std::ptr::NonNull, - f: G, - ) -> Output - where - G: for<'a> FnOnce(&'a mut >::Family) -> Output, - { - // SAFETY: `this` is dereference-able as per precondition. - let this = unsafe { this.as_ref() }; - - let mut fut = this.active_fut.borrow_mut(); - let fut = fut.as_mut().unwrap().deref_mut(); - // SAFETY: self.active_fut is never moved by self after the first call to produce completes. - // self itself is pinned. - let fut = unsafe { Pin::new_unchecked(fut) }; - // SAFETY: we didn't do anything particular here before calling `poll`, which may panic, so - // we have nothing to handle. - match fut.poll(&mut std::task::Context::from_waker(&waker::create())) { - Poll::Ready(_) => unreachable!(), - Poll::Pending => {} - } - let state = this.state.0.get(); - // SAFETY: cast the lifetime of the Family to `'borrow`. - // This is safe to do - let state: *mut ::Family = state.cast(); - let output; - { - // SAFETY: The `state` variable has been set to a dereference-able value by the future, - // or kept its NULL value. - let state = unsafe { - state - .as_mut() - .expect("The scope's future did not fill the value") - }; - // SAFETY: we're already in a clean state here even if `f` panics. - // (not doing anything afterwards beside returning `output`) - output = f(state); - } - output + unsafe fn run(self, time_capsule: TimeCapsule) -> Self::Future { + (self.0)(time_capsule) } } -impl<'a, 'b, T> Future for FrozenFuture<'a, 'b, T> +#[doc(hidden)] +/// Constructs a new scope from a producer +/// +/// # Safety +/// +/// - This function is only safe if the producer guarantees that any call to `crate::TimeCapsule::freeze` or +/// `crate::TimeCapsule::freeze_forever` happens at the top level of the producer, +/// and that the resulting future is awaited immediately. +/// +/// Using the [`crate::scope!`] macro always verifies this condition and is therefore always safe. +pub unsafe fn new_scope( + producer: P, +) -> impl Scope where - T: for<'c> Family<'c>, + P: FnOnce(TimeCapsule) -> Future, + Family: for<'a> crate::Family<'a>, + Future: std::future::Future, { - type Output = (); - - fn poll( - self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> Poll { - // SAFETY: `state` has been set in the future by the scope - let state = unsafe { self.state.as_ref().unwrap() }; - if state.0.get().is_null() { - let mut_ref = self - .mut_ref - .take() - .expect("poll called several times on the same future"); - let mut_ref: *mut ::Family = mut_ref; - // SAFETY: Will be given back a reasonable lifetime in the `enter` method. - let mut_ref: *mut >::Family = mut_ref.cast(); + Wrapper(producer, PhantomData) +} - state.0.set(mut_ref); - Poll::Pending - } else { - state.0.set(std::ptr::null_mut()); - Poll::Ready(()) - } - } +/// A macro to open a scope that can be frozen in time. +/// +/// You can write code like you normally would in that scope, but you get 3 additional superpowers: +/// +/// 1. `freeze!(&mut x)`: interrupts execution of the scope until the next call to [`crate::BoxScope::enter`], +/// that will resume execution. The passed `&mut x` will be available to the next call to [`crate::BoxScope::enter`]. +/// 2. `freeze_forever!(&mut x)`: interrupts execution of the scope forever. +/// All future calls to [`crate::BoxScope::enter`] will have access to the passed `&mut x`. +/// 3. `subscope!(some_subscope(...))`: execute an expression that can be another function returning a `scope!` itself. +/// This is meant to be able to structure your code in functions. +/// +/// A `scope!` invocation returns some type that `impl Scope` or `impl TopScope` (when the scope never returns). +/// The `Family` type of the `Scope` typically needs to be annotated, whereas the `Future` type should not be. +/// +/// +/// # Using a subscope +/// +/// subscopes are useful to split your logic into smaller units (like functions), +/// and in particular for error handling. +/// ``` +/// use nolife::{BoxScope, Scope, SingleFamily, TopScope, scope}; +/// +/// +/// fn outer_scope( +/// input_data: Vec, +/// ) -> impl TopScope>>> { +/// scope!({ +/// for input in input_data { +/// match sub_scope!(inner_scope(input)) { +/// Ok(string) => { +/// freeze!(&mut Ok(Some(string))); +/// } +/// Err(err) => { +/// freeze_forever!(&mut Err(err)); +/// } +/// } +/// } +/// freeze_forever!(&mut Ok(None)) +/// }) +/// } +/// +/// fn inner_scope( +/// mut input: impl std::io::Read, +/// ) -> impl Scope>>, Output = std::io::Result> +/// { +/// scope!({ +/// let mut buf = String::new(); +/// input.read_to_string(&mut buf)?; +/// freeze!(&mut Ok(Some(buf.clone()))); +/// Ok(buf) +/// }) +/// } +/// ``` +/// # Using a scope with a reference in input +/// +/// ``` +/// use nolife::{SingleFamily, BoxScope, TopScope, scope}; +/// +/// fn scope_with_ref<'scope, 'a: 'scope>( +/// s: &'a str, +/// ) -> impl TopScope> + 'scope { +/// scope!({ freeze_forever!(&mut s.len()) }) +/// } +/// let x = "Intel the Beagle".to_string(); +/// let mut scope = BoxScope::, _>::new_typed(scope_with_ref(&x)); +/// +/// scope.enter(|x| assert_eq!(*x, 16)); +/// ``` +/// +/// # Panics +/// +/// The block passed to `scope` is technically an `async` block, but trying to `await` a future in this block +/// will always result in a panic. +#[macro_export] +macro_rules! scope { + ($b:block) => { + match move |#[allow(unused_variables, unused_mut)] mut time_capsule| async move { + 'check_top: { + #[allow(unreachable_code)] + if false { + break 'check_top (loop {}); + } + /// `freeze!(&mut x)` interrupts execution of the scope, making `&mut x` available to the next call + /// to [`nolife::BoxScope::enter`]. + /// + /// Execution will resume after a call to [`nolife::BoxScope::enter`]. + #[allow(unused_macros)] + macro_rules! freeze { + ($e:expr) => { + #[allow(unreachable_code)] + if false { + break 'check_top (loop {}); + } + $crate::TimeCapsule::freeze(&mut time_capsule, $e).await + } + } + /// `freeze_forever!(&mut x)` stops execution of the scope forever, making `&mut x` available to all future calls + /// to [`$crate::BoxScope::enter`]. + /// + /// Execution will never resume. + #[allow(unused_macros)] + macro_rules! freeze_forever { + ($e:expr) => {{ + #[allow(unreachable_code)] + if false { + break 'check_top (loop {}); + } + $crate::TimeCapsule::freeze_forever(&mut time_capsule, $e).await} + } + } + /// `sub_scope(some_scope)` runs the sub-scope `some_scope` to completion before continuing execution of the current scope, + /// yielding the output value of the sub-scope. + /// + /// `some_scope` is typically an expression that is itself a `scope!`. + /// + /// This macro is meant to allow you to structure your code in multiple functions. + #[allow(unused_macros)] + macro_rules! sub_scope { + ($e:expr) => {{ + #[allow(unreachable_code)] + if false { + break 'check_top (loop {}); + } + match $e { e => unsafe { $crate::scope::Scope::run(e, time_capsule).await } } + }} + } + $b + } + } { scope => unsafe { $crate::scope::new_scope(scope) } } + }; }