From 4ece71daadbb62165c3efcae2ae8f3152de3fe59 Mon Sep 17 00:00:00 2001 From: Simonas Kazlauskas Date: Sun, 14 Nov 2021 16:09:47 +0200 Subject: [PATCH] Library & Symbol exist on supported platforms only This makes the error messages and use of this library on targets such as wasm32-unknown-unknown much more straightforward. --- src/changelog.rs | 3 - src/lib.rs | 301 ++------------------------------------------- src/os/unix/mod.rs | 13 +- src/safe.rs | 293 +++++++++++++++++++++++++++++++++++++++++++ tests/constants.rs | 2 +- 5 files changed, 309 insertions(+), 303 deletions(-) create mode 100644 src/safe.rs diff --git a/src/changelog.rs b/src/changelog.rs index 744eb09..a7c9b14 100644 --- a/src/changelog.rs +++ b/src/changelog.rs @@ -192,7 +192,6 @@ pub mod r0_6_1 {} /// [`Error`]: crate::Error pub mod r0_6_0 {} - /// Release 0.5.2 (2019-07-07) /// /// * Added API to convert OS-specific `Library` and `Symbol` conversion to underlying resources. @@ -223,7 +222,6 @@ pub mod r0_5_0 {} /// * `cargo test --release` now works when testing libloading. pub mod r0_4_3 {} - /// Release 0.4.2 (2017-09-24) /// /// * Improved error and race-condition handling on Windows; @@ -231,7 +229,6 @@ pub mod r0_4_3 {} /// * Added `Symbol::::lift_option() -> Option>` convenience method. pub mod r0_4_2 {} - /// Release 0.4.1 (2017-08-29) /// /// * Solaris support diff --git a/src/lib.rs b/src/lib.rs index 8637032..e301e81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,307 +35,24 @@ //! //! The compiler will ensure that the loaded function will not outlive the `Library` from which it comes, //! preventing the most common memory-safety issues. -#![deny(missing_docs, clippy::all, unreachable_pub, unused)] +#![cfg_attr(any(unix, windows), deny(missing_docs, clippy::all, unreachable_pub, unused))] #![cfg_attr(docsrs, deny(broken_intra_doc_links))] #![cfg_attr(docsrs, feature(doc_cfg))] -use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; -use std::ffi::{OsStr, OsString}; -use std::fmt; -use std::marker; -use std::ops; - -pub use self::error::Error; -#[cfg(unix)] -use self::os::unix as imp; -#[cfg(windows)] -use self::os::windows as imp; - pub mod changelog; -mod error; pub mod os; mod util; -/// A loaded dynamic library. -pub struct Library(imp::Library); +mod error; +pub use self::error::Error; -impl Library { - /// Find and load a dynamic library. - /// - /// The `filename` argument may be either: - /// - /// * A library filename; - /// * The absolute path to the library; - /// * A relative (to the current working directory) path to the library. - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within it are executed. - /// For the purposes of safety, the execution of these routines is conceptually the same calling an - /// unknown foreign function and may impose arbitrary requirements on the caller for the call - /// to be sound. - /// - /// Additionally, the callers of this function must also ensure that execution of the - /// termination routines contained within the library is safe as well. These routines may be - /// executed when the library is unloaded. - /// - /// # Thread-safety - /// - /// The implementation strives to be as MT-safe as sanely possible, however on certain - /// platforms the underlying error-handling related APIs not always MT-safe. This library - /// shares these limitations on those platforms. In particular, on certain UNIX targets - /// `dlerror` is not MT-safe, resulting in garbage error messages in certain MT-scenarios. - /// - /// Calling this function from multiple threads is not MT-safe if used in conjunction with - /// library filenames and the library search path is modified (`SetDllDirectory` function on - /// Windows, `{DY,}LD_LIBRARY_PATH` environment variable on UNIX). - /// - /// # Platform-specific behaviour - /// - /// When a plain library filename is supplied, the locations in which the library is searched are - /// platform specific and cannot be adjusted in a portable manner. See the documentation for - /// the platform specific [`os::unix::Library::new`] and [`os::windows::Library::new`] methods - /// for further information on library lookup behaviour. - /// - /// If the `filename` specifies a library filename without a path and with the extension omitted, - /// the `.dll` extension is implicitly added on Windows. - /// - /// # Tips - /// - /// Distributing your dynamic libraries under a filename common to all platforms (e.g. - /// `awesome.module`) allows you to avoid code which has to account for platform’s conventional - /// library filenames. - /// - /// Strive to specify an absolute or at least a relative path to your library, unless - /// system-wide libraries are being loaded. Platform-dependent library search locations - /// combined with various quirks related to path-less filenames may cause flakiness in - /// programs. - /// - /// # Examples - /// - /// ```no_run - /// # use ::libloading::Library; - /// // Any of the following are valid. - /// unsafe { - /// let _ = Library::new("/path/to/awesome.module").unwrap(); - /// let _ = Library::new("../awesome.module").unwrap(); - /// let _ = Library::new("libsomelib.so.1").unwrap(); - /// } - /// ``` - pub unsafe fn new>(filename: P) -> Result { - imp::Library::new(filename).map(From::from) - } +#[cfg(any(unix, windows, docsrs))] +mod safe; +#[cfg(any(unix, windows, docsrs))] +pub use self::safe::{Library, Symbol}; - /// Get a pointer to a function or static variable by symbol name. - /// - /// The `symbol` may not contain any null bytes, with the exception of the last byte. Providing a - /// null-terminated `symbol` may help to avoid an allocation. - /// - /// The symbol is interpreted as-is; no mangling is done. This means that symbols like `x::y` are - /// most likely invalid. - /// - /// # Safety - /// - /// Users of this API must specify the correct type of the function or variable loaded. - /// - /// # Platform-specific behaviour - /// - /// The implementation of thread-local variables is extremely platform specific and uses of such - /// variables that work on e.g. Linux may have unintended behaviour on other targets. - /// - /// On POSIX implementations where the `dlerror` function is not confirmed to be MT-safe (such - /// as FreeBSD), this function will unconditionally return an error when the underlying `dlsym` - /// call returns a null pointer. There are rare situations where `dlsym` returns a genuine null - /// pointer without it being an error. If loading a null pointer is something you care about, - /// consider using the [`os::unix::Library::get_singlethreaded`] call. - /// - /// # Examples - /// - /// Given a loaded library: - /// - /// ```no_run - /// # use ::libloading::Library; - /// let lib = unsafe { - /// Library::new("/path/to/awesome.module").unwrap() - /// }; - /// ``` - /// - /// Loading and using a function looks like this: - /// - /// ```no_run - /// # use ::libloading::{Library, Symbol}; - /// # let lib = unsafe { - /// # Library::new("/path/to/awesome.module").unwrap() - /// # }; - /// unsafe { - /// let awesome_function: Symbol f64> = - /// lib.get(b"awesome_function\0").unwrap(); - /// awesome_function(0.42); - /// } - /// ``` - /// - /// A static variable may also be loaded and inspected: - /// - /// ```no_run - /// # use ::libloading::{Library, Symbol}; - /// # let lib = unsafe { Library::new("/path/to/awesome.module").unwrap() }; - /// unsafe { - /// let awesome_variable: Symbol<*mut f64> = lib.get(b"awesome_variable\0").unwrap(); - /// **awesome_variable = 42.0; - /// }; - /// ``` - pub unsafe fn get<'lib, T>(&'lib self, symbol: &[u8]) -> Result, Error> { - self.0.get(symbol).map(|from| Symbol::from_raw(from, self)) - } - - /// Unload the library. - /// - /// This method might be a no-op, depending on the flags with which the `Library` was opened, - /// what library was opened or other platform specifics. - /// - /// You only need to call this if you are interested in handling any errors that may arise when - /// library is unloaded. Otherwise the implementation of `Drop` for `Library` will close the - /// library and ignore the errors were they arise. - /// - /// The underlying data structures may still get leaked if an error does occur. - pub fn close(self) -> Result<(), Error> { - self.0.close() - } -} - -impl fmt::Debug for Library { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl From for Library { - fn from(lib: imp::Library) -> Library { - Library(lib) - } -} - -impl From for imp::Library { - fn from(lib: Library) -> imp::Library { - lib.0 - } -} - -unsafe impl Send for Library {} -unsafe impl Sync for Library {} - -/// Symbol from a library. -/// -/// This type is a safeguard against using dynamically loaded symbols after a `Library` is -/// unloaded. The primary method to create an instance of a `Symbol` is via [`Library::get`]. -/// -/// The `Deref` trait implementation allows the use of `Symbol` as if it was a function or variable -/// itself, without taking care to “extract” the function or variable manually most of the time. -/// -/// [`Library::get`]: Library::get -pub struct Symbol<'lib, T: 'lib> { - inner: imp::Symbol, - pd: marker::PhantomData<&'lib T>, -} - -impl<'lib, T> Symbol<'lib, T> { - /// Extract the wrapped `os::platform::Symbol`. - /// - /// # Safety - /// - /// Using this function relinquishes all the lifetime guarantees. It is up to the developer to - /// ensure the resulting `Symbol` is not used past the lifetime of the `Library` this symbol - /// was loaded from. - /// - /// # Examples - /// - /// ```no_run - /// # use ::libloading::{Library, Symbol}; - /// unsafe { - /// let lib = Library::new("/path/to/awesome.module").unwrap(); - /// let symbol: Symbol<*mut u32> = lib.get(b"symbol\0").unwrap(); - /// let symbol = symbol.into_raw(); - /// } - /// ``` - pub unsafe fn into_raw(self) -> imp::Symbol { - self.inner - } - - /// Wrap the `os::platform::Symbol` into this safe wrapper. - /// - /// Note that, in order to create association between the symbol and the library this symbol - /// came from, this function requires a reference to the library. - /// - /// # Safety - /// - /// The `library` reference must be exactly the library `sym` was loaded from. - /// - /// # Examples - /// - /// ```no_run - /// # use ::libloading::{Library, Symbol}; - /// unsafe { - /// let lib = Library::new("/path/to/awesome.module").unwrap(); - /// let symbol: Symbol<*mut u32> = lib.get(b"symbol\0").unwrap(); - /// let symbol = symbol.into_raw(); - /// let symbol = Symbol::from_raw(symbol, &lib); - /// } - /// ``` - pub unsafe fn from_raw(sym: imp::Symbol, library: &'lib L) -> Symbol<'lib, T> { - let _ = library; // ignore here for documentation purposes. - Symbol { - inner: sym, - pd: marker::PhantomData, - } - } -} - -impl<'lib, T> Symbol<'lib, Option> { - /// Lift Option out of the symbol. - /// - /// # Examples - /// - /// ```no_run - /// # use ::libloading::{Library, Symbol}; - /// unsafe { - /// let lib = Library::new("/path/to/awesome.module").unwrap(); - /// let symbol: Symbol> = lib.get(b"symbol\0").unwrap(); - /// let symbol: Symbol<*mut u32> = symbol.lift_option().expect("static is not null"); - /// } - /// ``` - pub fn lift_option(self) -> Option> { - self.inner.lift_option().map(|is| Symbol { - inner: is, - pd: marker::PhantomData, - }) - } -} - -impl<'lib, T> Clone for Symbol<'lib, T> { - fn clone(&self) -> Symbol<'lib, T> { - Symbol { - inner: self.inner.clone(), - pd: marker::PhantomData, - } - } -} - -// FIXME: implement FnOnce for callable stuff instead. -impl<'lib, T> ops::Deref for Symbol<'lib, T> { - type Target = T; - fn deref(&self) -> &T { - ops::Deref::deref(&self.inner) - } -} - -impl<'lib, T> fmt::Debug for Symbol<'lib, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } -} - -unsafe impl<'lib, T: Send> Send for Symbol<'lib, T> {} -unsafe impl<'lib, T: Sync> Sync for Symbol<'lib, T> {} +use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; +use std::ffi::{OsStr, OsString}; /// Converts a library name to a filename generally appropriate for use on the system. /// diff --git a/src/os/unix/mod.rs b/src/os/unix/mod.rs index 508f2b1..ce79491 100644 --- a/src/os/unix/mod.rs +++ b/src/os/unix/mod.rs @@ -1,19 +1,18 @@ // A hack for docs.rs to build documentation that has both windows and linux documentation in the // same rustdoc build visible. #[cfg(all(docsrs, not(unix)))] -mod unix_imports { -} +mod unix_imports {} #[cfg(any(not(docsrs), unix))] mod unix_imports { pub(super) use std::os::unix::ffi::OsStrExt; } -use self::unix_imports::*; -use util::{ensure_compatible_types, cstr_cow_from_bytes}; -use std::ffi::{CStr, OsStr}; -use std::{fmt, marker, mem, ptr}; -use std::os::raw; pub use self::consts::*; +use self::unix_imports::*; +use std::ffi::{CStr, OsStr}; +use std::os::raw; +use std::{fmt, marker, mem, ptr}; +use util::{cstr_cow_from_bytes, ensure_compatible_types}; mod consts; diff --git a/src/safe.rs b/src/safe.rs new file mode 100644 index 0000000..578610b --- /dev/null +++ b/src/safe.rs @@ -0,0 +1,293 @@ +use super::Error; +#[cfg(any(unix, docsrs))] +use super::os::unix as imp; +#[cfg(windows)] +use super::os::windows as imp; +use super::Error; +use std::ffi::OsStr; +use std::fmt; +use std::marker; +use std::ops; + +/// A loaded dynamic library. +#[cfg_attr(docsrs, doc(cfg(any(unix, windows))))] +pub struct Library(imp::Library); + +impl Library { + /// Find and load a dynamic library. + /// + /// The `filename` argument may be either: + /// + /// * A library filename; + /// * The absolute path to the library; + /// * A relative (to the current working directory) path to the library. + /// + /// # Safety + /// + /// When a library is loaded, initialisation routines contained within it are executed. + /// For the purposes of safety, the execution of these routines is conceptually the same calling an + /// unknown foreign function and may impose arbitrary requirements on the caller for the call + /// to be sound. + /// + /// Additionally, the callers of this function must also ensure that execution of the + /// termination routines contained within the library is safe as well. These routines may be + /// executed when the library is unloaded. + /// + /// # Thread-safety + /// + /// The implementation strives to be as MT-safe as sanely possible, however on certain + /// platforms the underlying error-handling related APIs not always MT-safe. This library + /// shares these limitations on those platforms. In particular, on certain UNIX targets + /// `dlerror` is not MT-safe, resulting in garbage error messages in certain MT-scenarios. + /// + /// Calling this function from multiple threads is not MT-safe if used in conjunction with + /// library filenames and the library search path is modified (`SetDllDirectory` function on + /// Windows, `{DY,}LD_LIBRARY_PATH` environment variable on UNIX). + /// + /// # Platform-specific behaviour + /// + /// When a plain library filename is supplied, the locations in which the library is searched are + /// platform specific and cannot be adjusted in a portable manner. See the documentation for + /// the platform specific [`os::unix::Library::new`] and [`os::windows::Library::new`] methods + /// for further information on library lookup behaviour. + /// + /// If the `filename` specifies a library filename without a path and with the extension omitted, + /// the `.dll` extension is implicitly added on Windows. + /// + /// # Tips + /// + /// Distributing your dynamic libraries under a filename common to all platforms (e.g. + /// `awesome.module`) allows you to avoid code which has to account for platform’s conventional + /// library filenames. + /// + /// Strive to specify an absolute or at least a relative path to your library, unless + /// system-wide libraries are being loaded. Platform-dependent library search locations + /// combined with various quirks related to path-less filenames may cause flakiness in + /// programs. + /// + /// # Examples + /// + /// ```no_run + /// # use ::libloading::Library; + /// // Any of the following are valid. + /// unsafe { + /// let _ = Library::new("/path/to/awesome.module").unwrap(); + /// let _ = Library::new("../awesome.module").unwrap(); + /// let _ = Library::new("libsomelib.so.1").unwrap(); + /// } + /// ``` + pub unsafe fn new>(filename: P) -> Result { + imp::Library::new(filename).map(From::from) + } + + /// Get a pointer to a function or static variable by symbol name. + /// + /// The `symbol` may not contain any null bytes, with the exception of the last byte. Providing a + /// null-terminated `symbol` may help to avoid an allocation. + /// + /// The symbol is interpreted as-is; no mangling is done. This means that symbols like `x::y` are + /// most likely invalid. + /// + /// # Safety + /// + /// Users of this API must specify the correct type of the function or variable loaded. + /// + /// # Platform-specific behaviour + /// + /// The implementation of thread-local variables is extremely platform specific and uses of such + /// variables that work on e.g. Linux may have unintended behaviour on other targets. + /// + /// On POSIX implementations where the `dlerror` function is not confirmed to be MT-safe (such + /// as FreeBSD), this function will unconditionally return an error when the underlying `dlsym` + /// call returns a null pointer. There are rare situations where `dlsym` returns a genuine null + /// pointer without it being an error. If loading a null pointer is something you care about, + /// consider using the [`os::unix::Library::get_singlethreaded`] call. + /// + /// # Examples + /// + /// Given a loaded library: + /// + /// ```no_run + /// # use ::libloading::Library; + /// let lib = unsafe { + /// Library::new("/path/to/awesome.module").unwrap() + /// }; + /// ``` + /// + /// Loading and using a function looks like this: + /// + /// ```no_run + /// # use ::libloading::{Library, Symbol}; + /// # let lib = unsafe { + /// # Library::new("/path/to/awesome.module").unwrap() + /// # }; + /// unsafe { + /// let awesome_function: Symbol f64> = + /// lib.get(b"awesome_function\0").unwrap(); + /// awesome_function(0.42); + /// } + /// ``` + /// + /// A static variable may also be loaded and inspected: + /// + /// ```no_run + /// # use ::libloading::{Library, Symbol}; + /// # let lib = unsafe { Library::new("/path/to/awesome.module").unwrap() }; + /// unsafe { + /// let awesome_variable: Symbol<*mut f64> = lib.get(b"awesome_variable\0").unwrap(); + /// **awesome_variable = 42.0; + /// }; + /// ``` + pub unsafe fn get<'lib, T>(&'lib self, symbol: &[u8]) -> Result, Error> { + self.0.get(symbol).map(|from| Symbol::from_raw(from, self)) + } + + /// Unload the library. + /// + /// This method might be a no-op, depending on the flags with which the `Library` was opened, + /// what library was opened or other platform specifics. + /// + /// You only need to call this if you are interested in handling any errors that may arise when + /// library is unloaded. Otherwise the implementation of `Drop` for `Library` will close the + /// library and ignore the errors were they arise. + /// + /// The underlying data structures may still get leaked if an error does occur. + pub fn close(self) -> Result<(), Error> { + self.0.close() + } +} + +impl fmt::Debug for Library { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl From for Library { + fn from(lib: imp::Library) -> Library { + Library(lib) + } +} + +impl From for imp::Library { + fn from(lib: Library) -> imp::Library { + lib.0 + } +} + +unsafe impl Send for Library {} +unsafe impl Sync for Library {} + +/// Symbol from a library. +/// +/// This type is a safeguard against using dynamically loaded symbols after a `Library` is +/// unloaded. The primary method to create an instance of a `Symbol` is via [`Library::get`]. +/// +/// The `Deref` trait implementation allows the use of `Symbol` as if it was a function or variable +/// itself, without taking care to “extract” the function or variable manually most of the time. +/// +/// [`Library::get`]: Library::get +#[cfg_attr(docsrs, doc(cfg(any(unix, windows))))] +pub struct Symbol<'lib, T: 'lib> { + inner: imp::Symbol, + pd: marker::PhantomData<&'lib T>, +} + +impl<'lib, T> Symbol<'lib, T> { + /// Extract the wrapped `os::platform::Symbol`. + /// + /// # Safety + /// + /// Using this function relinquishes all the lifetime guarantees. It is up to the developer to + /// ensure the resulting `Symbol` is not used past the lifetime of the `Library` this symbol + /// was loaded from. + /// + /// # Examples + /// + /// ```no_run + /// # use ::libloading::{Library, Symbol}; + /// unsafe { + /// let lib = Library::new("/path/to/awesome.module").unwrap(); + /// let symbol: Symbol<*mut u32> = lib.get(b"symbol\0").unwrap(); + /// let symbol = symbol.into_raw(); + /// } + /// ``` + pub unsafe fn into_raw(self) -> imp::Symbol { + self.inner + } + + /// Wrap the `os::platform::Symbol` into this safe wrapper. + /// + /// Note that, in order to create association between the symbol and the library this symbol + /// came from, this function requires a reference to the library. + /// + /// # Safety + /// + /// The `library` reference must be exactly the library `sym` was loaded from. + /// + /// # Examples + /// + /// ```no_run + /// # use ::libloading::{Library, Symbol}; + /// unsafe { + /// let lib = Library::new("/path/to/awesome.module").unwrap(); + /// let symbol: Symbol<*mut u32> = lib.get(b"symbol\0").unwrap(); + /// let symbol = symbol.into_raw(); + /// let symbol = Symbol::from_raw(symbol, &lib); + /// } + /// ``` + pub unsafe fn from_raw(sym: imp::Symbol, library: &'lib L) -> Symbol<'lib, T> { + let _ = library; // ignore here for documentation purposes. + Symbol { + inner: sym, + pd: marker::PhantomData, + } + } +} + +impl<'lib, T> Symbol<'lib, Option> { + /// Lift Option out of the symbol. + /// + /// # Examples + /// + /// ```no_run + /// # use ::libloading::{Library, Symbol}; + /// unsafe { + /// let lib = Library::new("/path/to/awesome.module").unwrap(); + /// let symbol: Symbol> = lib.get(b"symbol\0").unwrap(); + /// let symbol: Symbol<*mut u32> = symbol.lift_option().expect("static is not null"); + /// } + /// ``` + pub fn lift_option(self) -> Option> { + self.inner.lift_option().map(|is| Symbol { + inner: is, + pd: marker::PhantomData, + }) + } +} + +impl<'lib, T> Clone for Symbol<'lib, T> { + fn clone(&self) -> Symbol<'lib, T> { + Symbol { + inner: self.inner.clone(), + pd: marker::PhantomData, + } + } +} + +// FIXME: implement FnOnce for callable stuff instead. +impl<'lib, T> ops::Deref for Symbol<'lib, T> { + type Target = T; + fn deref(&self) -> &T { + ops::Deref::deref(&self.inner) + } +} + +impl<'lib, T> fmt::Debug for Symbol<'lib, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +unsafe impl<'lib, T: Send> Send for Symbol<'lib, T> {} +unsafe impl<'lib, T: Sync> Sync for Symbol<'lib, T> {} diff --git a/tests/constants.rs b/tests/constants.rs index ad910c4..6ae5a84 100644 --- a/tests/constants.rs +++ b/tests/constants.rs @@ -1,5 +1,5 @@ -extern crate libloading; extern crate libc; +extern crate libloading; extern crate static_assertions; #[cfg(all(test, unix))]