genuinely the most cursed code i've written to date

Changed files
+240 -2
futures-core
+144
futures-core/src/compat.rs
··· 1 + //! Any interaction between the real Future ecosystem and ScopedFuture 2 + //! ecosystem is strictly unsound 3 + //! 4 + //! ScopedFutures cannot poll Futures because they can't guarantee they 5 + //! will outlive *const () ptrs they supply to the futures, leading to 6 + //! dangling pointers if the futures register the waker with something that 7 + //! assumes assumes it is valid for 'static and then the ScopedFuture goes 8 + //! out of scope 9 + //! 10 + //! Futures cannot poll ScopedFutures because they cannot guarantee their 11 + //! Waker will be valid for 'scope (due to lack of real borrowing), leading to 12 + //! unsoundness if a ScopedFuture internally registers the waker with something 13 + //! that expects it to live for 'scope, and then the ScopedFutureWrapper is 14 + //! dropped 15 + 16 + use crate::{ScopedFuture, Wake}; 17 + use std::{ 18 + cell::UnsafeCell, 19 + marker::PhantomData, 20 + mem, 21 + pin::Pin, 22 + task::{Context, Poll, Waker}, 23 + }; 24 + 25 + /// RawWaker: fat ptr (*const (), &'static RawWakerVTable) 26 + /// &'scope dyn Wake fat ptr: (&'scope (), &'scope WakeVTable) 27 + /// 28 + /// can transmute between them, but the waker will be completely invalid! 29 + 30 + /// wraps an internal ScopedFuture, implements Future 31 + pub struct ScopedFutureWrapper<'scope, F: ScopedFuture<'scope> + 'scope> { 32 + inner: UnsafeCell<F>, 33 + marker: PhantomData<&'scope ()>, 34 + } 35 + 36 + impl<'scope, F: ScopedFuture<'scope> + 'scope> Future 37 + for ScopedFutureWrapper<'scope, F> 38 + { 39 + type Output = F::Output; 40 + 41 + fn poll( 42 + self: std::pin::Pin<&mut Self>, 43 + cx: &mut std::task::Context<'_>, 44 + ) -> Poll<Self::Output> { 45 + // # Safety 46 + // 47 + // Transmutes `Waker` into `&'scope dyn Wake`. 48 + // This is possible because Waker (internally just RawWaker) contains 49 + // (*const (), &'static RawWakerVTable), and the fat ptr `&dyn Wake` 50 + // internally is (*const (), *const WakeVTable). 51 + // 52 + // For this to be sound, the input waker from `cx` must be an invalid 53 + // waker (using the waker as intended would be UB) that has the form of 54 + // a `&dyn Wake` fat ptr, as generated in `UnscopedFutureWrapper`. 55 + // 56 + // This is only sound because it is paired with the transmute in 57 + // `UnscopedFutureWrapper` 58 + // 59 + // This conversion is necessary to piggyback off rustc's expansion 60 + // of `async` blocks into state machines implementing `Future`. 61 + // 62 + // The unpinning is safe because inner (a `ScopedFuture`) cannot be 63 + // moved after a self reference is established on its first `poll`. 64 + // 65 + // The use of the `UnsafeCell` is sound and necessary to get around 66 + // the afformentioned immutable self reference (since `Future::poll`) 67 + // requires a `&mut Self`. It is sound because we never take a 68 + // `&mut self.inner`. 69 + unsafe { 70 + let this = self.get_unchecked_mut(); 71 + let wake: &'scope dyn Wake = mem::transmute::< 72 + Waker, 73 + &'scope dyn Wake, 74 + >(cx.waker().to_owned()); 75 + (&*this.inner.get()).poll(wake) 76 + } 77 + } 78 + } 79 + 80 + impl<'scope, F: ScopedFuture<'scope> + 'scope> ScopedFutureWrapper<'scope, F> { 81 + pub unsafe fn from_scoped(f: F) -> Self { 82 + Self { 83 + inner: f.into(), 84 + marker: PhantomData, 85 + } 86 + } 87 + } 88 + 89 + /// wraps an internal Future, implements ScopedFuture 90 + /// this is fundamentally unsafe and relies on the future not registering its waker 91 + /// in any reactor that lives beyond this wrapper, otherwise there will be a dangling pointer 92 + /// 93 + /// it is safe to use only with the #[async_scoped] macro, which guarantees that, internally, every futures is a ScopedFutureWrapper 94 + pub struct UnscopedFutureWrapper<'scope, F: Future> { 95 + inner: UnsafeCell<F>, 96 + marker: PhantomData<&'scope ()>, 97 + } 98 + 99 + impl<'scope, F: Future> ScopedFuture<'scope> 100 + for UnscopedFutureWrapper<'scope, F> 101 + { 102 + type Output = F::Output; 103 + 104 + fn poll( 105 + self: &'scope Self, 106 + wake: &'scope dyn Wake<'scope>, 107 + ) -> Poll<Self::Output> { 108 + // # Safety 109 + // 110 + // Transmutes `&'scope dyn Wake` into a Waker. 111 + // This is possible because Waker (internally just RawWaker) contains 112 + // (*const (), &'static RawWakerVTable), and the fat ptr `&dyn Wake` 113 + // internally is (*const (), *const WakeVTable). 114 + // 115 + // Using the resulting Waker is UB, which is why UnscopedFutureWrapper 116 + // can only be used in pair with ScopedFutureWrapper, which transmutes 117 + // the invalid `Waker` back to a `&dyn Wake`. 118 + // 119 + // This conversion is necessary to piggyback off rustc's expansion 120 + // of `async` blocks into state machines implementing `Future`. 121 + let waker: Waker = 122 + unsafe { mem::transmute::<&'scope dyn Wake<'scope>, Waker>(wake) }; 123 + // # Safety 124 + // 125 + // Once any ScopedFuture is first polled and stores a waker, it becomes 126 + // immutable and immovable because it has an immutable self reference. 127 + // 128 + // The Pin::new_unchecked is necessary to be compatible with 129 + // `task::Future` 130 + let pinned_future = 131 + unsafe { Pin::new_unchecked(&mut *self.inner.get()) }; 132 + 133 + pinned_future.poll(&mut Context::from_waker(&waker)) 134 + } 135 + } 136 + 137 + impl<'scope, F: Future> UnscopedFutureWrapper<'scope, F> { 138 + pub unsafe fn from_future(f: F) -> Self { 139 + Self { 140 + inner: f.into(), 141 + marker: PhantomData, 142 + } 143 + } 144 + }
+10 -2
futures-core/src/lib.rs
··· 1 - use std::task::{Poll, RawWaker}; 1 + use std::{ 2 + cell::UnsafeCell, 3 + marker::PhantomData, 4 + mem, 5 + pin::Pin, 6 + task::{Context, Poll, RawWaker, Waker}, 7 + }; 8 + 9 + pub mod compat; 2 10 3 11 /// A task that can be woken. 4 12 /// ··· 11 19 /// ScopedFuture represents a unit of asynchronous computation that must be 12 20 /// polled by an external actor. 13 21 /// 14 - /// Implementations access a context (`cx: &'scope mut dyn Wake`) to signal 22 + /// Implementations access a context (`cx: &'scope dyn Wake`) to signal 15 23 /// they are ready to resume execution. 16 24 /// 17 25 /// A notable difference between `bcsc::ScopedFuture` and `core::task::Future`
+86
futures-core/src/task.rs
··· 1 + use crate::wake::ScopedWaker; 2 + 3 + /// A task that can be woken. 4 + /// 5 + /// This acts as a handle for a reactor to indicate when a `ScopedFuture` is 6 + /// once again ready to be polled. 7 + pub trait Wake<'scope> { 8 + fn wake(&self); 9 + } 10 + 11 + impl<'scope> From<&'scope dyn Wake<'scope>> for ScopedWaker<'scope> { 12 + fn from(value: &'scope dyn Wake) -> Self {} 13 + } 14 + 15 + fn raw_waker<'scope, W: Wake<'scope> + Send + Sync>( 16 + waker: &'scope dyn Wake, 17 + ) -> RawWaker { 18 + // Increment the reference count of the arc to clone it. 19 + 20 + // 21 + 22 + // The #[inline(always)] is to ensure that raw_waker and clone_waker are 23 + 24 + // always generated in the same code generation unit as one another, and 25 + 26 + // therefore that the structurally identical const-promoted RawWakerVTable 27 + 28 + // within both functions is deduplicated at LLVM IR code generation time. 29 + 30 + // This allows optimizing Waker::will_wake to a single pointer comparison of 31 + 32 + // the vtable pointers, rather than comparing all four function pointers 33 + 34 + // within the vtables. 35 + 36 + #[inline(always)] 37 + 38 + unsafe fn clone_waker<W: Wake + Send + Sync + 'static>( 39 + waker: *const (), 40 + ) -> RawWaker { 41 + unsafe { Arc::increment_strong_count(waker as *const W) }; 42 + 43 + RawWaker::new( 44 + waker, 45 + &RawWakerVTable::new( 46 + clone_waker::<W>, 47 + wake::<W>, 48 + wake_by_ref::<W>, 49 + drop_waker::<W>, 50 + ), 51 + ) 52 + } 53 + 54 + // Wake by value, moving the Arc into the Wake::wake function 55 + 56 + unsafe fn wake<W: Wake + Send + Sync + 'static>(waker: *const ()) { 57 + let waker = unsafe { Arc::from_raw(waker as *const W) }; 58 + 59 + <W as Wake>::wake(waker); 60 + } 61 + 62 + // Wake by reference, wrap the waker in ManuallyDrop to avoid dropping it 63 + 64 + unsafe fn wake_by_ref<W: Wake + Send + Sync + 'static>(waker: *const ()) { 65 + let waker = 66 + unsafe { ManuallyDrop::new(Arc::from_raw(waker as *const W)) }; 67 + 68 + <W as Wake>::wake_by_ref(&waker); 69 + } 70 + 71 + // Decrement the reference count of the Arc on drop 72 + 73 + unsafe fn drop_waker<W: Wake + Send + Sync + 'static>(waker: *const ()) { 74 + unsafe { Arc::decrement_strong_count(waker as *const W) }; 75 + } 76 + 77 + RawWaker::new( 78 + Arc::into_raw(waker) as *const (), 79 + &RawWakerVTable::new( 80 + clone_waker::<W>, 81 + wake::<W>, 82 + wake_by_ref::<W>, 83 + drop_waker::<W>, 84 + ), 85 + ) 86 + }