Skip to content

Commit 112dc14

Browse files
committed
Don't neglect to forget self in close
Otherwise it leads to a double-free, once in `close` and another time in `drop`. Curiously this was only a bug on Windows, and Unix code was fine. Not sure how this happened.
1 parent 4ad1af8 commit 112dc14

File tree

4 files changed

+43
-2
lines changed

4 files changed

+43
-2
lines changed

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ impl Library {
199199
/// You only need to call this if you are interested in handling any errors that may arise when
200200
/// library is unloaded. Otherwise the implementation of `Drop` for `Library` will close the
201201
/// library and ignore the errors were they arise.
202+
///
203+
/// The underlying data structures may still get leaked if an error does occur.
202204
pub fn close(self) -> Result<(), Error> {
203205
self.0.close()
204206
}

src/os/unix/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,8 @@ impl Library {
301301
///
302302
/// You only need to call this if you are interested in handling any errors that may arise when
303303
/// library is unloaded. Otherwise this will be done when `Library` is dropped.
304+
///
305+
/// The underlying data structures may still get leaked if an error does occur.
304306
pub fn close(self) -> Result<(), crate::Error> {
305307
let result = with_dlerror(|desc| crate::Error::DlClose { desc }, || {
306308
if unsafe { dlclose(self.handle) } == 0 {
@@ -309,6 +311,9 @@ impl Library {
309311
None
310312
}
311313
}).map_err(|e| e.unwrap_or(crate::Error::DlCloseUnknown));
314+
// While the library is not free'd yet in case of an error, there is no reason to try
315+
// dropping it again, because all that will do is try calling `FreeLibrary` again. only
316+
// this time it would ignore the return result, which we already seen failing…
312317
std::mem::forget(self);
313318
result
314319
}

src/os/windows/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,14 +217,24 @@ impl Library {
217217
}
218218

219219
/// Unload the library.
220+
///
221+
/// You only need to call this if you are interested in handling any errors that may arise when
222+
/// library is unloaded. Otherwise this will be done when `Library` is dropped.
223+
///
224+
/// The underlying data structures may still get leaked if an error does occur.
220225
pub fn close(self) -> Result<(), crate::Error> {
221-
with_get_last_error(|source| crate::Error::FreeLibrary { source }, || {
226+
let result = with_get_last_error(|source| crate::Error::FreeLibrary { source }, || {
222227
if unsafe { libloaderapi::FreeLibrary(self.0) == 0 } {
223228
None
224229
} else {
225230
Some(())
226231
}
227-
}).map_err(|e| e.unwrap_or(crate::Error::FreeLibraryUnknown))
232+
}).map_err(|e| e.unwrap_or(crate::Error::FreeLibraryUnknown));
233+
// While the library is not free'd yet in case of an error, there is no reason to try
234+
// dropping it again, because all that will do is try calling `FreeLibrary` again. only
235+
// this time it would ignore the return result, which we already seen failing…
236+
std::mem::forget(self);
237+
result
228238
}
229239
}
230240

tests/functions.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,30 @@ fn test_static_ptr() {
145145
}
146146
}
147147

148+
#[test]
149+
// Something about i686-pc-windows-gnu, makes dll initialization code call abort when it is loaded
150+
// and unloaded many times. So far it seems like an issue with mingw, not libloading, so ignoring
151+
// the target. Especially since it is very unlikely to be fixed given the state of support its
152+
// support.
153+
#[cfg(not(all(target_arch="x86", target_os="windows", target_env="gnu")))]
154+
fn manual_close_many_times() {
155+
make_helpers();
156+
let join_handles: Vec<_> = (0..16).map(|_| {
157+
std::thread::spawn(|| unsafe {
158+
for _ in 0..10000 {
159+
let lib = Library::new(LIBPATH).expect("open library");
160+
let _: Symbol<unsafe extern fn(u32) -> u32> =
161+
lib.get(b"test_identity_u32").expect("get fn");
162+
lib.close().expect("close is successful");
163+
}
164+
})
165+
}).collect();
166+
for handle in join_handles {
167+
handle.join().expect("thread should succeed");
168+
}
169+
}
170+
171+
148172
#[cfg(unix)]
149173
#[test]
150174
fn library_this_get() {

0 commit comments

Comments
 (0)