Skip to content

Commit 46f49f0

Browse files
committed
Future::boxed and Stream::boxed should prevent double boxing
Fixes #511
1 parent 9f03c50 commit 46f49f0

File tree

7 files changed

+142
-2
lines changed

7 files changed

+142
-2
lines changed

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ appveyor = { repository = "alexcrichton/futures-rs" }
2020

2121
[dependencies]
2222

23+
[build-dependencies]
24+
regex = "0.2"
25+
2326
[features]
2427
use_std = []
2528
with-deprecated = []

build.rs

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
extern crate regex;
2+
3+
use std::env;
4+
use std::str;
5+
use std::process;
6+
7+
fn main() {
8+
let rustc = env::var("RUSTC").expect("RUSTC variable is unset");
9+
10+
let command = process::Command::new(rustc)
11+
.args(&["--version"])
12+
.stdin(process::Stdio::null())
13+
.stderr(process::Stdio::inherit())
14+
.stdout(process::Stdio::piped())
15+
.spawn()
16+
.expect("spawn rustc");
17+
18+
let wait = command.wait_with_output().expect("wait for rust");
19+
if !wait.status.success() {
20+
panic!("rustc --version exited with non-zero code");
21+
}
22+
23+
let stdout = str::from_utf8(&wait.stdout).expect("stdout is not UTF-8");
24+
25+
let re = regex::Regex::new(r"^rustc (\d+)\.(\d+)\.(\d+)").expect("compile regex");
26+
let captures = re.captures(stdout)
27+
.expect(&format!("regex cannot match `rustc --version` output: {:?}", stdout));
28+
29+
let major: u32 = captures.get(1).expect("major").as_str().parse().unwrap();
30+
let minor: u32 = captures.get(2).expect("minor").as_str().parse().unwrap();
31+
let _patch: u32 = captures.get(3).expect("patch").as_str().parse().unwrap();
32+
33+
if major > 1 || minor >= 18 {
34+
println!("cargo:rustc-cfg=rust_at_least_1_18");
35+
}
36+
}

src/boxed.rs

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#[inline(always)]
2+
#[cfg(rust_at_least_1_18)]
3+
pub fn transmute_or_convert<A, B, F>(a: A, put_in_box: F)
4+
-> B where A: 'static, B: 'static, F: FnOnce(A) -> B
5+
{
6+
use std::any::TypeId;
7+
use std::mem;
8+
use std::ptr;
9+
10+
if TypeId::of::<A>() == TypeId::of::<B>() {
11+
// Prevent double boxing
12+
assert!(mem::size_of::<A>() == mem::size_of::<B>());
13+
unsafe {
14+
let mut r: B = mem::uninitialized();
15+
ptr::copy_nonoverlapping(
16+
&a as *const A,
17+
&mut r as *mut B as *mut u8 as *mut A,
18+
1);
19+
mem::forget(a);
20+
r
21+
}
22+
} else {
23+
put_in_box(a)
24+
}
25+
}
26+
27+
#[inline(always)]
28+
#[cfg(not(rust_at_least_1_18))]
29+
pub fn transmute_or_convert<A, B, F>(a: A, put_in_box: F)
30+
-> B where A: 'static, B: 'static, F: FnOnce(A) -> B
31+
{
32+
put_in_box(a)
33+
}

src/future/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ pub trait Future {
308308
fn boxed(self) -> BoxFuture<Self::Item, Self::Error>
309309
where Self: Sized + Send + 'static
310310
{
311-
::std::boxed::Box::new(self)
311+
::boxed::transmute_or_convert::<Self, BoxFuture<Self::Item, Self::Error>, _>(
312+
self, |f| ::std::boxed::Box::new(f))
312313
}
313314

314315
/// Map this future's result to a different type, returning a new future of

src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ pub mod sync;
207207
#[cfg(feature = "use_std")]
208208
pub mod unsync;
209209

210+
#[cfg(feature = "use_std")]
211+
#[doc(hidden)]
212+
pub mod boxed;
213+
210214

211215
if_std! {
212216
#[doc(hidden)]

src/stream/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,8 @@ pub trait Stream {
250250
fn boxed(self) -> BoxStream<Self::Item, Self::Error>
251251
where Self: Sized + Send + 'static,
252252
{
253-
::std::boxed::Box::new(self)
253+
::boxed::transmute_or_convert::<Self, BoxStream<Self::Item, Self::Error>, _>(
254+
self, |s| ::std::boxed::Box::new(s))
254255
}
255256

256257
/// Converts this stream into a `Future`.

tests/boxed.rs

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#![cfg(rust_at_least_1_18)]
2+
3+
extern crate futures;
4+
5+
use futures::Async;
6+
use futures::Poll;
7+
use futures::future::Future;
8+
use futures::stream::Stream;
9+
10+
11+
#[test]
12+
fn future_boxed_prevents_double_boxing() {
13+
struct MyFuture {
14+
r: &'static str,
15+
}
16+
17+
impl Future for MyFuture {
18+
type Item = &'static str;
19+
type Error = ();
20+
21+
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
22+
Ok(Async::Ready(self.r))
23+
}
24+
}
25+
26+
let f = MyFuture { r: "I'm ready" };
27+
let f = f.boxed();
28+
let ptr = f.as_ref() as *const Future<Item=_, Error=_>;
29+
let f = f.boxed();
30+
let f = f.boxed();
31+
let mut f = f.boxed();
32+
assert_eq!(f.as_ref() as *const Future<Item=_, Error=_>, ptr);
33+
assert_eq!(Ok(Async::Ready("I'm ready")), f.poll());
34+
}
35+
36+
#[test]
37+
fn stream_boxed_prevents_double_boxing() {
38+
struct MyStream {
39+
i: u32,
40+
}
41+
42+
impl Stream for MyStream {
43+
type Item = u32;
44+
type Error = ();
45+
46+
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
47+
self.i += 1;
48+
Ok(Async::Ready(Some(self.i)))
49+
}
50+
}
51+
52+
let s = MyStream { i: 0 };
53+
let s = s.boxed();
54+
let ptr = s.as_ref() as *const Stream<Item=_, Error=_>;
55+
let s = s.boxed();
56+
let s = s.boxed();
57+
let mut s = s.boxed();
58+
assert_eq!(s.as_ref() as *const Stream<Item=_, Error=_>, ptr);
59+
assert_eq!(Ok(Async::Ready(Some(1))), s.poll());
60+
assert_eq!(Ok(Async::Ready(Some(2))), s.poll());
61+
assert_eq!(Ok(Async::Ready(Some(3))), s.poll());
62+
}

0 commit comments

Comments
 (0)