Skip to content

Commit ade71b7

Browse files
committed
address soundness footgun in poll_fn
1 parent cb83922 commit ade71b7

File tree

3 files changed

+63
-8
lines changed

3 files changed

+63
-8
lines changed

actix-utils/src/future/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Asynchronous values.
1+
//! Helpers for constructing futures.
22
33
mod either;
44
mod poll_fn;

actix-utils/src/future/poll_fn.rs

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,31 @@ use core::{
88
};
99

1010
/// Creates a future driven by the provided function that receives a task context.
11+
///
12+
/// # Examples
13+
/// ```
14+
/// # use std::task::Poll;
15+
/// # use actix_utils::future::poll_fn;
16+
/// # async fn test_poll_fn() {
17+
/// let res = poll_fn(|_| Poll::Ready(42)).await;
18+
/// assert_eq!(res, 42);
19+
///
20+
/// let mut i = 5;
21+
/// let res = poll_fn(|cx| {
22+
/// i -= 1;
23+
///
24+
/// if i > 0 {
25+
/// cx.waker().wake_by_ref();
26+
/// Poll::Pending
27+
/// } else {
28+
/// Poll::Ready(42)
29+
/// }
30+
/// })
31+
/// .await;
32+
/// assert_eq!(res, 42);
33+
/// # }
34+
/// # actix_rt::Runtime::new().unwrap().block_on(test_poll_fn());
35+
/// ```
1136
#[inline]
1237
pub fn poll_fn<F, T>(f: F) -> PollFn<F>
1338
where
@@ -16,13 +41,11 @@ where
1641
PollFn { f }
1742
}
1843

19-
/// A Future driven by the inner function.
44+
/// Future for the [`poll_fn`] function.
2045
pub struct PollFn<F> {
2146
f: F,
2247
}
2348

24-
impl<F> Unpin for PollFn<F> {}
25-
2649
impl<F> fmt::Debug for PollFn<F> {
2750
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2851
f.debug_struct("PollFn").finish()
@@ -36,15 +59,22 @@ where
3659
type Output = T;
3760

3861
#[inline]
39-
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
40-
(self.f)(cx)
62+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
63+
// SAFETY: we are not moving out of the pinned field
64+
// see https://github.com/rust-lang/rust/pull/102737
65+
(unsafe { &mut self.get_unchecked_mut().f })(cx)
4166
}
4267
}
4368

4469
#[cfg(test)]
4570
mod tests {
71+
use std::marker::PhantomPinned;
72+
4673
use super::*;
4774

75+
static_assertions::assert_impl_all!(PollFn<()>: Unpin);
76+
static_assertions::assert_not_impl_all!(PollFn<PhantomPinned>: Unpin);
77+
4878
#[actix_rt::test]
4979
async fn test_poll_fn() {
5080
let res = poll_fn(|_| Poll::Ready(42)).await;
@@ -64,4 +94,29 @@ mod tests {
6494
.await;
6595
assert_eq!(res, 42);
6696
}
97+
98+
// following soundness tests taken from https://github.com/tokio-rs/tokio/pull/5087
99+
100+
#[allow(dead_code)]
101+
fn require_send<T: Send>(_t: &T) {}
102+
#[allow(dead_code)]
103+
fn require_sync<T: Sync>(_t: &T) {}
104+
105+
trait AmbiguousIfUnpin<A> {
106+
fn some_item(&self) {}
107+
}
108+
impl<T: ?Sized> AmbiguousIfUnpin<()> for T {}
109+
impl<T: ?Sized + Unpin> AmbiguousIfUnpin<[u8; 0]> for T {}
110+
111+
const _: fn() = || {
112+
let pinned = std::marker::PhantomPinned;
113+
let f = poll_fn(move |_| {
114+
// Use `pinned` to take ownership of it.
115+
let _ = &pinned;
116+
std::task::Poll::Pending::<()>
117+
});
118+
require_send(&f);
119+
require_sync(&f);
120+
AmbiguousIfUnpin::some_item(&f);
121+
};
67122
}

actix-utils/src/future/ready.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
//! When MSRV is 1.48, replace with `core::future::Ready` and `core::future::ready()`.
1+
//! When `core::future::Ready` has a `into_inner()` method, this can be deprecated.
22
33
use core::{
44
future::Future,
55
pin::Pin,
66
task::{Context, Poll},
77
};
88

9-
/// Future for the [`ready`](ready()) function.
9+
/// Future for the [`ready`] function.
1010
///
1111
/// Panic will occur if polled more than once.
1212
///

0 commit comments

Comments
 (0)