diff --git a/.github/workflows/libloading.yml b/.github/workflows/libloading.yml index 673d36f9f..274208268 100644 --- a/.github/workflows/libloading.yml +++ b/.github/workflows/libloading.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - rust_toolchain: [nightly, stable, 1.71.0] + rust_toolchain: [nightly, stable, 1.81.0] os: [ubuntu-latest, windows-latest, macOS-latest] timeout-minutes: 20 steps: @@ -24,10 +24,12 @@ jobs: - run: rustup default ${{ matrix.rust_toolchain }} - run: rustup component add clippy - run: cargo update -p libc --precise 0.2.155 - if: ${{ matrix.rust_toolchain == '1.71.0' }} + if: ${{ matrix.rust_toolchain == '1.81.0' }} - run: cargo clippy - run: cargo test -- --nocapture - run: cargo test --release -- --nocapture + - run: cargo test --no-default-features -- --nocapture + - run: cargo test --release --no-default-features -- --nocapture - run: cargo rustdoc -Zunstable-options --config 'build.rustdocflags=["--cfg", "libloading_docs", "-D", "rustdoc::broken_intra_doc_links"]' if: ${{ matrix.rust_toolchain == 'nightly' }} # pwsh.exe drops quotes kekw. https://stackoverflow.com/a/59036879 diff --git a/Cargo.toml b/Cargo.toml index a1084d9f0..9cea23f76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.mkd" description = "Bindings around the platform's dynamic library loading primitives with greatly improved memory safety." keywords = ["dlopen", "load", "shared", "dylib"] categories = ["api-bindings"] -rust-version = "1.71.0" +rust-version = "1.81.0" edition = "2015" [features] diff --git a/src/as_filename.rs b/src/as_filename.rs new file mode 100644 index 000000000..d28702e92 --- /dev/null +++ b/src/as_filename.rs @@ -0,0 +1,356 @@ +use alloc::ffi::CString; +use alloc::string::String; +use alloc::vec::Vec; +use core::ffi::{c_char, CStr}; +use util::cstr_cow_from_bytes; +use Error; + +mod private { + + pub trait AsFilenameSeal { + #[allow(unused)] //Posix doesnt use this + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result; + + #[allow(unused)] //Windows doesnt use this + fn posix_filename( + &self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result; + } +} + +/// This trait is implemented on all types where libloading can derrive a filename from. +/// It is sealed and cannot be implemented by a user of libloading. +/// +/// This trait is implemented for the following common types: +/// - String &String &str +/// - CString &CString &CStr +/// - OsString &OsString &OsStr +/// - PathBuf &PathBuf &Path +/// - &[u8] assumes utf8 data! +/// - &[u16] assumes utf16-ne data! +/// +pub trait AsFilename: private::AsFilenameSeal {} + +impl AsFilename for T where T: private::AsFilenameSeal {} + +impl private::AsFilenameSeal for &str { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + let mut utf16: Vec = self.encode_utf16().collect(); + if utf16.last() != Some(&0) { + utf16.push(0); + }; + function(utf16.as_ptr()) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let cow = cstr_cow_from_bytes(self.as_bytes())?; + function(cow.as_ptr()) + } +} + +impl private::AsFilenameSeal for &String { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let cow = cstr_cow_from_bytes(self.as_bytes())?; + function(cow.as_ptr()) + } +} + +impl private::AsFilenameSeal for String { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let cow = cstr_cow_from_bytes(self.as_bytes())?; + function(cow.as_ptr()) + } +} + +impl private::AsFilenameSeal for &CStr { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + //We assume cstr is utf-8 here, if it's something bespoke like CESU-8 (thanks java) then yeah... no. + let utf8 = self.to_str()?; + utf8.windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + function(self.as_ptr()) + } +} + +impl private::AsFilenameSeal for &CString { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_c_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_c_str().posix_filename(function) + } +} + +impl private::AsFilenameSeal for CString { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_c_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_c_str().posix_filename(function) + } +} + +/// This implementation assumes that a slice always contains utf-8 bytes. +/// (which is likely the most common case if the slice originated in rust) +impl private::AsFilenameSeal for &[u8] { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + let utf8 = core::str::from_utf8(self)?; + utf8.windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let utf8 = core::str::from_utf8(self)?; + utf8.posix_filename(function) + } +} + +impl private::AsFilenameSeal for [u8; N] { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_slice().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_slice().posix_filename(function) + } +} + +impl private::AsFilenameSeal for &[u8; N] { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_slice().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_slice().posix_filename(function) + } +} + +/// This implementation assumes that the slice contains utf-16 in native endian. +/// Sidenote: For windows this is always utf-16-le because the last big endian Windows system was the xbox 360 that rust doesn't support. +/// For linux this is highly likely to also be utf-16-le because big endian is only used in some old mips routers or some IBM hardware. +impl private::AsFilenameSeal for &[u16] { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + //Check that we have valid utf-16 + for c in core::char::decode_utf16(self.iter().copied()) { + let _ = c?; + } + + if self.last() != Some(&0) { + let mut copy = self.to_vec(); + copy.push(0); + return function(copy.as_ptr()); + } + + function(self.as_ptr()) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let utf8 = String::from_utf16(self)?; + utf8.posix_filename(function) + } +} + +#[cfg(feature = "std")] +#[cfg(any(windows, unix))] +mod std { + use as_filename::private; + use core::ffi::c_char; + use std::ffi::{OsStr, OsString}; + use Error; + + impl private::AsFilenameSeal for &OsStr { + #[cfg(unix)] + fn windows_filename( + &self, + _function: impl FnOnce(*const u16) -> Result, + ) -> Result { + panic!("windows_filename() not implemented for OsStr on posix platform"); + } + + #[cfg(windows)] + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + let mut utf16: alloc::vec::Vec = + std::os::windows::ffi::OsStrExt::encode_wide(*self).collect(); + if utf16.last() != Some(&0) { + utf16.push(0); + }; + function(utf16.as_ptr()) + } + + #[cfg(unix)] + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let cow = + crate::util::cstr_cow_from_bytes(std::os::unix::ffi::OsStrExt::as_bytes(*self))?; + function(cow.as_ptr()) + } + + #[cfg(windows)] + fn posix_filename( + &self, + _function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + panic!("posix_filename() not implemented for OsStr on windows") + } + } + + impl private::AsFilenameSeal for &OsString { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_os_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().posix_filename(function) + } + } + + impl private::AsFilenameSeal for OsString { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_os_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().posix_filename(function) + } + } + + impl private::AsFilenameSeal for std::path::PathBuf { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_os_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().posix_filename(function) + } + } + + impl private::AsFilenameSeal for &std::path::PathBuf { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_os_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().posix_filename(function) + } + } + + impl private::AsFilenameSeal for &std::path::Path { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_os_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().posix_filename(function) + } + } +} diff --git a/src/as_symbol_name.rs b/src/as_symbol_name.rs new file mode 100644 index 000000000..069bccf50 --- /dev/null +++ b/src/as_symbol_name.rs @@ -0,0 +1,193 @@ +use crate::{util, Error}; +use alloc::ffi::CString; +use alloc::string::String; +use core::ffi::{c_char, CStr}; + +mod private { + + pub trait AsSymbolNameSeal { + fn symbol_name( + &self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result; + } +} + +/// This trait is implemented on all types where libloading can derrive a symbol name from. +/// It is sealed and cannot be implemented by a user of libloading. +/// +/// This trait is implemented for the following common types: +/// - String &String &str +/// - CString &CString &CStr +/// - OsString &OsString &OsStr +/// - PathBuf &PathBuf &Path +/// - &[u8] assumes utf8 data! +/// - &[u16] assumes utf16-ne data! +/// +pub trait AsSymbolName: private::AsSymbolNameSeal {} + +impl AsSymbolName for T where T: private::AsSymbolNameSeal {} + +impl private::AsSymbolNameSeal for &str { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_bytes().symbol_name(function) + } +} + +impl private::AsSymbolNameSeal for &String { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_str().symbol_name(function) + } +} + +impl private::AsSymbolNameSeal for String { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_str().symbol_name(function) + } +} + +impl private::AsSymbolNameSeal for &CStr { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + function(self.as_ptr()) + } +} + +impl private::AsSymbolNameSeal for &CString { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + function(self.as_ptr()) + } +} + +impl private::AsSymbolNameSeal for CString { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + function(self.as_ptr()) + } +} + +impl private::AsSymbolNameSeal for &[u8] { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let cow = util::cstr_cow_from_bytes(self)?; + function(cow.as_ptr()) + } +} + +impl private::AsSymbolNameSeal for [u8; N] { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_slice().symbol_name(function) + } +} + +impl private::AsSymbolNameSeal for &[u8; N] { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_slice().symbol_name(function) + } +} + +impl private::AsSymbolNameSeal for &[u16] { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let string = String::from_utf16(self)?; + string.symbol_name(function) + } +} + +#[cfg(feature = "std")] +#[cfg(any(windows, unix))] +mod std { + use as_symbol_name::private; + use std::ffi::{c_char, OsStr, OsString}; + use std::path::{Path, PathBuf}; + use Error; + + impl private::AsSymbolNameSeal for &OsStr { + #[cfg(unix)] + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + std::os::unix::ffi::OsStrExt::as_bytes(*self).symbol_name(function) + } + + #[cfg(windows)] + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_encoded_bytes().symbol_name(function) + } + } + + impl private::AsSymbolNameSeal for &OsString { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().symbol_name(function) + } + } + + impl private::AsSymbolNameSeal for OsString { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().symbol_name(function) + } + } + + impl private::AsSymbolNameSeal for PathBuf { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().symbol_name(function) + } + } + + impl private::AsSymbolNameSeal for &PathBuf { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().symbol_name(function) + } + } + + impl private::AsSymbolNameSeal for &Path { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().symbol_name(function) + } + } +} diff --git a/src/error.rs b/src/error.rs index 15d7d3f7f..ed37c227b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,8 @@ use alloc::ffi::CString; +use alloc::string::FromUtf16Error; +use core::char::DecodeUtf16Error; use core::ffi::CStr; +use core::str::Utf8Error; /// A `dlerror` error. pub struct DlDescription(pub(crate) CString); @@ -17,18 +20,22 @@ impl From<&CStr> for DlDescription { } /// A Windows API error. -#[cfg(not(feature = "std"))] +#[derive(Copy, Clone)] pub struct WindowsError(pub(crate) i32); -#[cfg(feature = "std")] -pub struct WindowsError(pub(crate) std::io::Error); - impl core::fmt::Debug for WindowsError { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { core::fmt::Debug::fmt(&self.0, f) } } +#[cfg(feature = "std")] +impl From for std::io::Error { + fn from(value: WindowsError) -> Self { + std::io::Error::from_raw_os_error(value.0) + } +} + /// Errors. #[derive(Debug)] #[non_exhaustive] @@ -89,6 +96,21 @@ pub enum Error { /// The source error. source: alloc::ffi::NulError, }, + /// Could not parse some sequence of bytes as utf-8. + Utf8Error { + /// The source error. + source: Utf8Error, + }, + /// Could not parse some sequence of bytes as utf-16. + DecodeUtf16Error { + ///The source error. + source: DecodeUtf16Error, + }, + /// Could not parse some sequence of bytes as utf-16. + FromUtf16Error { + ///The source error. + source: FromUtf16Error, + }, /// Could not create a new CString from bytes with trailing null. CreateCStringWithTrailing { /// The source error. @@ -96,27 +118,30 @@ pub enum Error { }, } -impl core::error::Error for Error { - #[cfg(not(feature = "std"))] - fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { - use Error::*; - match *self { - CreateCString { ref source } => Some(source), - CreateCStringWithTrailing { ref source } => Some(source), - _ => None, - } +impl From for Error { + fn from(value: Utf8Error) -> Self { + Self::Utf8Error { source: value } + } +} + +impl From for Error { + fn from(value: DecodeUtf16Error) -> Self { + Self::DecodeUtf16Error { source: value } } +} - #[cfg(feature = "std")] +impl From for Error { + fn from(value: FromUtf16Error) -> Self { + Self::FromUtf16Error { source: value } + } +} + +impl core::error::Error for Error { fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { use Error::*; match *self { CreateCString { ref source } => Some(source), CreateCStringWithTrailing { ref source } => Some(source), - LoadLibraryExW { ref source } => Some(&source.0), - GetModuleHandleExW { ref source } => Some(&source.0), - GetProcAddress { ref source } => Some(&source.0), - FreeLibrary { ref source } => Some(&source.0), _ => None, } } @@ -152,10 +177,17 @@ impl core::fmt::Display for Error { write!(f, "FreeLibrary failed, but system did not report the error") } CreateCString { .. } => write!(f, "could not create a C string from bytes"), + CreateCStringWithTrailing { .. } => write!( f, "could not create a C string from bytes with trailing null" ), + Utf8Error { .. } => write!( + f, + "could not create a C string from bytes with trailing null" + ), + DecodeUtf16Error { .. } => write!(f, "could not decode some bytes as utf16"), + FromUtf16Error { .. } => write!(f, "could not parse some utf16 bytes to a string"), IncompatibleSize => write!(f, "requested type cannot possibly work"), } } diff --git a/src/lib.rs b/src/lib.rs index e78351c4a..ea1b9ab07 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ //! fn call_dynamic() -> Result> { //! unsafe { //! let lib = libloading::Library::new("/path/to/liblibrary.so")?; -//! let func: libloading::Symbol u32> = lib.get(b"my_func")?; +//! let func: libloading::Symbol u32> = lib.get(b"my_func")?; //! Ok(func()) //! } //! } @@ -46,6 +46,12 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; +mod as_filename; +mod as_symbol_name; + +pub use as_filename::AsFilename; +pub use as_symbol_name::AsSymbolName; + pub mod changelog; mod error; pub mod os; diff --git a/src/os/unix/mod.rs b/src/os/unix/mod.rs index 9eb3d8bd9..1cdf57afa 100644 --- a/src/os/unix/mod.rs +++ b/src/os/unix/mod.rs @@ -1,25 +1,10 @@ -// A hack for docs.rs to build documentation that has both windows and linux documentation in the -// same rustdoc build visible. -#[cfg(all(libloading_docs, not(unix)))] -mod unix_imports {} -#[cfg(any(not(libloading_docs), unix))] -mod unix_imports { - #[cfg(feature = "std")] - pub(super) use std::os::unix::ffi::OsStrExt; -} - pub use self::consts::*; - -#[cfg(feature = "std")] -use self::unix_imports::*; - +use as_filename::AsFilename; +use as_symbol_name::AsSymbolName; use core::ffi::CStr; -#[cfg(feature = "std")] -use std::ffi::OsStr; - use core::ptr::null; use core::{fmt, marker, mem, ptr}; -use util::{cstr_cow_from_bytes, ensure_compatible_types}; +use util::ensure_compatible_types; mod consts; @@ -138,67 +123,10 @@ impl Library { /// termination routines contained within the library is safe as well. These routines may be /// executed when the library is unloaded. #[inline] - #[cfg(feature = "std")] - pub unsafe fn new>(filename: P) -> Result { + pub unsafe fn new(filename: impl AsFilename) -> Result { Library::open(Some(filename), RTLD_LAZY | RTLD_LOCAL) } - /// Find and eagerly load a shared library (module). - /// - /// If the `filename` contains a [path separator], the `filename` is interpreted as a `path` to - /// a file. Otherwise, platform-specific algorithms are employed to find a library with a - /// matching file name. - /// - /// This function will temporarily copy `filename` unless it is 0 terminated. - /// - /// If the `filename` contains 0 bytes other than the last character then this function will error - /// - /// This is equivalent to [Library::open](filename, [RTLD_LAZY] | [RTLD_LOCAL]). - /// - /// [path separator]: std::path::MAIN_SEPARATOR - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library 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. - pub unsafe fn new_utf8(filename: impl AsRef) -> Result { - Library::open_utf8(filename.as_ref(), RTLD_LAZY | RTLD_LOCAL) - } - - /// Find and eagerly load a shared library (module). - /// - /// If the `filename` contains a [path separator], the `filename` is interpreted as a `path` to - /// a file. Otherwise, platform-specific algorithms are employed to find a library with a - /// matching file name. - /// - /// This function will temporarily copy `filename` unless it last element is 0. - /// - /// If the `filename` contains 0 bytes other than the last element then this function will error - /// - /// This is equivalent to [Library::open](filename, [RTLD_LAZY] | [RTLD_LOCAL]). - /// - /// [path separator]: std::path::MAIN_SEPARATOR - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library 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. - pub unsafe fn new_raw(filename: &[u8]) -> Result { - Library::open_raw(filename, RTLD_LAZY | RTLD_LOCAL) - } - /// Load the `Library` representing the current executable. /// /// [`Library::get`] calls of the returned `Library` will look for symbols in following @@ -241,83 +169,18 @@ impl Library { /// 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. - #[cfg(feature = "std")] pub unsafe fn open

( filename: Option

, flags: core::ffi::c_int, ) -> Result where - P: AsRef, + P: AsFilename, { - //TODO Why is this an option?, we have Library::this? - let filename = match filename { - None => None, - Some(ref f) => Some(cstr_cow_from_bytes(f.as_ref().as_bytes())?), + let Some(filename) = filename else { + return Self::open_char_ptr(null(), flags); }; - let result = Library::open_char_ptr( - match filename { - None => null(), - Some(ref f) => f.as_ptr(), - }, - flags, - ); - - drop(filename); - result - } - - /// Find and load an executable object file (shared library). - /// - /// This function will copy the filename if it does not end with a terminating 0 character. - /// - /// This function will fail if the filename contains a 0 character other than the last character. - /// - /// Corresponds to `dlopen(filename, flags)`. - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library 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. - pub unsafe fn open_utf8( - filename: impl AsRef, - flags: core::ffi::c_int, - ) -> Result { - Library::open_raw(filename.as_ref().as_bytes(), flags) - } - - /// Find and load an executable object file (shared library). - /// - /// This function will copy the filename if the last element is not a 0 byte. - /// - /// This function will fail if the filename contains a 0 byte other than the last element. - /// - /// Corresponds to `dlopen(filename, flags)`. - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library 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. - pub unsafe fn open_raw( - filename: &[u8], - flags: core::ffi::c_int, - ) -> Result { - let filename = cstr_cow_from_bytes(filename)?; - let res = Library::open_char_ptr(filename.as_ptr(), flags); - drop(filename); - res + filename.posix_filename(|posix_filename| Library::open_char_ptr(posix_filename, flags)) } /// private helper to call dlopen+dlerror once we de-tangled the string into a raw pointer to a 0 terminated utf-8 string. @@ -342,38 +205,43 @@ impl Library { .map_err(|e| e.unwrap_or(crate::Error::DlOpenUnknown)) } - unsafe fn get_impl(&self, symbol: &[u8], on_null: F) -> Result, crate::Error> + unsafe fn get_impl( + &self, + symbol: impl AsSymbolName, + on_null: F, + ) -> Result, crate::Error> where F: FnOnce() -> Result, crate::Error>, { ensure_compatible_types::()?; - let symbol = cstr_cow_from_bytes(symbol)?; // `dlsym` may return nullptr in two cases: when a symbol genuinely points to a null // pointer or the symbol cannot be found. In order to detect this case a double dlerror // pattern must be used, which is, sadly, a little bit racy. // // We try to leave as little space as possible for this to occur, but we can’t exactly // fully prevent it. - let result = with_dlerror( - || { - dlerror(); - let symbol = dlsym(self.handle, symbol.as_ptr()); - if symbol.is_null() { - None - } else { - Some(Symbol { - pointer: symbol, - pd: marker::PhantomData, - }) - } - }, - |desc| crate::Error::DlSym { desc: desc.into() }, - ); - match result { - Err(None) => on_null(), - Err(Some(e)) => Err(e), - Ok(x) => Ok(x), - } + symbol.symbol_name(|posix_symbol| { + let result = with_dlerror( + || { + dlerror(); + let symbol = dlsym(self.handle, posix_symbol); + if symbol.is_null() { + None + } else { + Some(Symbol { + pointer: symbol, + pd: marker::PhantomData, + }) + } + }, + |desc| crate::Error::DlSym { desc: desc.into() }, + ); + match result { + Err(None) => on_null(), + Err(Some(e)) => Err(e), + Ok(x) => Ok(x), + } + }) } /// Get a pointer to a function or static variable by symbol name. @@ -400,7 +268,7 @@ impl Library { /// pointer without it being an error. If loading a null pointer is something you care about, /// consider using the [`Library::get_singlethreaded`] call. #[inline(always)] - pub unsafe fn get(&self, symbol: &[u8]) -> Result, crate::Error> { + pub unsafe fn get(&self, symbol: impl AsSymbolName) -> Result, crate::Error> { extern crate cfg_if; cfg_if::cfg_if! { // These targets are known to have MT-safe `dlerror`. @@ -444,7 +312,10 @@ impl Library { /// 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. #[inline(always)] - pub unsafe fn get_singlethreaded(&self, symbol: &[u8]) -> Result, crate::Error> { + pub unsafe fn get_singlethreaded( + &self, + symbol: impl AsSymbolName, + ) -> Result, crate::Error> { self.get_impl(symbol, || { Ok(Symbol { pointer: ptr::null_mut(), diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs index 7f824d25f..bf41a6140 100644 --- a/src/os/windows/mod.rs +++ b/src/os/windows/mod.rs @@ -5,8 +5,6 @@ mod windows_imports {} #[cfg(any(not(libloading_docs), windows))] mod windows_imports { use super::{BOOL, DWORD, FARPROC, HANDLE, HMODULE}; - #[cfg(feature = "std")] - pub(super) use std::os::windows::ffi::{OsStrExt, OsStringExt}; windows_link::link!("kernel32.dll" "system" fn GetLastError() -> DWORD); windows_link::link!("kernel32.dll" "system" fn SetThreadErrorMode(new_mode: DWORD, old_mode: *mut DWORD) -> BOOL); windows_link::link!("kernel32.dll" "system" fn GetModuleHandleExW(flags: u32, module_name: *const u16, module: *mut HMODULE) -> BOOL); @@ -17,11 +15,10 @@ mod windows_imports { } use self::windows_imports::*; -use alloc::vec::Vec; +use as_filename::AsFilename; +use as_symbol_name::AsSymbolName; use core::{fmt, marker, mem, ptr}; -#[cfg(feature = "std")] -use std::ffi::{OsStr, OsString}; -use util::{cstr_cow_from_bytes, ensure_compatible_types}; +use util::ensure_compatible_types; /// The platform-specific counterpart of the cross-platform [`Library`](crate::Library). pub struct Library(HMODULE); @@ -69,81 +66,10 @@ impl Library { /// termination routines contained within the library is safe as well. These routines may be /// executed when the library is unloaded. #[inline] - #[cfg(feature = "std")] - pub unsafe fn new>(filename: P) -> Result { + pub unsafe fn new(filename: impl AsFilename) -> Result { Library::load_with_flags(filename, 0) } - /// Find and load a module. - /// - /// If the `filename` specifies a full path, the function only searches that path for the - /// module. Otherwise, if the `filename` specifies a relative path or a module name without a - /// path, the function uses a Windows-specific search strategy to find the module. For more - /// information, see the [Remarks on MSDN][msdn]. - /// - /// If the `filename` specifies a library filename without a path and with the extension omitted, - /// the `.dll` extension is implicitly added. This behaviour may be suppressed by appending a - /// trailing `.` to the `filename`. - /// - /// This function will check if the last element in the filename slice is 0. If it is then - /// the pointer of the slice is passed to windows. Otherwise, the function will copy - /// the slice and append a 0 element at the end. To prevent unnecessary copying it is recommended - /// to add the 0 element on the caller side. - /// - /// This is equivalent to [Library::load_with_flags](filename, 0). - /// - /// [msdn]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw#remarks - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library 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. - #[inline] - pub unsafe fn new_raw(filename: &[u16]) -> Result { - Library::load_with_flags_raw(filename, 0) - } - - /// Find and load a module. - /// - /// If the `filename` specifies a full path, the function only searches that path for the - /// module. Otherwise, if the `filename` specifies a relative path or a module name without a - /// path, the function uses a Windows-specific search strategy to find the module. For more - /// information, see the [Remarks on MSDN][msdn]. - /// - /// If the `filename` specifies a library filename without a path and with the extension omitted, - /// the `.dll` extension is implicitly added. This behaviour may be suppressed by appending a - /// trailing `.` to the `filename`. - /// - /// This function will convert filename from its rust utf8 representation - /// to the utf16 w_char representation windows expects. - /// - /// This is equivalent to [Library::load_with_flags](filename, 0). - /// - /// [msdn]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw#remarks - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library 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. - #[inline] - pub unsafe fn new_utf8(filename: impl AsRef) -> Result { - let filename = filename.as_ref(); - let collector: Vec = filename.encode_utf16().collect(); - Library::new_raw(&collector) - } - /// Get the `Library` representing the original program executable. /// /// Note that the behaviour of the `Library` loaded with this method is different from @@ -188,173 +114,26 @@ impl Library { /// This is equivalent to `GetModuleHandleExW(0, filename, _)`. /// /// [MSDN]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexw - #[cfg(feature = "std")] - pub fn open_already_loaded>(filename: P) -> Result { - let wide_filename: Vec = filename.as_ref().encode_wide().chain(Some(0)).collect(); - - let ret = unsafe { - let mut handle: HMODULE = 0; - with_get_last_error( - |source| crate::Error::GetModuleHandleExW { source }, - || { - // Make sure no winapi calls as a result of drop happen inside this closure, because - // otherwise that might change the return value of the GetLastError. - let result = GetModuleHandleExW(0, wide_filename.as_ptr(), &mut handle); - if result == 0 { - None - } else { - Some(Library(handle)) - } - }, - ) - .map_err(|e| e.unwrap_or(crate::Error::GetModuleHandleExWUnknown)) - }; - - drop(wide_filename); // Drop wide_filename here to ensure it doesn’t get moved and dropped - // inside the closure by mistake. See comment inside the closure. - ret - } - - /// Get a module that is already loaded by the program. - /// - /// This function returns a `Library` corresponding to a module with the given name that is - /// already mapped into the address space of the process. If the module isn't found, an error is - /// returned. - /// - /// If the `filename` does not include a full path and there are multiple different loaded - /// modules corresponding to the `filename`, it is impossible to predict which module handle - /// will be returned. For more information refer to [MSDN]. - /// - /// If the `filename` specifies a library filename without a path and with the extension omitted, - /// the `.dll` extension is implicitly added. This behaviour may be suppressed by appending a - /// trailing `.` to the `filename`. - /// - /// This function will convert filename from its rust utf8 representation - /// to the utf16 w_char representation windows expects. - /// - /// This is equivalent to `GetModuleHandleExW(0, filename, _)`. - /// - /// [MSDN]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexw - pub fn open_already_loaded_utf8(filename: impl AsRef) -> Result { - let utf16: Vec = filename.as_ref().encode_utf16().chain(Some(0)).collect(); - Library::open_already_loaded_raw(utf16.as_slice()) - } - - /// Get a module that is already loaded by the program. - /// - /// This function returns a `Library` corresponding to a module with the given name that is - /// already mapped into the address space of the process. If the module isn't found, an error is - /// returned. - /// - /// If the `filename` does not include a full path and there are multiple different loaded - /// modules corresponding to the `filename`, it is impossible to predict which module handle - /// will be returned. For more information refer to [MSDN]. - /// - /// If the `filename` specifies a library filename without a path and with the extension omitted, - /// the `.dll` extension is implicitly added. This behaviour may be suppressed by appending a - /// trailing `.` to the `filename`. - /// - /// This function will check if the last element in the filename slice is 0. If it is then - /// the pointer of the slice is passed to windows. Otherwise, the function will copy - /// the slice and append a 0 element at the end. To prevent unnecessary copying it is recommended - /// to add the 0 element on the caller side. - /// - /// This is equivalent to `GetModuleHandleExW(0, filename, _)`. - /// - /// [MSDN]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexw - pub fn open_already_loaded_raw(filename: &[u16]) -> Result { - let mut temp_filename_vec = Vec::new(); - let mut raw_filename_ptr = filename.as_ptr(); - - //Poor mans 0 byte detection. - if filename.last() != Some(&0) { - temp_filename_vec.reserve_exact(filename.len() + 1); - temp_filename_vec.extend_from_slice(filename); - temp_filename_vec.push(0); - raw_filename_ptr = temp_filename_vec.as_ptr(); - } - - let ret = unsafe { - let mut handle: HMODULE = 0; - with_get_last_error( - |source| crate::Error::GetModuleHandleExW { source }, - || { - // Make sure no winapi calls as a result of drop happen inside this closure, because - // otherwise that might change the return value of the GetLastError. - let result = GetModuleHandleExW(0, raw_filename_ptr, &mut handle); - if result == 0 { - None - } else { - Some(Library(handle)) - } - }, - ) - .map_err(|e| e.unwrap_or(crate::Error::GetModuleHandleExWUnknown)) - }; - - drop(temp_filename_vec); // Drop temp_filename_vec here to ensure it doesn’t get moved and dropped - ret - } - - /// Find and load a module, additionally adjusting behaviour with flags. - /// - /// See [`Library::new`] for documentation on the handling of the `filename` argument. See the - /// [flag table on MSDN][flags] for information on applicable values for the `flags` argument. - /// - /// Corresponds to `LoadLibraryExW(filename, reserved: NULL, flags)`. - /// - /// [flags]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw#parameters - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library 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. - #[cfg(feature = "std")] - pub unsafe fn load_with_flags>( - filename: P, - flags: LOAD_LIBRARY_FLAGS, - ) -> Result { - let wide_filename: Vec = filename.as_ref().encode_wide().chain(Some(0)).collect(); - let _guard = ErrorModeGuard::new(); - let ret = load_with_flags_raw(wide_filename.as_slice()); - drop(wide_filename); // Drop wide_filename here to ensure it doesn’t get moved and dropped - ret - } - - /// Find and load a module, additionally adjusting behaviour with flags. - /// - /// See [`Library::new`] for documentation on the handling of the `filename` argument. See the - /// [flag table on MSDN][flags] for information on applicable values for the `flags` argument. - /// - /// This function will convert filename from its rust utf8 representation - /// to the utf16 w_char representation windows expects. - /// - /// Corresponds to `LoadLibraryExW(filename, reserved: NULL, flags)`. - /// - /// [flags]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw#parameters - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library 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. - pub unsafe fn load_with_flags_utf8( - filename: impl AsRef, - flags: LOAD_LIBRARY_FLAGS, - ) -> Result { - let utf16: Vec = filename.as_ref().encode_utf16().chain(Some(0)).collect(); - Library::load_with_flags_raw(utf16.as_slice(), flags) + pub fn open_already_loaded(filename: impl AsFilename) -> Result { + filename.windows_filename(|windows_filename| { + unsafe { + let mut handle: HMODULE = 0; + with_get_last_error( + |source| crate::Error::GetModuleHandleExW { source }, + || { + // Make sure no winapi calls as a result of drop happen inside this closure, because + // otherwise that might change the return value of the GetLastError. + let result = GetModuleHandleExW(0, windows_filename, &mut handle); + if result == 0 { + None + } else { + Some(Library(handle)) + } + }, + ) + .map_err(|e| e.unwrap_or(crate::Error::GetModuleHandleExWUnknown)) + } + }) } /// Find and load a module, additionally adjusting behaviour with flags. @@ -362,11 +141,6 @@ impl Library { /// See [`Library::new`] for documentation on the handling of the `filename` argument. See the /// [flag table on MSDN][flags] for information on applicable values for the `flags` argument. /// - /// This function will check if the last element in the filename slice is 0. If it is then - /// the pointer of the slice is passed to windows. Otherwise, the function will copy - /// the slice and append a 0 element at the end. To prevent unnecessary copying it is recommended - /// to add the 0 element on the caller side. - /// /// Corresponds to `LoadLibraryExW(filename, reserved: NULL, flags)`. /// /// [flags]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw#parameters @@ -381,42 +155,27 @@ impl Library { /// 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. - pub unsafe fn load_with_flags_raw( - filename: &[u16], + pub unsafe fn load_with_flags( + filename: impl AsFilename, flags: LOAD_LIBRARY_FLAGS, ) -> Result { - let mut temp_filename_vec = Vec::new(); - let mut raw_filename_ptr = filename.as_ptr(); - - //Poor mans 0 byte detection. - if filename.last() != Some(&0) { - temp_filename_vec.reserve_exact(filename.len() + 1); - temp_filename_vec.extend_from_slice(filename); - temp_filename_vec.push(0); - raw_filename_ptr = temp_filename_vec.as_ptr(); - } - - let _guard = ErrorModeGuard::new(); - - let ret = with_get_last_error( - |source| crate::Error::LoadLibraryExW { source }, - || { - // Make sure no winapi calls as a result of drop happen inside this closure, because - // otherwise that might change the return value of the GetLastError. - let handle = LoadLibraryExW(raw_filename_ptr, 0, flags); - if handle == 0 { - None - } else { - Some(Library(handle)) - } - }, - ) - .map_err(|e| e.unwrap_or(crate::Error::LoadLibraryExWUnknown)); - - //Ensure this lives until the closure returns. - drop(temp_filename_vec); - - ret + filename.windows_filename(|windows_filename| { + let _guard = ErrorModeGuard::new(); + with_get_last_error( + |source| crate::Error::LoadLibraryExW { source }, + || { + // Make sure no winapi calls as a result of drop happen inside this closure, because + // otherwise that might change the return value of the GetLastError. + let handle = LoadLibraryExW(windows_filename, 0, flags); + if handle == 0 { + None + } else { + Some(Library(handle)) + } + }, + ) + .map_err(|e| e.unwrap_or(crate::Error::LoadLibraryExWUnknown)) + }) } /// Attempts to pin the module represented by the current `Library` into memory. @@ -468,24 +227,25 @@ impl Library { /// # Safety /// /// Users of this API must specify the correct type of the function or variable loaded. - pub unsafe fn get(&self, symbol: &[u8]) -> Result, crate::Error> { + pub unsafe fn get(&self, symbol: impl AsSymbolName) -> Result, crate::Error> { ensure_compatible_types::()?; - let symbol = cstr_cow_from_bytes(symbol)?; - with_get_last_error( - |source| crate::Error::GetProcAddress { source }, - || { - let symbol = GetProcAddress(self.0, symbol.as_ptr().cast()); - if symbol.is_none() { - None - } else { - Some(Symbol { - pointer: symbol, - pd: marker::PhantomData, - }) - } - }, - ) - .map_err(|e| e.unwrap_or(crate::Error::GetProcAddressUnknown)) + symbol.symbol_name(|windows_symbol| { + with_get_last_error( + |source| crate::Error::GetProcAddress { source }, + || { + let symbol = GetProcAddress(self.0, windows_symbol.cast()); + if symbol.is_none() { + None + } else { + Some(Symbol { + pointer: symbol, + pd: marker::PhantomData, + }) + } + }, + ) + .map_err(|e| e.unwrap_or(crate::Error::GetProcAddressUnknown)) + }) } /// Get a pointer to a function or static variable by ordinal number. @@ -573,13 +333,13 @@ impl fmt::Debug for Library { let mut buf = mem::MaybeUninit::<[mem::MaybeUninit; 1024]>::uninit().assume_init(); let len = GetModuleFileNameW(self.0, buf[..].as_mut_ptr().cast(), 1024) as usize; if len == 0 { - f.write_str(&format!("Library@{:#x}", self.0)) + f.write_fmt(format_args!("Library@{:#x}", self.0)) } else { - let string: std::os::OsString = std::os::OsString::from_wide( + let string: std::ffi::OsString = std::os::windows::ffi::OsStringExt::from_wide( // FIXME: use Maybeuninit::slice_get_ref when stable &*(&buf[..len] as *const [_] as *const [u16]), ); - f.write_str(&format!("Library@{:#x} from {:?}", self.0, string)) + f.write_fmt(format_args!("Library@{:#x} from {:?}", self.0, string)) } } } @@ -682,27 +442,6 @@ impl Drop for ErrorModeGuard { } } -#[cfg(feature = "std")] -fn with_get_last_error( - wrap: fn(crate::error::WindowsError) -> crate::Error, - closure: F, -) -> Result> -where - F: FnOnce() -> Option, -{ - closure().ok_or_else(|| { - let error = unsafe { GetLastError() }; - if error == 0 { - None - } else { - Some(wrap(crate::error::WindowsError( - std::io::Error::from_raw_os_error(error as i32), - ))) - } - }) -} - -#[cfg(not(feature = "std"))] fn with_get_last_error( wrap: fn(crate::error::WindowsError) -> crate::Error, closure: F, diff --git a/src/safe.rs b/src/safe.rs index 7cda4697e..409eaf96b 100644 --- a/src/safe.rs +++ b/src/safe.rs @@ -5,11 +5,11 @@ use super::os::unix as imp; #[cfg(all(not(libloading_docs), windows))] use super::os::windows as imp; use super::Error; +use as_filename::AsFilename; +use as_symbol_name::AsSymbolName; use core::fmt; use core::marker; use core::ops; -#[cfg(feature = "std")] -use std::ffi::OsStr; /// A loaded dynamic library. #[cfg_attr(libloading_docs, doc(cfg(any(unix, windows))))] @@ -81,22 +81,10 @@ impl Library { /// let _ = Library::new("libsomelib.so.1").unwrap(); /// } /// ``` - #[cfg(feature = "std")] - pub unsafe fn new>(filename: P) -> Result { + pub unsafe fn new(filename: impl AsFilename) -> Result { imp::Library::new(filename).map(From::from) } - /// This function is equivalent to calling new, except that it accepts a rust utf-8 string - /// as the filename. It will either use or convert this string into the appropriate - /// representation that the target requires to load the library. - /// - /// Since this fn does a conversion this function is likely slower than using the `new` function. - /// - /// All other aspects such as safety are identical to the `new` function. - pub unsafe fn new_utf8(filename: impl AsRef) -> Result { - imp::Library::new_utf8(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 @@ -141,7 +129,7 @@ impl Library { /// # Library::new("/path/to/awesome.module").unwrap() /// # }; /// unsafe { - /// let awesome_function: Symbol f64> = + /// let awesome_function: Symbol f64> = /// lib.get(b"awesome_function\0").unwrap(); /// awesome_function(0.42); /// } @@ -157,7 +145,7 @@ impl Library { /// **awesome_variable = 42.0; /// }; /// ``` - pub unsafe fn get(&self, symbol: &[u8]) -> Result, Error> { + pub unsafe fn get(&self, symbol: impl AsSymbolName) -> Result, Error> { self.0.get(symbol).map(|from| Symbol::from_raw(from, self)) } diff --git a/tests/functions.rs b/tests/functions.rs index 5ebff6fed..98675aabb 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -5,6 +5,8 @@ extern crate libloading; use libloading::{Library, Symbol}; use std::os::raw::c_void; +use std::thread; +use std::time::Duration; const TARGET_DIR: Option<&'static str> = option_env!("CARGO_TARGET_DIR"); const TARGET_TMPDIR: Option<&'static str> = option_env!("CARGO_TARGET_TMPDIR"); @@ -28,6 +30,13 @@ fn lib_path_utf8() -> String { fn make_helpers() { static ONCE: ::std::sync::Once = ::std::sync::Once::new(); ONCE.call_once(|| { + if std::env::var_os("PRECOMPILED_TEST_HELPER").is_some() { + //I can't be asked to make rustc work in wine. + //I can call it myself from my linux host and then just move the file here this allows me to skip this. + eprintln!("WILL NOT COMPILE TEST HELPERS, PROGRAM WILL ASSUME THAT {} EXISTS AND WAS EXTERNALLY PRE COMPILED", lib_path().display()); + return; + } + let rustc = std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()); let mut cmd = ::std::process::Command::new(rustc); cmd.arg("src/test_helpers.rs").arg("-o").arg(lib_path()); @@ -43,6 +52,26 @@ fn make_helpers() { }); } +#[cfg(not(windows))] +fn is_wine() -> bool { + false +} + +#[cfg(windows)] +fn is_wine() -> bool { + unsafe { + //This detects wine, the linux runtime for windows programs. + //Wine exposes the symbol wine_get_version in ntdll.dll; naturally, this symbol is absent on actual windows. + let lib = Library::new("ntdll.dll").expect("open library"); + let wine: Result i32>, _> = lib.get("wine_get_version"); + if wine.is_ok() { + return true; + } + } + + false +} + #[test] #[cfg(feature = "std")] fn test_id_u32() { @@ -58,7 +87,7 @@ fn test_id_u32() { fn test_id_u32_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); let f: Symbol u32> = lib.get(b"test_identity_u32\0").unwrap(); assert_eq!(42, f(42)); } @@ -82,7 +111,7 @@ fn test_try_into_ptr() { fn test_try_into_ptr_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); let f: Symbol u32> = lib.get(b"test_identity_u32\0").unwrap(); let ptr: *mut c_void = f.try_as_raw_ptr().unwrap(); assert!(!ptr.is_null()); @@ -128,7 +157,7 @@ fn test_id_struct() { fn test_id_struct_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); let f: Symbol S> = lib.get(b"test_identity_struct\0").unwrap(); assert_eq!( S { @@ -166,7 +195,7 @@ fn test_0_no_0() { fn test_0_no_0_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); let f: Symbol S> = lib.get(b"test_identity_struct\0").unwrap(); let f2: Symbol S> = lib.get(b"test_identity_struct").unwrap(); assert_eq!(*f, *f2); @@ -186,7 +215,7 @@ fn wrong_name_fails() { #[test] fn wrong_name_fails_utf8() { unsafe { - Library::new_utf8("target/this_location_is_definitely_non existent:^~") + Library::new("target/this_location_is_definitely_non existent:^~") .err() .unwrap(); } @@ -207,7 +236,7 @@ fn missing_symbol_fails() { fn missing_symbol_fails_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); lib.get::<*mut ()>(b"test_does_not_exist").err().unwrap(); lib.get::<*mut ()>(b"test_does_not_exist\0").err().unwrap(); } @@ -230,7 +259,7 @@ fn interior_null_fails() { fn interior_null_fails_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); lib.get::<*mut ()>(b"test_does\0_not_exist").err().unwrap(); lib.get::<*mut ()>(b"test\0_does_not_exist\0") .err() @@ -255,7 +284,7 @@ fn test_incompatible_type() { fn test_incompatible_type_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); assert!(match lib.get::<()>(b"test_identity_u32\0") { Err(libloading::Error::IncompatibleSize) => true, _ => false, @@ -286,7 +315,7 @@ fn test_incompatible_type_named_fn_utf8() { l.get::(b"test_identity_u32\0") } unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); assert!(match get(&lib, test_incompatible_type_named_fn_utf8) { Err(libloading::Error::IncompatibleSize) => true, _ => false, @@ -312,7 +341,7 @@ fn test_static_u32() { fn test_static_u32_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); let var: Symbol<*mut u32> = lib.get(b"TEST_STATIC_U32\0").unwrap(); **var = 42; let help: Symbol u32> = @@ -339,7 +368,7 @@ fn test_static_ptr() { fn test_static_ptr_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); let var: Symbol<*mut *mut ()> = lib.get(b"TEST_STATIC_PTR\0").unwrap(); **var = *var as *mut _; let works: Symbol bool> = @@ -358,6 +387,13 @@ fn test_static_ptr_utf8() { #[cfg(not(target_os = "cygwin"))] #[cfg(feature = "std")] fn manual_close_many_times() { + if is_wine() { + // The wine runtime to run windows programs under linux + // will run out of thread local storage indices and fail this test. + eprintln!("DETECTED WINE RUNTIME, WILL SKIP THIS TEST"); + return; + } + make_helpers(); let join_handles: Vec<_> = (0..16) .map(|_| { @@ -382,12 +418,18 @@ fn manual_close_many_times() { // Cygwin returns errors on `close`. #[cfg(not(target_os = "cygwin"))] fn manual_close_many_times_utf8() { + if is_wine() { + //See above for why + eprintln!("DETECTED WINE RUNTIME, WILL SKIP THIS TEST"); + return; + } + make_helpers(); let join_handles: Vec<_> = (0..16) .map(|_| { std::thread::spawn(|| unsafe { for _ in 0..10000 { - let lib = Library::new_utf8(lib_path_utf8()).expect("open library"); + let lib = Library::new(lib_path_utf8()).expect("open library"); let _: Symbol u32> = lib.get(b"test_identity_u32").expect("get fn"); lib.close().expect("close is successful"); @@ -428,7 +470,7 @@ fn library_this_get_utf8() { make_helpers(); // SAFE: functions are never called unsafe { - let _lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let _lib = Library::new(lib_path_utf8()).unwrap(); let this = Library::this(); // Library we loaded in `_lib` (should be RTLD_LOCAL). assert!(this @@ -468,7 +510,7 @@ fn library_this_utf8() { make_helpers(); unsafe { // SAFE: well-known library without initialisers is loaded. - let _lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let _lib = Library::new(lib_path_utf8()).unwrap(); let this = Library::this().expect("this library"); // SAFE: functions are never called. // Library we loaded in `_lib`. @@ -502,7 +544,7 @@ fn works_getlasterror_utf8() { use windows_sys::Win32::Foundation::{GetLastError, SetLastError}; unsafe { - let lib = Library::new_utf8("kernel32.dll").unwrap(); + let lib = Library::new("kernel32.dll").unwrap(); let gle: Symbol u32> = lib.get(b"GetLastError").unwrap(); SetLastError(42); assert_eq!(GetLastError(), gle()) @@ -531,7 +573,7 @@ fn works_getlasterror0_utf8() { use windows_sys::Win32::Foundation::{GetLastError, SetLastError}; unsafe { - let lib = Library::new_utf8("kernel32.dll").unwrap(); + let lib = Library::new("kernel32.dll").unwrap(); let gle: Symbol u32> = lib.get(b"GetLastError\0").unwrap(); SetLastError(42); assert_eq!(GetLastError(), gle()) @@ -556,7 +598,7 @@ fn works_pin_module_utf8() { use libloading::os::windows::Library; unsafe { - let lib = Library::new_utf8("kernel32.dll").unwrap(); + let lib = Library::new("kernel32.dll").unwrap(); lib.pin().unwrap(); } } @@ -582,24 +624,3 @@ fn library_open_already_loaded() { assert!(Library::open_already_loaded(LIBPATH).is_ok()); } } - -#[cfg(windows)] -#[test] -fn library_open_already_loaded_utf8() { - use libloading::os::windows::Library; - - // Present on Windows systems and NOT used by any other tests to prevent races. - const LIBPATH: &str = "Msftedit.dll"; - - // Not loaded yet. - assert!(match Library::open_already_loaded_utf8(LIBPATH) { - Err(libloading::Error::GetModuleHandleExW { .. }) => true, - _ => false, - }); - - unsafe { - let _lib = Library::new_utf8(LIBPATH).unwrap(); - // Loaded now. - assert!(Library::open_already_loaded_utf8(LIBPATH).is_ok()); - } -} diff --git a/tests/library_filename.rs b/tests/library_filename.rs index dd5cd7e09..7029e0a72 100644 --- a/tests/library_filename.rs +++ b/tests/library_filename.rs @@ -1,20 +1,20 @@ extern crate libloading; - #[cfg(feature = "std")] -use libloading::library_filename; -use std::path::Path; +mod test { + use libloading::library_filename; + use std::path::Path; -#[cfg(any(target_os = "windows", target_os = "cygwin"))] -const EXPECTED: &str = "audioengine.dll"; -#[cfg(target_os = "linux")] -const EXPECTED: &str = "libaudioengine.so"; -#[cfg(target_os = "macos")] -const EXPECTED: &str = "libaudioengine.dylib"; + #[cfg(any(target_os = "windows", target_os = "cygwin"))] + const EXPECTED: &str = "audioengine.dll"; + #[cfg(target_os = "linux")] + const EXPECTED: &str = "libaudioengine.so"; + #[cfg(target_os = "macos")] + const EXPECTED: &str = "libaudioengine.dylib"; -#[test] -#[cfg(feature = "std")] -fn test_library_filename() { - let name = "audioengine"; - let resolved = library_filename(name); - assert!(Path::new(&resolved).ends_with(EXPECTED)); + #[test] + fn test_library_filename() { + let name = "audioengine"; + let resolved = library_filename(name); + assert!(Path::new(&resolved).ends_with(EXPECTED)); + } } diff --git a/tests/windows.rs b/tests/windows.rs index 534c9cf9e..13a414502 100644 --- a/tests/windows.rs +++ b/tests/windows.rs @@ -14,29 +14,15 @@ use std::os::raw::c_void; // The DLLs were kindly compiled by WindowsBunny (aka. @retep998). #[cfg(target_arch = "x86")] -#[cfg(feature = "std")] fn load_ordinal_lib() -> Library { unsafe { Library::new("tests/nagisa32.dll").expect("nagisa32.dll") } } #[cfg(target_arch = "x86_64")] -#[cfg(feature = "std")] fn load_ordinal_lib() -> Library { unsafe { Library::new("tests/nagisa64.dll").expect("nagisa64.dll") } } -#[cfg(target_arch = "x86")] -#[cfg(not(feature = "std"))] -fn load_ordinal_lib() -> Library { - unsafe { Library::new_utf8("tests/nagisa32.dll").expect("nagisa32.dll") } -} - -#[cfg(target_arch = "x86_64")] -#[cfg(not(feature = "std"))] -fn load_ordinal_lib() -> Library { - unsafe { Library::new_utf8("tests/nagisa64.dll").expect("nagisa64.dll") } -} - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] #[test] fn test_ordinal() { @@ -71,7 +57,6 @@ fn test_ordinal_missing_fails() { } #[test] -#[cfg(feature = "std")] fn test_new_kernel23() { unsafe { Library::new("kernel23").err().unwrap(); @@ -79,23 +64,8 @@ fn test_new_kernel23() { } #[test] -fn test_new_kernel23_utf8() { - unsafe { - Library::new_utf8("kernel23").err().unwrap(); - } -} - -#[test] -#[cfg(feature = "std")] fn test_new_kernel32_no_ext() { unsafe { Library::new("kernel32").unwrap(); } } - -#[test] -fn test_new_kernel32_no_ext_utf8() { - unsafe { - Library::new_utf8("kernel32").unwrap(); - } -}