diff --git a/.github/workflows/libloading.yml b/.github/workflows/libloading.yml index 817d1df19..8ed2b7dac 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.88.0] os: [ubuntu-latest, windows-latest, macOS-latest] timeout-minutes: 20 steps: @@ -24,15 +24,30 @@ 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.88.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 shell: bash + no-std-build-test: + runs-on: ubuntu-latest + # This will rebuild core and alloc, which will re-mangle symbols in alloc, + # which then will break the pre-compiled std. This will cause the program to fail to link. + # This test does not test anything in an actual no-std environment only that the library compiles on it. + # We test all no-std only functions together with the ordinary functions. + steps: + - uses: actions/checkout@v4 + - name: Add nightly toolchain + run: rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu + - name: Compile without the standard library + run: cargo clean && cargo +nightly build -Zbuild-std=core,alloc --no-default-features + windows-test: runs-on: windows-latest strategy: diff --git a/Cargo.toml b/Cargo.toml index 9276fd6b6..1cd7b7b28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "libloading" # When bumping # * Don’t forget to add an entry to `src/changelog.rs` # * If bumping to an incompatible version, adjust the documentation in `src/lib.rs` -version = "0.8.9" +version = "0.9.0" authors = ["Simonas Kazlauskas "] license = "ISC" repository = "https://github.com/nagisa/rust_libloading/" @@ -12,8 +12,12 @@ 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" -edition = "2015" +rust-version = "1.88.0" +edition = "2021" + +[features] +default = ["std"] +std = [] [target.'cfg(windows)'.dependencies.windows-link] version = "0.2" diff --git a/src/as_filename.rs b/src/as_filename.rs new file mode 100644 index 000000000..3a7d64a57 --- /dev/null +++ b/src/as_filename.rs @@ -0,0 +1,242 @@ +use crate::Error; +use alloc::string::String; + +pub(crate) trait Sealed { + #[cfg(windows)] + #[doc(hidden)] + fn windows_filename( + self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result; + + #[cfg(unix)] + #[doc(hidden)] + fn posix_filename( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result; +} + +/// This trait is implemented for types that can be used as a filename when loading new +/// [`Library`](crate::Library) instances. +/// +/// It is currently sealed and cannot be implemented or its methods called by users of this crate. +#[expect(private_bounds)] +pub trait AsFilename: Sealed {} + +impl AsFilename for &str {} +impl Sealed for &str { + #[cfg(windows)] + fn windows_filename( + self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + let utf16: alloc::vec::Vec = if crate::util::check_null_bytes(self.as_bytes())? { + self.encode_utf16().collect() + } else { + self.encode_utf16().chain(Some(0)).collect() + }; + function(utf16.as_ptr()) + } + + #[cfg(unix)] + fn posix_filename( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + if crate::util::check_null_bytes(self.as_bytes())? { + function(self.as_ptr().cast()) + } else { + let buffer = crate::util::copy_and_push(self.as_bytes(), 0); + function(buffer.as_ptr().cast()) + } + } +} + +impl AsFilename for &String {} +impl Sealed for &String { + #[cfg(windows)] + fn windows_filename( + self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_str().windows_filename(function) + } + + #[cfg(unix)] + fn posix_filename( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + self.as_str().posix_filename(function) + } +} + +impl AsFilename for String {} +impl Sealed for String { + #[cfg(windows)] + fn windows_filename( + self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_str().windows_filename(function) + } + + #[cfg(unix)] + fn posix_filename( + mut self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + if crate::util::check_null_bytes(self.as_bytes())? { + function(self.as_ptr().cast()) + } else { + self.push('\0'); + function(self.as_ptr().cast()) + } + } +} + +#[cfg(feature = "std")] +#[cfg_attr(libloading_docs, doc(cfg(feature = "std")))] +mod std { + use super::{Sealed, AsFilename}; + use crate::Error; + use std::ffi::{OsStr, OsString}; + + impl AsFilename for &OsStr {} + impl Sealed for &OsStr { + #[cfg(windows)] + fn windows_filename( + self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + use std::os::windows::ffi::OsStrExt; + let bytes = self.as_encoded_bytes(); + let utf16: alloc::vec::Vec = if crate::util::check_null_bytes(bytes)? { + self.encode_wide().collect() + } else { + self.encode_wide().chain(Some(0)).collect() + }; + function(utf16.as_ptr()) + } + + #[cfg(unix)] + fn posix_filename( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + let bytes = std::os::unix::ffi::OsStrExt::as_bytes(self); + if crate::util::check_null_bytes(bytes)? { + function(bytes.as_ptr().cast()) + } else { + let buffer = crate::util::copy_and_push(bytes, 0); + function(buffer.as_ptr().cast()) + } + } + } + + impl AsFilename for &OsString {} + impl Sealed for &OsString { + #[cfg(windows)] + fn windows_filename( + self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_os_str().windows_filename(function) + } + + #[cfg(unix)] + fn posix_filename( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + self.as_os_str().posix_filename(function) + } + } + + impl AsFilename for OsString {} + impl Sealed for OsString { + #[cfg(windows)] + fn windows_filename( + self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + // This is the best we can do. + // There is no into_wide for windows. + // The internal repr is wtf-8 and this is different + // from LCPWSTR that we need for the ffi calls. + self.as_os_str().windows_filename(function) + } + + #[cfg(unix)] + fn posix_filename( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + let mut bytes = std::os::unix::ffi::OsStringExt::into_vec(self); + if crate::util::check_null_bytes(&bytes)? { + function(bytes.as_ptr().cast()) + } else { + bytes.push(0); + function(bytes.as_ptr().cast()) + } + } + } + + impl AsFilename for std::path::PathBuf {} + impl Sealed for std::path::PathBuf { + #[cfg(windows)] + fn windows_filename( + self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.into_os_string().windows_filename(function) + } + + #[cfg(unix)] + fn posix_filename( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + self.into_os_string().posix_filename(function) + } + } + + impl AsFilename for &std::path::PathBuf {} + impl Sealed for &std::path::PathBuf { + #[cfg(windows)] + fn windows_filename( + self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_os_str().windows_filename(function) + } + + #[cfg(unix)] + fn posix_filename( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + self.as_os_str().posix_filename(function) + } + } + + impl AsFilename for &std::path::Path {} + impl Sealed for &std::path::Path { + #[cfg(windows)] + fn windows_filename( + self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_os_str().windows_filename(function) + } + + #[cfg(unix)] + fn posix_filename( + self, + function: impl FnOnce(*const core::ffi::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..b87251324 --- /dev/null +++ b/src/as_symbol_name.rs @@ -0,0 +1,118 @@ +use crate::Error; +use alloc::ffi::CString; +use alloc::string::String; +use core::ffi::CStr; + +pub(crate) trait Sealed { + fn symbol_name( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result; +} + +/// This trait is implemented for types [`Library`](crate::Library) implementations can use to look +/// up symbols. +/// +/// It is currently sealed and cannot be implemented or its methods called by users of this crate. +#[expect(private_bounds)] +pub trait AsSymbolName: Sealed {} + +impl AsSymbolName for &str {} +impl Sealed for &str { + fn symbol_name( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + self.as_bytes().symbol_name(function) + } +} + +impl AsSymbolName for &String {} +impl Sealed for &String { + fn symbol_name( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + self.as_str().symbol_name(function) + } +} + +impl AsSymbolName for String {} +impl Sealed for String { + fn symbol_name( + mut self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + if crate::util::check_null_bytes(self.as_bytes())? { + function(self.as_ptr().cast()) + } else { + self.push('\0'); + function(self.as_ptr().cast()) + } + } +} + +impl AsSymbolName for &CStr {} +impl Sealed for &CStr { + fn symbol_name( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + function(self.as_ptr()) + } +} + +impl AsSymbolName for &CString {} +impl Sealed for &CString { + fn symbol_name( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + function(self.as_ptr()) + } +} + +impl AsSymbolName for CString {} +impl Sealed for CString { + fn symbol_name( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + function(self.as_ptr()) + } +} + +impl AsSymbolName for &[u8] {} +impl Sealed for &[u8] { + fn symbol_name( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + if crate::util::check_null_bytes(self)? { + function(self.as_ptr().cast()) + } else { + let copy = crate::util::copy_and_push(self, 0); + function(copy.as_ptr().cast()) + } + } +} + +impl AsSymbolName for [u8; N] {} +impl Sealed for [u8; N] { + fn symbol_name( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + self.as_slice().symbol_name(function) + } +} + +impl AsSymbolName for &[u8; N] {} +impl Sealed for &[u8; N] { + fn symbol_name( + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result { + self.as_slice().symbol_name(function) + } +} diff --git a/src/changelog.rs b/src/changelog.rs index 181915e98..c37aabc2c 100644 --- a/src/changelog.rs +++ b/src/changelog.rs @@ -1,5 +1,34 @@ //! The change log. +/// Release 0.9.0 (2025-11-05) +/// +/// ## Breaking changes +/// +/// * MSRV has been increased to 1.88.0; +/// * This release adds a `std` feature. Most functionality remains available with this feature +/// disabled, but anything involving `OsStr`, `OsString`, `Path` or `PathBuf` is only available +/// with `std` feature enabled. If you are depending on `libloading` with `no-default-features` +/// set to true, you may see compilation errors. Running with `std` feature enabled is still +/// the strongly recommended option. no-std functionality has been contributed in [#184]; +/// * As a result of the change above, functions that previously using `AsRef` to +/// describe the library path now require an implementor of [`AsFilename`](crate::AsFilename) +/// instead. `AsFilename` has been implemented for the most likely types used previously. Please +/// file an issue if you rely on a type that no longer works. +/// +/// [#184]: https://github.com/nagisa/rust_libloading/pull/184 +/// +/// ## Non-breaking changes +/// +/// * Symbol lookups via `Library::get` and related functions can now use a wider variety of string +/// types, including `&CStr`. This, among other things, means that `c"symbol"` literals can be +/// used without incurring any additional null-byte checking overhead or reallocations that were +/// necessary for `&[u8]` argument used previously to support `b"literals"`. `&[u8]` is still +/// accepted. This has been contributed in [#174] and [#184]. +/// +/// [#174]: https://github.com/nagisa/rust_libloading/pull/174 +/// +pub mod r0_9_0 {} + /// Release 0.8.9 (2025-09-17) /// /// ## Non-breaking changes diff --git a/src/error.rs b/src/error.rs index 43cf320b1..cfaa1f360 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,26 +1,50 @@ -use std::ffi::{CStr, CString}; +use alloc::ffi::CString; +use core::ffi::CStr; /// A `dlerror` error. -pub struct DlDescription(pub(crate) CString); +pub struct DlError(pub(crate) CString); -impl std::fmt::Debug for DlDescription { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Debug::fmt(&self.0, f) +impl core::error::Error for DlError {} + +impl core::fmt::Debug for DlError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.0, f) + } +} + +impl core::fmt::Display for DlError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(&self.0.to_string_lossy()) } } -impl From<&CStr> for DlDescription { +impl From<&CStr> for DlError { fn from(value: &CStr) -> Self { Self(value.into()) } } /// A Windows API error. -pub struct WindowsError(pub(crate) std::io::Error); +#[derive(Copy, Clone)] +pub struct WindowsError(pub(crate) i32); + +impl core::error::Error for WindowsError { } + +impl core::fmt::Debug for WindowsError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.0, f) + } +} -impl std::fmt::Debug for WindowsError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Debug::fmt(&self.0, f) +impl core::fmt::Display for WindowsError { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let error = std::io::Error::from_raw_os_error(self.0); + core::fmt::Display::fmt(&error, f) + } + #[cfg(not(feature = "std"))] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_fmt(format_args!("OS error {}", self.0)) } } @@ -31,21 +55,21 @@ pub enum Error { /// The `dlopen` call failed. DlOpen { /// The source error. - desc: DlDescription, + source: DlError, }, /// The `dlopen` call failed and system did not report an error. DlOpenUnknown, /// The `dlsym` call failed. DlSym { /// The source error. - desc: DlDescription, + source: DlError, }, /// The `dlsym` call failed and system did not report an error. DlSymUnknown, /// The `dlclose` call failed. DlClose { /// The source error. - desc: DlDescription, + source: DlError, }, /// The `dlclose` call failed and system did not report an error. DlCloseUnknown, @@ -79,42 +103,41 @@ pub enum Error { FreeLibraryUnknown, /// The requested type cannot possibly work. IncompatibleSize, - /// Could not create a new CString. - CreateCString { - /// The source error. - source: std::ffi::NulError, - }, - /// Could not create a new CString from bytes with trailing null. - CreateCStringWithTrailing { - /// The source error. - source: std::ffi::FromBytesWithNulError, - }, + /// Input symbol of filename contains interior 0/null elements. + InteriorZeroElements, } -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { +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, + match self { + LoadLibraryExW { source } + | GetModuleHandleExW { source } + | GetProcAddress { source } + | FreeLibrary { source } => Some(source), + DlOpen { source } | DlSym { source } | DlClose { source } => Some(source), + DlOpenUnknown + | DlSymUnknown + | DlCloseUnknown + | LoadLibraryExWUnknown + | GetModuleHandleExWUnknown + | GetProcAddressUnknown + | FreeLibraryUnknown + | IncompatibleSize + | InteriorZeroElements => None, } } } -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { use Error::*; match *self { - DlOpen { ref desc } => write!(f, "{}", desc.0.to_string_lossy()), + DlOpen { .. } => write!(f, "dlopen failed"), DlOpenUnknown => write!(f, "dlopen failed, but system did not report the error"), - DlSym { ref desc } => write!(f, "{}", desc.0.to_string_lossy()), + DlSym { .. } => write!(f, "dlsym failed"), DlSymUnknown => write!(f, "dlsym failed, but system did not report the error"), - DlClose { ref desc } => write!(f, "{}", desc.0.to_string_lossy()), + DlClose { .. } => write!(f, "dlclose failed"), DlCloseUnknown => write!(f, "dlclose failed, but system did not report the error"), LoadLibraryExW { .. } => write!(f, "LoadLibraryExW failed"), LoadLibraryExWUnknown => write!( @@ -135,11 +158,7 @@ impl std::fmt::Display for Error { FreeLibraryUnknown => { 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" - ), + InteriorZeroElements => write!(f, "interior zero element in parameter"), IncompatibleSize => write!(f, "requested type cannot possibly work"), } } diff --git a/src/lib.rs b/src/lib.rs index d1e2ced62..75637dc8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ //! contents, but little is done to hide the differences in behaviour between platforms. //! The API documentation strives to document such differences as much as possible. //! -//! Platform-specific APIs are also available in the [`os`](crate::os) module. These APIs are more +//! Platform-specific APIs are also available in the [`os`] module. These APIs are more //! flexible, but less safe. //! //! # Installation @@ -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()) //! } //! } @@ -40,6 +40,17 @@ deny(missing_docs, clippy::all, unreachable_pub, unused) )] #![cfg_attr(libloading_docs, feature(doc_cfg))] +#![no_std] + +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; @@ -49,10 +60,9 @@ mod safe; mod util; pub use self::error::Error; + #[cfg(any(unix, windows, libloading_docs))] pub use self::safe::{Library, Symbol}; -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. /// @@ -71,9 +81,14 @@ use std::ffi::{OsStr, OsString}; /// Library::new(library_filename("LLVM")) /// }; /// ``` -pub fn library_filename>(name: S) -> OsString { +#[cfg(feature = "std")] +#[cfg_attr(libloading_docs, doc(cfg(feature = "std")))] +pub fn library_filename>(name: S) -> std::ffi::OsString { + use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; + let name = name.as_ref(); - let mut string = OsString::with_capacity(name.len() + DLL_PREFIX.len() + DLL_SUFFIX.len()); + let mut string = + std::ffi::OsString::with_capacity(name.len() + DLL_PREFIX.len() + DLL_SUFFIX.len()); string.push(DLL_PREFIX); string.push(name); string.push(DLL_SUFFIX); diff --git a/src/os/unix/consts.rs b/src/os/unix/consts.rs index 4ae00592d..46fd1a1c2 100644 --- a/src/os/unix/consts.rs +++ b/src/os/unix/consts.rs @@ -1,4 +1,4 @@ -use std::os::raw::c_int; +use core::ffi::c_int; /// Perform lazy binding. /// @@ -54,8 +54,7 @@ mod posix { #[cfg(any(not(libloading_docs), unix))] mod posix { - extern crate cfg_if; - use self::cfg_if::cfg_if; + use cfg_if::cfg_if; use super::c_int; cfg_if! { if #[cfg(target_os = "haiku")] { diff --git a/src/os/unix/mod.rs b/src/os/unix/mod.rs index 0e42c50d9..d727c622f 100644 --- a/src/os/unix/mod.rs +++ b/src/os/unix/mod.rs @@ -1,18 +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 { - pub(super) use std::os::unix::ffi::OsStrExt; -} - 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}; +use crate::as_filename::AsFilename; +use crate::as_symbol_name::AsSymbolName; +use crate::util::ensure_compatible_types; +use core::ffi::CStr; +use core::ptr::null; +use core::{fmt, marker, mem, ptr}; mod consts; @@ -88,7 +80,7 @@ where /// A platform-specific counterpart of the cross-platform [`Library`](crate::Library). pub struct Library { - handle: *mut raw::c_void, + handle: *mut core::ffi::c_void, } unsafe impl Send for Library {} @@ -131,7 +123,7 @@ impl Library { /// 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>(filename: P) -> Result { + pub unsafe fn new(filename: impl AsFilename) -> Result { Library::open(Some(filename), RTLD_LAZY | RTLD_LOCAL) } @@ -156,7 +148,7 @@ impl Library { unsafe { // SAFE: this does not load any new shared library images, no danger in it executing // initialiser routines. - Library::open(None::<&OsStr>, RTLD_LAZY | RTLD_LOCAL).expect("this should never fail") + Library::open_char_ptr(null(), RTLD_LAZY | RTLD_LOCAL).expect("this should never fail") } } @@ -177,68 +169,82 @@ 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 open

(filename: Option

, flags: raw::c_int) -> Result + pub unsafe fn open

( + filename: Option

, + flags: core::ffi::c_int, + ) -> Result where - P: AsRef, + P: AsFilename, { - 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); }; + 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. + /// caller must ensure that the string is actually 0 terminated. + unsafe fn open_char_ptr( + filename: *const core::ffi::c_char, + flags: core::ffi::c_int, + ) -> Result { with_dlerror( move || { - let result = dlopen( - match filename { - None => ptr::null(), - Some(ref f) => f.as_ptr(), - }, - flags, - ); + let result = dlopen(filename, flags); + // ensure filename lives until dlopen completes - drop(filename); if result.is_null() { None } else { Some(Library { handle: result }) } }, - |desc| crate::Error::DlOpen { desc: desc.into() }, + |desc| crate::Error::DlOpen { + source: desc.into(), + }, ) .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)?; + ensure_compatible_types::()?; // `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 { + source: 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. @@ -265,7 +271,9 @@ 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> { + #[cfg_attr(libloading_docs, allow(unused_extern_crates))] + #[cfg(libloading_docs)] extern crate cfg_if; cfg_if::cfg_if! { // These targets are known to have MT-safe `dlerror`. @@ -309,7 +317,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(), @@ -322,7 +333,7 @@ impl Library { /// /// The handle returned by this function shall be usable with APIs which accept handles /// as returned by `dlopen`. - pub fn into_raw(self) -> *mut raw::c_void { + pub fn into_raw(self) -> *mut core::ffi::c_void { let handle = self.handle; mem::forget(self); handle @@ -335,7 +346,7 @@ impl Library { /// The pointer shall be a result of a successful call of the `dlopen`-family of functions or a /// pointer previously returned by `Library::into_raw` call. It must be valid to call `dlclose` /// with this pointer as an argument. - pub unsafe fn from_raw(handle: *mut raw::c_void) -> Library { + pub unsafe fn from_raw(handle: *mut core::ffi::c_void) -> Library { Library { handle } } @@ -358,13 +369,15 @@ impl Library { None } }, - |desc| crate::Error::DlClose { desc: desc.into() }, + |desc| crate::Error::DlClose { + source: desc.into(), + }, ) .map_err(|e| e.unwrap_or(crate::Error::DlCloseUnknown)); // While the library is not free'd yet in case of an error, there is no reason to try // dropping it again, because all that will do is try calling `dlclose` again. only // this time it would ignore the return result, which we already seen failing… - std::mem::forget(self); + mem::forget(self); result } } @@ -379,7 +392,7 @@ impl Drop for Library { impl fmt::Debug for Library { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(&format!("Library@{:p}", self.handle)) + f.write_fmt(format_args!("Library@{:p}", self.handle)) } } @@ -388,19 +401,19 @@ impl fmt::Debug for Library { /// A major difference compared to the cross-platform `Symbol` is that this does not ensure that the /// `Symbol` does not outlive the `Library` it comes from. pub struct Symbol { - pointer: *mut raw::c_void, + pointer: *mut core::ffi::c_void, pd: marker::PhantomData, } impl Symbol { /// Convert the loaded `Symbol` into a raw pointer. - pub fn into_raw(self) -> *mut raw::c_void { + pub fn into_raw(self) -> *mut core::ffi::c_void { self.pointer } /// Convert the loaded `Symbol` into a raw pointer. /// For unix this does the same as into_raw. - pub fn as_raw_ptr(self) -> *mut raw::c_void { + pub fn as_raw_ptr(self) -> *mut core::ffi::c_void { self.pointer } } @@ -428,7 +441,7 @@ impl Clone for Symbol { } } -impl ::std::ops::Deref for Symbol { +impl core::ops::Deref for Symbol { type Target = T; fn deref(&self) -> &T { unsafe { @@ -445,13 +458,13 @@ impl fmt::Debug for Symbol { if dladdr(self.pointer, info.as_mut_ptr()) != 0 { let info = info.assume_init(); if info.dli_sname.is_null() { - f.write_str(&format!( + f.write_fmt(format_args!( "Symbol@{:p} from {:?}", self.pointer, CStr::from_ptr(info.dli_fname) )) } else { - f.write_str(&format!( + f.write_fmt(format_args!( "Symbol {:?}@{:p} from {:?}", CStr::from_ptr(info.dli_sname), self.pointer, @@ -459,7 +472,7 @@ impl fmt::Debug for Symbol { )) } } else { - f.write_str(&format!("Symbol@{:p}", self.pointer)) + f.write_fmt(format_args!("Symbol@{:p}", self.pointer)) } } } @@ -469,17 +482,23 @@ impl fmt::Debug for Symbol { #[cfg_attr(any(target_os = "linux", target_os = "android"), link(name = "dl"))] #[cfg_attr(any(target_os = "freebsd", target_os = "dragonfly"), link(name = "c"))] extern "C" { - fn dlopen(filename: *const raw::c_char, flags: raw::c_int) -> *mut raw::c_void; - fn dlclose(handle: *mut raw::c_void) -> raw::c_int; - fn dlsym(handle: *mut raw::c_void, symbol: *const raw::c_char) -> *mut raw::c_void; - fn dlerror() -> *mut raw::c_char; - fn dladdr(addr: *mut raw::c_void, info: *mut DlInfo) -> raw::c_int; + fn dlopen( + filename: *const core::ffi::c_char, + flags: core::ffi::c_int, + ) -> *mut core::ffi::c_void; + fn dlclose(handle: *mut core::ffi::c_void) -> core::ffi::c_int; + fn dlsym( + handle: *mut core::ffi::c_void, + symbol: *const core::ffi::c_char, + ) -> *mut core::ffi::c_void; + fn dlerror() -> *mut core::ffi::c_char; + fn dladdr(addr: *mut core::ffi::c_void, info: *mut DlInfo) -> core::ffi::c_int; } #[repr(C)] struct DlInfo { - dli_fname: *const raw::c_char, - dli_fbase: *mut raw::c_void, - dli_sname: *const raw::c_char, - dli_saddr: *mut raw::c_void, + dli_fname: *const core::ffi::c_char, + dli_fbase: *mut core::ffi::c_void, + dli_sname: *const core::ffi::c_char, + dli_saddr: *mut core::ffi::c_void, } diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs index fa6713138..9bf652027 100644 --- a/src/os/windows/mod.rs +++ b/src/os/windows/mod.rs @@ -5,7 +5,6 @@ mod windows_imports {} #[cfg(any(not(libloading_docs), windows))] mod windows_imports { use super::{BOOL, DWORD, FARPROC, HANDLE, HMODULE}; - 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); @@ -16,10 +15,10 @@ mod windows_imports { } use self::windows_imports::*; -use std::ffi::{OsStr, OsString}; -use std::os::raw; -use std::{fmt, io, marker, mem, ptr}; -use util::{cstr_cow_from_bytes, ensure_compatible_types}; +use crate::as_filename::AsFilename; +use crate::as_symbol_name::AsSymbolName; +use crate::util::ensure_compatible_types; +use core::{fmt, marker, mem, ptr}; /// The platform-specific counterpart of the cross-platform [`Library`](crate::Library). pub struct Library(HMODULE); @@ -67,7 +66,7 @@ impl Library { /// 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>(filename: P) -> Result { + pub unsafe fn new(filename: impl AsFilename) -> Result { Library::load_with_flags(filename, 0) } @@ -86,7 +85,7 @@ impl Library { with_get_last_error( |source| crate::Error::GetModuleHandleExW { source }, || { - let result = GetModuleHandleExW(0, std::ptr::null_mut(), &mut handle); + let result = GetModuleHandleExW(0, ptr::null_mut(), &mut handle); if result == 0 { None } else { @@ -115,30 +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 - 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 + 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. @@ -160,30 +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>( - filename: P, + pub unsafe fn load_with_flags( + filename: impl AsFilename, flags: LOAD_LIBRARY_FLAGS, ) -> Result { - let wide_filename: Vec = filename.as_ref().encode_wide().chain(Some(0)).collect(); - 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(wide_filename.as_ptr(), 0, flags); - if handle == 0 { - None - } else { - Some(Library(handle)) - } - }, - ) - .map_err(|e| e.unwrap_or(crate::Error::LoadLibraryExWUnknown)); - 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 + 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. @@ -235,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. @@ -319,7 +312,7 @@ impl Library { // While the library is not free'd yet in case of an error, there is no reason to try // dropping it again, because all that will do is try calling `FreeLibrary` again. only // this time it would ignore the return result, which we already seen failing... - std::mem::forget(self); + mem::forget(self); result } } @@ -333,22 +326,28 @@ impl Drop for Library { } impl fmt::Debug for Library { + #[cfg(feature = "std")] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { unsafe { // FIXME: use Maybeuninit::uninit_array when stable 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: OsString = 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)) } } } + + #[cfg(not(feature = "std"))] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("Library@{:#x}", self.0)) + } } /// A symbol from a library. @@ -367,10 +366,10 @@ impl Symbol { } /// Convert the loaded `Symbol` into a raw pointer. - pub fn as_raw_ptr(self) -> *mut raw::c_void { + pub fn as_raw_ptr(self) -> *mut core::ffi::c_void { self.pointer - .map(|raw| raw as *mut raw::c_void) - .unwrap_or(std::ptr::null_mut()) + .map(|raw| raw as *mut core::ffi::c_void) + .unwrap_or(ptr::null_mut()) } } @@ -397,7 +396,7 @@ impl Clone for Symbol { } } -impl ::std::ops::Deref for Symbol { +impl core::ops::Deref for Symbol { type Target = T; fn deref(&self) -> &T { unsafe { &*((&self.pointer) as *const FARPROC as *const T) } @@ -408,7 +407,7 @@ impl fmt::Debug for Symbol { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.pointer { None => f.write_str("Symbol@0x0"), - Some(ptr) => f.write_str(&format!("Symbol@{:p}", ptr as *const ())), + Some(ptr) => f.write_fmt(format_args!("Symbol@{:p}", ptr as *const ())), } } } @@ -455,9 +454,7 @@ where if error == 0 { None } else { - Some(wrap(crate::error::WindowsError( - io::Error::from_raw_os_error(error as i32), - ))) + Some(wrap(crate::error::WindowsError(error as i32))) } }) } diff --git a/src/safe.rs b/src/safe.rs index e217ee394..f2b5c3837 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 std::ffi::OsStr; -use std::fmt; -use std::marker; -use std::ops; -use std::os::raw; +use crate::as_filename::AsFilename; +use crate::as_symbol_name::AsSymbolName; +use core::fmt; +use core::marker; +use core::ops; /// A loaded dynamic library. #[cfg_attr(libloading_docs, doc(cfg(any(unix, windows))))] @@ -81,7 +81,7 @@ impl Library { /// let _ = Library::new("libsomelib.so.1").unwrap(); /// } /// ``` - pub unsafe fn new>(filename: P) -> Result { + pub unsafe fn new(filename: impl AsFilename) -> Result { imp::Library::new(filename).map(From::from) } @@ -129,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); /// } @@ -145,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)) } @@ -259,7 +259,7 @@ impl<'lib, T> Symbol<'lib, T> { /// 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. - pub unsafe fn try_as_raw_ptr(self) -> Option<*mut raw::c_void> { + pub unsafe fn try_as_raw_ptr(self) -> Option<*mut core::ffi::c_void> { Some( unsafe { // SAFE: the calling function has the same soundness invariants as this callee. diff --git a/src/util.rs b/src/util.rs index 599e6c254..03d401f94 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,34 +1,36 @@ -use std::borrow::Cow; -use std::ffi::{CStr, CString}; -use std::os::raw; - use crate::Error; -/// Checks for the last byte and avoids allocating if it is zero. -/// -/// Non-last null bytes still result in an error. -pub(crate) fn cstr_cow_from_bytes(slice: &[u8]) -> Result, Error> { - static ZERO: raw::c_char = 0; - Ok(match slice.last() { - // Slice out of 0 elements - None => unsafe { Cow::Borrowed(CStr::from_ptr(&ZERO)) }, - // Slice with trailing 0 - Some(&0) => Cow::Borrowed( - CStr::from_bytes_with_nul(slice) - .map_err(|source| Error::CreateCStringWithTrailing { source })?, - ), - // Slice with no trailing 0 - Some(_) => { - Cow::Owned(CString::new(slice).map_err(|source| Error::CreateCString { source })?) - } - }) -} - #[inline] pub(crate) fn ensure_compatible_types() -> Result<(), Error> { - if ::std::mem::size_of::() != ::std::mem::size_of::() { + if size_of::() != size_of::() { Err(Error::IncompatibleSize) } else { Ok(()) } } + +/// Verify that the input has no interior nulls and check whether the last byte is a null. +/// +/// If any `b'\0'` at positions other than the last byte are found, an error is returned. Otherwise +/// `true` will be returned only if the last byte is `b'\0'`. +pub(crate) fn check_null_bytes(data: &[u8]) -> Result { + if let [rest @ .., last] = data { + if rest.contains(&0) { + Err(Error::InteriorZeroElements) + } else { + Ok(*last == 0) + } + } else { + Ok(false) + } +} + +/// This function copies the slice into a vec and appends an element to its end. +/// The vec is allocated with reserve_exact. +pub(crate) fn copy_and_push(data: &[T], to_push: T) -> alloc::vec::Vec { + let mut copy = alloc::vec::Vec::new(); + copy.reserve_exact(data.len() + 1); + copy.extend_from_slice(data); + copy.push(to_push); + copy +} diff --git a/tests/constants.rs b/tests/constants.rs index 6ae5a8460..b367c50cb 100644 --- a/tests/constants.rs +++ b/tests/constants.rs @@ -1,10 +1,6 @@ -extern crate libc; -extern crate libloading; -extern crate static_assertions; - #[cfg(all(test, unix))] mod unix { - use super::static_assertions::const_assert_eq; + use static_assertions::const_assert_eq; const_assert_eq!(libloading::os::unix::RTLD_LOCAL, libc::RTLD_LOCAL); const_assert_eq!(libloading::os::unix::RTLD_GLOBAL, libc::RTLD_GLOBAL); diff --git a/tests/functions.rs b/tests/functions.rs index dc6b316e7..6f4fc2755 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -1,7 +1,3 @@ -#[cfg(windows)] -extern crate windows_sys; - -extern crate libloading; use libloading::{Library, Symbol}; use std::os::raw::c_void; @@ -20,6 +16,13 @@ fn lib_path() -> std::path::PathBuf { 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()); @@ -35,7 +38,28 @@ 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() { make_helpers(); unsafe { @@ -45,7 +69,126 @@ fn test_id_u32() { } } +#[cfg(feature = "std")] +#[test] +fn test_as_filename_osstring() { + as_filename_test::(lib_path().into_os_string(), "potato\0beetroot".into()); +} + +#[cfg(feature = "std")] +#[test] +fn test_as_filename_osstr() { + let with_nulls = std::ffi::OsStr::new("hazelnut\0peanut"); + as_filename_test::<&std::ffi::OsStr>(lib_path().as_os_str(), with_nulls); +} + +#[cfg(feature = "std")] +#[test] +fn test_as_filename_pathbuf() { + as_filename_test::(lib_path(), "orange\0grape".into()); +} + +#[cfg(feature = "std")] +#[test] +fn test_as_filename_path() { + as_filename_test::<&std::path::Path>(&*lib_path(), std::path::Path::new("peach\0mango")); +} + +#[cfg(feature = "std")] +#[test] +fn test_as_filename_str() { + let path = lib_path(); + if let Some(p) = path.to_str() { + as_filename_test::<&str>(p, "kiwi\0peach\0"); + } +} + +#[cfg(feature = "std")] #[test] +fn test_as_filename_string() { + let path = lib_path(); + if let Some(p) = path.to_str() { + as_filename_test::(p.to_string(), "apple\0banana".to_string()); + } +} + +#[cfg(feature = "std")] +fn as_filename_test(path: T, with_interior_nulls: T) { + make_helpers(); + unsafe { + assert!(matches!( + Library::new(with_interior_nulls).unwrap_err(), + libloading::Error::InteriorZeroElements, + )); + let lib = Library::new(path).unwrap(); + let f: Symbol u32> = lib.get(b"test_identity_u32\0").unwrap(); + assert_eq!(42, f(42)); + } +} + +#[cfg(feature = "std")] +#[test] +fn test_as_symbol_name_string() { + as_symbol_name_test::("test_identity_u32".to_string()); + as_symbol_name_test::("test_identity_u32\0".to_string()); + as_symbol_name_test_interior_nulls::("test_iden\0tity_u32".to_string()); +} + +#[cfg(feature = "std")] +#[test] +fn test_as_symbol_name_str() { + as_symbol_name_test::<&str>("test_identity_u32"); + as_symbol_name_test::<&str>("test_identity_u32\0"); + as_symbol_name_test_interior_nulls::<&str>("test_iden\0tity_u32\0"); +} + +#[cfg(feature = "std")] +#[test] +fn test_as_symbol_name_cstr() { + as_symbol_name_test::<&std::ffi::CStr>(c"test_identity_u32"); +} + +#[cfg(feature = "std")] +#[test] +fn test_as_symbol_name_cstring() { + as_symbol_name_test::(c"test_identity_u32".to_owned()); +} + +#[cfg(feature = "std")] +#[test] +fn test_as_symbol_name_bytes() { + as_symbol_name_test::<&[u8]>(b"test_identity_u32"); + as_symbol_name_test::<&[u8]>(b"test_identity_u32\0"); + as_symbol_name_test::<&[u8; 18]>(b"test_identity_u32\0"); + as_symbol_name_test_interior_nulls::<&[u8]>(b"test_identity\0_u32"); + as_symbol_name_test_interior_nulls::<&[u8]>(b"test\0_identity_u32"); + as_symbol_name_test_interior_nulls::<&[u8; 19]>(b"test_iden\0tity_u32\0"); +} + +#[cfg(feature = "std")] +fn as_symbol_name_test(symbol: T) { + make_helpers(); + unsafe { + let lib = Library::new(lib_path()).unwrap(); + let f: Symbol u32> = lib.get(symbol).unwrap(); + assert_eq!(42, f(42)); + } +} + +#[cfg(feature = "std")] +fn as_symbol_name_test_interior_nulls(symbol: T) { + make_helpers(); + unsafe { + let lib = Library::new(lib_path()).unwrap(); + assert!(matches!( + lib.get:: u32>(symbol), + Err(libloading::Error::InteriorZeroElements), + )); + } +} + +#[test] +#[cfg(feature = "std")] fn test_try_into_ptr() { make_helpers(); unsafe { @@ -68,6 +211,7 @@ struct S { } #[test] +#[cfg(feature = "std")] fn test_id_struct() { make_helpers(); unsafe { @@ -91,12 +235,15 @@ fn test_id_struct() { } #[test] +#[allow(unpredictable_function_pointer_comparisons)] +#[cfg(feature = "std")] fn test_0_no_0() { make_helpers(); unsafe { let lib = Library::new(lib_path()).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); } } @@ -111,6 +258,7 @@ fn wrong_name_fails() { } #[test] +#[cfg(feature = "std")] fn missing_symbol_fails() { make_helpers(); unsafe { @@ -121,18 +269,22 @@ fn missing_symbol_fails() { } #[test] +#[cfg(feature = "std")] fn interior_null_fails() { make_helpers(); unsafe { let lib = Library::new(lib_path()).unwrap(); lib.get::<*mut ()>(b"test_does\0_not_exist").err().unwrap(); + lib.get::<*mut ()>("test_does\0_not_exist").err().unwrap(); lib.get::<*mut ()>(b"test\0_does_not_exist\0") .err() .unwrap(); + lib.get::<*mut ()>("test_does\0_not_exist\0").err().unwrap(); } } #[test] +#[cfg(feature = "std")] fn test_incompatible_type() { make_helpers(); unsafe { @@ -145,6 +297,7 @@ fn test_incompatible_type() { } #[test] +#[cfg(feature = "std")] fn test_incompatible_type_named_fn() { make_helpers(); unsafe fn get<'a, T>(l: &'a Library, _: T) -> Result, libloading::Error> { @@ -160,6 +313,7 @@ fn test_incompatible_type_named_fn() { } #[test] +#[cfg(feature = "std")] fn test_static_u32() { make_helpers(); unsafe { @@ -173,6 +327,7 @@ fn test_static_u32() { } #[test] +#[cfg(feature = "std")] fn test_static_ptr() { make_helpers(); unsafe { @@ -193,7 +348,15 @@ fn test_static_ptr() { #[cfg(not(all(target_arch = "x86", target_os = "windows", target_env = "gnu")))] // Cygwin returns errors on `close`. #[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(|_| { @@ -213,6 +376,7 @@ fn manual_close_many_times() { } #[cfg(unix)] +#[cfg(feature = "std")] #[test] fn library_this_get() { use libloading::os::unix::Library; @@ -233,6 +397,7 @@ fn library_this_get() { } #[cfg(windows)] +#[cfg(feature = "std")] #[test] fn library_this() { use libloading::os::windows::Library; diff --git a/tests/library_filename.rs b/tests/library_filename.rs index 4642ece08..7029e0a72 100644 --- a/tests/library_filename.rs +++ b/tests/library_filename.rs @@ -1,17 +1,20 @@ extern crate libloading; -use libloading::library_filename; -use std::path::Path; +#[cfg(feature = "std")] +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] -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)); + } }