Skip to content

Commit 6929dec

Browse files
authored
macros: don't take ownership of futures in macros (#5087)
1 parent f809743 commit 6929dec

File tree

8 files changed

+83
-14
lines changed

8 files changed

+83
-14
lines changed

tokio/src/future/poll_fn.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,23 @@ use std::future::Future;
77
use std::pin::Pin;
88
use std::task::{Context, Poll};
99

10+
// This struct is intentionally `!Unpin` when `F` is `!Unpin`. This is to
11+
// mitigate the issue where rust puts noalias on mutable references to the
12+
// `PollFn` type if it is `Unpin`. If the closure has ownership of a future,
13+
// then this "leaks" and the future is affected by noalias too, which we don't
14+
// want.
15+
//
16+
// See this thread for more information:
17+
// <https://internals.rust-lang.org/t/surprising-soundness-trouble-around-pollfn/17484>
18+
//
19+
// The fact that `PollFn` is not `Unpin` when it shouldn't be is tested in
20+
// `tests/async_send_sync.rs`.
21+
1022
/// Future for the [`poll_fn`] function.
1123
pub struct PollFn<F> {
1224
f: F,
1325
}
1426

15-
impl<F> Unpin for PollFn<F> {}
16-
1727
/// Creates a new future wrapping around a function returning [`Poll`].
1828
pub fn poll_fn<T, F>(f: F) -> PollFn<F>
1929
where
@@ -34,7 +44,17 @@ where
3444
{
3545
type Output = T;
3646

37-
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
38-
(self.f)(cx)
47+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
48+
// Safety: We never construct a `Pin<&mut F>` anywhere, so accessing `f`
49+
// mutably in an unpinned way is sound.
50+
//
51+
// This use of unsafe cannot be replaced with the pin-project macro
52+
// because:
53+
// * If we put `#[pin]` on the field, then it gives us a `Pin<&mut F>`,
54+
// which we can't use to call the closure.
55+
// * If we don't put `#[pin]` on the field, then it makes `PollFn` be
56+
// unconditionally `Unpin`, which we also don't want.
57+
let me = unsafe { Pin::into_inner_unchecked(self) };
58+
(me.f)(cx)
3959
}
4060
}

tokio/src/macros/join.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,18 @@ macro_rules! join {
7272

7373
// Safety: nothing must be moved out of `futures`. This is to satisfy
7474
// the requirement of `Pin::new_unchecked` called below.
75+
//
76+
// We can't use the `pin!` macro for this because `futures` is a tuple
77+
// and the standard library provides no way to pin-project to the fields
78+
// of a tuple.
7579
let mut futures = ( $( maybe_done($e), )* );
7680

81+
// This assignment makes sure that the `poll_fn` closure only has a
82+
// reference to the futures, instead of taking ownership of them. This
83+
// mitigates the issue described in
84+
// <https://internals.rust-lang.org/t/surprising-soundness-trouble-around-pollfn/17484>
85+
let mut futures = &mut futures;
86+
7787
// Each time the future created by poll_fn is polled, a different future will be polled first
7888
// to ensure every future passed to join! gets a chance to make progress even if
7989
// one of the futures consumes the whole budget.
@@ -106,7 +116,7 @@ macro_rules! join {
106116
to_run -= 1;
107117

108118
// Extract the future for this branch from the tuple.
109-
let ( $($skip,)* fut, .. ) = &mut futures;
119+
let ( $($skip,)* fut, .. ) = &mut *futures;
110120

111121
// Safety: future is stored on the stack above
112122
// and never moved.

tokio/src/macros/select.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,8 +460,18 @@ macro_rules! select {
460460
let mut output = {
461461
// Safety: Nothing must be moved out of `futures`. This is to
462462
// satisfy the requirement of `Pin::new_unchecked` called below.
463+
//
464+
// We can't use the `pin!` macro for this because `futures` is a
465+
// tuple and the standard library provides no way to pin-project to
466+
// the fields of a tuple.
463467
let mut futures = ( $( $fut , )+ );
464468

469+
// This assignment makes sure that the `poll_fn` closure only has a
470+
// reference to the futures, instead of taking ownership of them.
471+
// This mitigates the issue described in
472+
// <https://internals.rust-lang.org/t/surprising-soundness-trouble-around-pollfn/17484>
473+
let mut futures = &mut futures;
474+
465475
$crate::macros::support::poll_fn(|cx| {
466476
// Track if any branch returns pending. If no branch completes
467477
// **or** returns pending, this implies that all branches are
@@ -497,7 +507,7 @@ macro_rules! select {
497507

498508
// Extract the future for this branch from the
499509
// tuple
500-
let ( $($skip,)* fut, .. ) = &mut futures;
510+
let ( $($skip,)* fut, .. ) = &mut *futures;
501511

502512
// Safety: future is stored on the stack above
503513
// and never moved.

tokio/src/macros/try_join.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,18 @@ macro_rules! try_join {
118118

119119
// Safety: nothing must be moved out of `futures`. This is to satisfy
120120
// the requirement of `Pin::new_unchecked` called below.
121+
//
122+
// We can't use the `pin!` macro for this because `futures` is a tuple
123+
// and the standard library provides no way to pin-project to the fields
124+
// of a tuple.
121125
let mut futures = ( $( maybe_done($e), )* );
122126

127+
// This assignment makes sure that the `poll_fn` closure only has a
128+
// reference to the futures, instead of taking ownership of them. This
129+
// mitigates the issue described in
130+
// <https://internals.rust-lang.org/t/surprising-soundness-trouble-around-pollfn/17484>
131+
let mut futures = &mut futures;
132+
123133
// Each time the future created by poll_fn is polled, a different future will be polled first
124134
// to ensure every future passed to join! gets a chance to make progress even if
125135
// one of the futures consumes the whole budget.
@@ -152,7 +162,7 @@ macro_rules! try_join {
152162
to_run -= 1;
153163

154164
// Extract the future for this branch from the tuple.
155-
let ( $($skip,)* fut, .. ) = &mut futures;
165+
let ( $($skip,)* fut, .. ) = &mut *futures;
156166

157167
// Safety: future is stored on the stack above
158168
// and never moved.

tokio/tests/async_send_sync.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,21 @@ macro_rules! cfg_not_wasi {
136136
}
137137
}
138138

139+
// Manualy re-implementation of `async_assert_fn` for `poll_fn`. The macro
140+
// doesn't work for this particular case because constructing the closure
141+
// is too complicated.
142+
const _: fn() = || {
143+
let pinned = std::marker::PhantomPinned;
144+
let f = tokio::macros::support::poll_fn(move |_| {
145+
// Use `pinned` to take ownership of it.
146+
let _ = &pinned;
147+
std::task::Poll::Pending::<()>
148+
});
149+
require_send(&f);
150+
require_sync(&f);
151+
AmbiguousIfUnpin::some_item(&f);
152+
};
153+
139154
cfg_not_wasi! {
140155
mod fs {
141156
use super::*;

tokio/tests/macros_join.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::sync::Arc;
44

55
#[cfg(tokio_wasm_not_wasi)]
6+
#[cfg(target_pointer_width = "64")]
67
use wasm_bindgen_test::wasm_bindgen_test as test;
78
#[cfg(tokio_wasm_not_wasi)]
89
use wasm_bindgen_test::wasm_bindgen_test as maybe_tokio_test;
@@ -64,6 +65,7 @@ async fn two_await() {
6465
}
6566

6667
#[test]
68+
#[cfg(target_pointer_width = "64")]
6769
fn join_size() {
6870
use futures::future;
6971
use std::mem;
@@ -72,14 +74,14 @@ fn join_size() {
7274
let ready = future::ready(0i32);
7375
tokio::join!(ready)
7476
};
75-
assert_eq!(mem::size_of_val(&fut), 20);
77+
assert_eq!(mem::size_of_val(&fut), 32);
7678

7779
let fut = async {
7880
let ready1 = future::ready(0i32);
7981
let ready2 = future::ready(0i32);
8082
tokio::join!(ready1, ready2)
8183
};
82-
assert_eq!(mem::size_of_val(&fut), 32);
84+
assert_eq!(mem::size_of_val(&fut), 48);
8385
}
8486

8587
async fn non_cooperative_task(permits: Arc<Semaphore>) -> usize {

tokio/tests/macros_select.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ async fn nested() {
207207
}
208208

209209
#[maybe_tokio_test]
210+
#[cfg(target_pointer_width = "64")]
210211
async fn struct_size() {
211212
use futures::future;
212213
use std::mem;
@@ -219,7 +220,7 @@ async fn struct_size() {
219220
}
220221
};
221222

222-
assert!(mem::size_of_val(&fut) <= 32);
223+
assert_eq!(mem::size_of_val(&fut), 40);
223224

224225
let fut = async {
225226
let ready1 = future::ready(0i32);
@@ -231,7 +232,7 @@ async fn struct_size() {
231232
}
232233
};
233234

234-
assert!(mem::size_of_val(&fut) <= 40);
235+
assert_eq!(mem::size_of_val(&fut), 48);
235236

236237
let fut = async {
237238
let ready1 = future::ready(0i32);
@@ -245,7 +246,7 @@ async fn struct_size() {
245246
}
246247
};
247248

248-
assert!(mem::size_of_val(&fut) <= 48);
249+
assert_eq!(mem::size_of_val(&fut), 56);
249250
}
250251

251252
#[maybe_tokio_test]

tokio/tests/macros_try_join.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ async fn err_abort_early() {
8888
}
8989

9090
#[test]
91+
#[cfg(target_pointer_width = "64")]
9192
fn join_size() {
9293
use futures::future;
9394
use std::mem;
@@ -96,14 +97,14 @@ fn join_size() {
9697
let ready = future::ready(ok(0i32));
9798
tokio::try_join!(ready)
9899
};
99-
assert_eq!(mem::size_of_val(&fut), 20);
100+
assert_eq!(mem::size_of_val(&fut), 32);
100101

101102
let fut = async {
102103
let ready1 = future::ready(ok(0i32));
103104
let ready2 = future::ready(ok(0i32));
104105
tokio::try_join!(ready1, ready2)
105106
};
106-
assert_eq!(mem::size_of_val(&fut), 32);
107+
assert_eq!(mem::size_of_val(&fut), 48);
107108
}
108109

109110
fn ok<T>(val: T) -> Result<T, ()> {

0 commit comments

Comments
 (0)