Skip to content

Commit

Permalink
test(maitake): Miri test for rust-lang/miri#3780
Browse files Browse the repository at this point in the history
This reproduces a potential UB where a `task::Cell` gets niche-optimized
in the `Joined` case. This is based on the test added to Tokio in
tokio-rs/tokio#6744
  • Loading branch information
hawkw committed Aug 2, 2024
1 parent d9bc76b commit 2f831df
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 15 deletions.
33 changes: 18 additions & 15 deletions maitake/src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -734,21 +734,7 @@ where
#[cfg(any(feature = "tracing-01", feature = "tracing-02", test))]
let _span = self.span().enter();

self.inner.with_mut(|cell| {
let cell = unsafe { &mut *cell };
let poll = match cell {
Cell::Pending(future) => unsafe { Pin::new_unchecked(future).poll(&mut cx) },
_ => unreachable!("tried to poll a completed future!"),
};

match poll {
Poll::Ready(ready) => {
*cell = Cell::Ready(ready);
Poll::Ready(())
}
Poll::Pending => Poll::Pending,
}
})
self.inner.with_mut(|cell| unsafe { (*cell).poll(&mut cx) })
}

/// Wakes the task's [`JoinHandle`], if it has one.
Expand Down Expand Up @@ -1427,6 +1413,23 @@ impl<F: Future> fmt::Debug for Cell<F> {
}
}

impl<F: Future> Cell<F> {
fn poll(&mut self, cx: &mut Context<'_>) -> Poll<()> {
let poll = match self {
Cell::Pending(future) => unsafe { Pin::new_unchecked(future).poll(cx) },
_ => unreachable!("tried to poll a completed future!"),
};

match poll {
Poll::Ready(ready) => {
*self = Cell::Ready(ready);
Poll::Ready(())
}
Poll::Pending => Poll::Pending,
}
}
}

// === impl Vtable ===

impl fmt::Debug for Vtable {
Expand Down
50 changes: 50 additions & 0 deletions maitake/src/task/tests/alloc_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,53 @@ fn drop_join_handle() {

assert!(COMPLETED.load(Ordering::Relaxed))
}

// Test for potential UB in `Cell::poll` due to niche optimization.
// See https://github.com/rust-lang/miri/issues/3780 for details.
//
// This is based on the test for analogous types in Tokio added in
// https://github.com/tokio-rs/tokio/pull/6744
#[test]
fn cell_miri() {
use super::Cell;
use alloc::{string::String, sync::Arc, task::Wake};
use core::{
future::Future,
pin::Pin,
task::{Context, Poll},
};

struct DummyWaker;

impl Wake for DummyWaker {
fn wake(self: Arc<Self>) {}
}

struct ThingAdder<'a> {
thing: &'a mut String,
}

impl Future for ThingAdder<'_> {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
unsafe {
*self.get_unchecked_mut().thing += ", world";
}
Poll::Pending
}
}

let mut thing = "hello".to_owned();

// The async block is necessary to trigger the miri failure.
#[allow(clippy::redundant_async_block)]
let fut = async move { ThingAdder { thing: &mut thing }.await };

let mut fut = Cell::Pending(fut);

let waker = Arc::new(DummyWaker).into();
let mut ctx = Context::from_waker(&waker);
assert_eq!(fut.poll(&mut ctx), Poll::Pending);
assert_eq!(fut.poll(&mut ctx), Poll::Pending);
}

0 comments on commit 2f831df

Please sign in to comment.