Skip to content
This repository was archived by the owner on Aug 16, 2021. It is now read-only.

Commit 345cb5a

Browse files
authored
Fix a memory leak in downcast (#262)
1 parent 1657af3 commit 345cb5a

File tree

4 files changed

+67
-3
lines changed

4 files changed

+67
-3
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ license = "MIT OR Apache-2.0"
77
name = "failure"
88
repository = "https://github.com/rust-lang-nursery/failure"
99
version = "0.1.2"
10+
build = "build.rs"
1011

1112
[dependencies.failure_derive]
1213
optional = true

build.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use std::env;
2+
use std::process::Command;
3+
use std::str;
4+
use std::str::FromStr;
5+
6+
fn main() {
7+
if rustc_has_global_alloc() {
8+
println!("cargo:rustc-cfg=has_global_alloc");
9+
}
10+
}
11+
12+
fn rustc_has_global_alloc() -> bool {
13+
let rustc = match env::var_os("RUSTC") {
14+
Some(rustc) => rustc,
15+
None => return false,
16+
};
17+
18+
let output = match Command::new(rustc).arg("--version").output() {
19+
Ok(output) => output,
20+
Err(_) => return false,
21+
};
22+
23+
let version = match str::from_utf8(&output.stdout) {
24+
Ok(version) => version,
25+
Err(_) => return false,
26+
};
27+
28+
let mut pieces = version.split('.');
29+
if pieces.next() != Some("rustc 1") {
30+
return true;
31+
}
32+
33+
let next = match pieces.next() {
34+
Some(next) => next,
35+
None => return false,
36+
};
37+
38+
u32::from_str(next).unwrap_or(0) >= 28
39+
}

src/error/error_impl.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use core::mem;
21
use core::ptr;
32

43
use Fail;
@@ -49,8 +48,23 @@ impl ErrorImpl {
4948
});
5049
match ret {
5150
Some(ret) => {
52-
// forget self (backtrace is dropped, failure is moved
53-
mem::forget(self);
51+
// deallocate the box without dropping the inner parts
52+
#[cfg(has_global_alloc)] {
53+
use std::alloc::{dealloc, Layout};
54+
unsafe {
55+
let layout = Layout::for_value(&*self.inner);
56+
let ptr = Box::into_raw(self.inner);
57+
dealloc(ptr as *mut u8, layout);
58+
}
59+
}
60+
61+
// slightly leaky versions of the above thing which makes the box
62+
// itself leak. There is no good way around this as far as I know.
63+
#[cfg(not(has_global_alloc))] {
64+
use core::mem;
65+
mem::forget(self);
66+
}
67+
5468
Ok(ret)
5569
}
5670
_ => Err(self)

src/error/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ impl Error {
122122
/// failure is of the type `T`. For this reason it returns a `Result` - in
123123
/// the case that the underlying error is of a different type, the
124124
/// original `Error` is returned.
125+
///
126+
/// Note that this method leaks on Rust versions < 1.28.0.
127+
#[cfg_attr(not(has_global_alloc), deprecated(note = "this method leaks on Rust versions < 1.28"))]
125128
pub fn downcast<T: Fail>(self) -> Result<T, Error> {
126129
self.imp.downcast().map_err(|imp| Error { imp })
127130
}
@@ -225,4 +228,11 @@ mod test {
225228
drop(error);
226229
assert!(true);
227230
}
231+
232+
#[test]
233+
fn test_downcast() {
234+
let error: Error = io::Error::new(io::ErrorKind::NotFound, "test").into();
235+
let real_io_error = error.downcast_ref::<io::Error>().unwrap();
236+
assert_eq!(real_io_error.to_string(), "test");
237+
}
228238
}

0 commit comments

Comments
 (0)