From 46f49f092e65e1e6618b725080afe7a677f4c6cd Mon Sep 17 00:00:00 2001 From: Stepan Koltsov Date: Sun, 11 Jun 2017 02:32:19 +0300 Subject: [PATCH] Future::boxed and Stream::boxed should prevent double boxing Fixes #511 --- Cargo.toml | 3 +++ build.rs | 36 +++++++++++++++++++++++++++ src/boxed.rs | 33 +++++++++++++++++++++++++ src/future/mod.rs | 3 ++- src/lib.rs | 4 +++ src/stream/mod.rs | 3 ++- tests/boxed.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 build.rs create mode 100644 src/boxed.rs create mode 100644 tests/boxed.rs diff --git a/Cargo.toml b/Cargo.toml index ae4bed2cda..69220ee88b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,9 @@ appveyor = { repository = "alexcrichton/futures-rs" } [dependencies] +[build-dependencies] +regex = "0.2" + [features] use_std = [] with-deprecated = [] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000000..0cb6c74657 --- /dev/null +++ b/build.rs @@ -0,0 +1,36 @@ +extern crate regex; + +use std::env; +use std::str; +use std::process; + +fn main() { + let rustc = env::var("RUSTC").expect("RUSTC variable is unset"); + + let command = process::Command::new(rustc) + .args(&["--version"]) + .stdin(process::Stdio::null()) + .stderr(process::Stdio::inherit()) + .stdout(process::Stdio::piped()) + .spawn() + .expect("spawn rustc"); + + let wait = command.wait_with_output().expect("wait for rust"); + if !wait.status.success() { + panic!("rustc --version exited with non-zero code"); + } + + let stdout = str::from_utf8(&wait.stdout).expect("stdout is not UTF-8"); + + let re = regex::Regex::new(r"^rustc (\d+)\.(\d+)\.(\d+)").expect("compile regex"); + let captures = re.captures(stdout) + .expect(&format!("regex cannot match `rustc --version` output: {:?}", stdout)); + + let major: u32 = captures.get(1).expect("major").as_str().parse().unwrap(); + let minor: u32 = captures.get(2).expect("minor").as_str().parse().unwrap(); + let _patch: u32 = captures.get(3).expect("patch").as_str().parse().unwrap(); + + if major > 1 || minor >= 18 { + println!("cargo:rustc-cfg=rust_at_least_1_18"); + } +} diff --git a/src/boxed.rs b/src/boxed.rs new file mode 100644 index 0000000000..605bed8bcf --- /dev/null +++ b/src/boxed.rs @@ -0,0 +1,33 @@ +#[inline(always)] +#[cfg(rust_at_least_1_18)] +pub fn transmute_or_convert(a: A, put_in_box: F) + -> B where A: 'static, B: 'static, F: FnOnce(A) -> B +{ + use std::any::TypeId; + use std::mem; + use std::ptr; + + if TypeId::of::() == TypeId::of::() { + // Prevent double boxing + assert!(mem::size_of::() == mem::size_of::()); + unsafe { + let mut r: B = mem::uninitialized(); + ptr::copy_nonoverlapping( + &a as *const A, + &mut r as *mut B as *mut u8 as *mut A, + 1); + mem::forget(a); + r + } + } else { + put_in_box(a) + } +} + +#[inline(always)] +#[cfg(not(rust_at_least_1_18))] +pub fn transmute_or_convert(a: A, put_in_box: F) + -> B where A: 'static, B: 'static, F: FnOnce(A) -> B +{ + put_in_box(a) +} diff --git a/src/future/mod.rs b/src/future/mod.rs index 7e278bac88..4e66efaf7e 100644 --- a/src/future/mod.rs +++ b/src/future/mod.rs @@ -308,7 +308,8 @@ pub trait Future { fn boxed(self) -> BoxFuture where Self: Sized + Send + 'static { - ::std::boxed::Box::new(self) + ::boxed::transmute_or_convert::, _>( + self, |f| ::std::boxed::Box::new(f)) } /// Map this future's result to a different type, returning a new future of diff --git a/src/lib.rs b/src/lib.rs index ed8c93f96d..fa6f11d7ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -207,6 +207,10 @@ pub mod sync; #[cfg(feature = "use_std")] pub mod unsync; +#[cfg(feature = "use_std")] +#[doc(hidden)] +pub mod boxed; + if_std! { #[doc(hidden)] diff --git a/src/stream/mod.rs b/src/stream/mod.rs index 8a7e466ca8..2c9769ece2 100755 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -250,7 +250,8 @@ pub trait Stream { fn boxed(self) -> BoxStream where Self: Sized + Send + 'static, { - ::std::boxed::Box::new(self) + ::boxed::transmute_or_convert::, _>( + self, |s| ::std::boxed::Box::new(s)) } /// Converts this stream into a `Future`. diff --git a/tests/boxed.rs b/tests/boxed.rs new file mode 100644 index 0000000000..e060817e94 --- /dev/null +++ b/tests/boxed.rs @@ -0,0 +1,62 @@ +#![cfg(rust_at_least_1_18)] + +extern crate futures; + +use futures::Async; +use futures::Poll; +use futures::future::Future; +use futures::stream::Stream; + + +#[test] +fn future_boxed_prevents_double_boxing() { + struct MyFuture { + r: &'static str, + } + + impl Future for MyFuture { + type Item = &'static str; + type Error = (); + + fn poll(&mut self) -> Poll { + Ok(Async::Ready(self.r)) + } + } + + let f = MyFuture { r: "I'm ready" }; + let f = f.boxed(); + let ptr = f.as_ref() as *const Future; + let f = f.boxed(); + let f = f.boxed(); + let mut f = f.boxed(); + assert_eq!(f.as_ref() as *const Future, ptr); + assert_eq!(Ok(Async::Ready("I'm ready")), f.poll()); +} + +#[test] +fn stream_boxed_prevents_double_boxing() { + struct MyStream { + i: u32, + } + + impl Stream for MyStream { + type Item = u32; + type Error = (); + + fn poll(&mut self) -> Poll, Self::Error> { + self.i += 1; + Ok(Async::Ready(Some(self.i))) + } + } + + let s = MyStream { i: 0 }; + let s = s.boxed(); + let ptr = s.as_ref() as *const Stream; + let s = s.boxed(); + let s = s.boxed(); + let mut s = s.boxed(); + assert_eq!(s.as_ref() as *const Stream, ptr); + assert_eq!(Ok(Async::Ready(Some(1))), s.poll()); + assert_eq!(Ok(Async::Ready(Some(2))), s.poll()); + assert_eq!(Ok(Async::Ready(Some(3))), s.poll()); +}