diff --git a/.github/workflows/libloading.yml b/.github/workflows/libloading.yml index 311271693..59e2197b6 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.63.0] + rust_toolchain: [nightly, stable, 1.71.0] os: [ubuntu-latest, windows-latest, macOS-latest] timeout-minutes: 20 steps: @@ -24,7 +24,7 @@ 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.63.0' }} + if: ${{ matrix.rust_toolchain == '1.71.0' }} - run: cargo clippy - run: cargo test -- --nocapture - run: cargo test --release -- --nocapture diff --git a/Cargo.toml b/Cargo.toml index 3c087483c..9276fd6b6 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.8" +version = "0.8.9" authors = ["Simonas Kazlauskas "] license = "ISC" repository = "https://github.com/nagisa/rust_libloading/" @@ -12,14 +12,14 @@ 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.56.0" +rust-version = "1.71.0" edition = "2015" -[target.'cfg(windows)'.dependencies.windows-targets] -version = ">=0.48, <0.54" +[target.'cfg(windows)'.dependencies.windows-link] +version = "0.2" [target.'cfg(windows)'.dev-dependencies.windows-sys] -version = ">=0.52,<0.60" +version = "0.61" features = ["Win32_Foundation"] [target.'cfg(unix)'.dependencies.cfg-if] diff --git a/src/changelog.rs b/src/changelog.rs index 58c3e2e90..181915e98 100644 --- a/src/changelog.rs +++ b/src/changelog.rs @@ -1,5 +1,12 @@ //! The change log. +/// Release 0.8.9 (2025-09-17) +/// +/// ## Non-breaking changes +/// +/// Migrate from windows-targets to windows-link for linking Windows API functions. +pub mod r0_8_9 {} + /// Release 0.8.8 (2025-05-27) /// /// ## Non-breaking changes diff --git a/src/error.rs b/src/error.rs index 18f3c4d03..43cf320b1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -use std::ffi::{CString, CStr}; +use std::ffi::{CStr, CString}; /// A `dlerror` error. pub struct DlDescription(pub(crate) CString); @@ -31,49 +31,49 @@ pub enum Error { /// The `dlopen` call failed. DlOpen { /// The source error. - desc: DlDescription + desc: DlDescription, }, /// The `dlopen` call failed and system did not report an error. DlOpenUnknown, /// The `dlsym` call failed. DlSym { /// The source error. - desc: DlDescription + desc: DlDescription, }, /// The `dlsym` call failed and system did not report an error. DlSymUnknown, /// The `dlclose` call failed. DlClose { /// The source error. - desc: DlDescription + desc: DlDescription, }, /// The `dlclose` call failed and system did not report an error. DlCloseUnknown, /// The `LoadLibraryW` call failed. LoadLibraryExW { /// The source error. - source: WindowsError + source: WindowsError, }, /// The `LoadLibraryW` call failed and system did not report an error. LoadLibraryExWUnknown, /// The `GetModuleHandleExW` call failed. GetModuleHandleExW { /// The source error. - source: WindowsError + source: WindowsError, }, /// The `GetModuleHandleExW` call failed and system did not report an error. GetModuleHandleExWUnknown, /// The `GetProcAddress` call failed. GetProcAddress { /// The source error. - source: WindowsError + source: WindowsError, }, /// The `GetProcAddressUnknown` call failed and system did not report an error. GetProcAddressUnknown, /// The `FreeLibrary` call failed. FreeLibrary { /// The source error. - source: WindowsError + source: WindowsError, }, /// The `FreeLibrary` call failed and system did not report an error. FreeLibraryUnknown, @@ -82,12 +82,12 @@ pub enum Error { /// Could not create a new CString. CreateCString { /// The source error. - source: std::ffi::NulError + source: std::ffi::NulError, }, /// Could not create a new CString from bytes with trailing null. CreateCStringWithTrailing { /// The source error. - source: std::ffi::FromBytesWithNulError + source: std::ffi::FromBytesWithNulError, }, } @@ -117,20 +117,29 @@ impl std::fmt::Display for Error { DlClose { ref desc } => write!(f, "{}", desc.0.to_string_lossy()), DlCloseUnknown => write!(f, "dlclose failed, but system did not report the error"), LoadLibraryExW { .. } => write!(f, "LoadLibraryExW failed"), - LoadLibraryExWUnknown => - write!(f, "LoadLibraryExW failed, but system did not report the error"), + LoadLibraryExWUnknown => write!( + f, + "LoadLibraryExW failed, but system did not report the error" + ), GetModuleHandleExW { .. } => write!(f, "GetModuleHandleExW failed"), - GetModuleHandleExWUnknown => - write!(f, "GetModuleHandleExWUnknown failed, but system did not report the error"), + GetModuleHandleExWUnknown => write!( + f, + "GetModuleHandleExWUnknown failed, but system did not report the error" + ), GetProcAddress { .. } => write!(f, "GetProcAddress failed"), - GetProcAddressUnknown => - write!(f, "GetProcAddress failed, but system did not report the error"), + GetProcAddressUnknown => write!( + f, + "GetProcAddress failed, but system did not report the error" + ), FreeLibrary { .. } => write!(f, "FreeLibrary failed"), - FreeLibraryUnknown => - write!(f, "FreeLibrary failed, but system did not report the 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"), + CreateCStringWithTrailing { .. } => write!( + f, + "could not create a C string from bytes with trailing null" + ), IncompatibleSize => write!(f, "requested type cannot possibly work"), } } diff --git a/src/lib.rs b/src/lib.rs index 3ddf98a34..d1e2ced62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,15 +35,18 @@ //! //! The compiler will ensure that the loaded function will not outlive the `Library` from which it comes, //! preventing the most common memory-safety issues. -#![cfg_attr(any(unix, windows), deny(missing_docs, clippy::all, unreachable_pub, unused))] +#![cfg_attr( + any(unix, windows), + deny(missing_docs, clippy::all, unreachable_pub, unused) +)] #![cfg_attr(libloading_docs, feature(doc_cfg))] pub mod changelog; -pub mod os; -mod util; mod error; +pub mod os; #[cfg(any(unix, windows, libloading_docs))] mod safe; +mod util; pub use self::error::Error; #[cfg(any(unix, windows, libloading_docs))] diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs index 59fa69a5e..fa6713138 100644 --- a/src/os/windows/mod.rs +++ b/src/os/windows/mod.rs @@ -4,22 +4,22 @@ mod windows_imports {} #[cfg(any(not(libloading_docs), windows))] mod windows_imports { - use super::{DWORD, BOOL, HANDLE, HMODULE, FARPROC}; + use super::{BOOL, DWORD, FARPROC, HANDLE, HMODULE}; pub(super) use std::os::windows::ffi::{OsStrExt, OsStringExt}; - windows_targets::link!("kernel32.dll" "system" fn GetLastError() -> DWORD); - windows_targets::link!("kernel32.dll" "system" fn SetThreadErrorMode(new_mode: DWORD, old_mode: *mut DWORD) -> BOOL); - windows_targets::link!("kernel32.dll" "system" fn GetModuleHandleExW(flags: u32, module_name: *const u16, module: *mut HMODULE) -> BOOL); - windows_targets::link!("kernel32.dll" "system" fn FreeLibrary(module: HMODULE) -> BOOL); - windows_targets::link!("kernel32.dll" "system" fn LoadLibraryExW(filename: *const u16, file: HANDLE, flags: DWORD) -> HMODULE); - windows_targets::link!("kernel32.dll" "system" fn GetModuleFileNameW(module: HMODULE, filename: *mut u16, size: DWORD) -> DWORD); - windows_targets::link!("kernel32.dll" "system" fn GetProcAddress(module: HMODULE, procname: *const u8) -> FARPROC); + 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); + windows_link::link!("kernel32.dll" "system" fn FreeLibrary(module: HMODULE) -> BOOL); + windows_link::link!("kernel32.dll" "system" fn LoadLibraryExW(filename: *const u16, file: HANDLE, flags: DWORD) -> HMODULE); + windows_link::link!("kernel32.dll" "system" fn GetModuleFileNameW(module: HMODULE, filename: *mut u16, size: DWORD) -> DWORD); + windows_link::link!("kernel32.dll" "system" fn GetProcAddress(module: HMODULE, procname: *const u8) -> FARPROC); } use self::windows_imports::*; -use util::{ensure_compatible_types, cstr_cow_from_bytes}; use std::ffi::{OsStr, OsString}; -use std::{fmt, io, marker, mem, ptr}; use std::os::raw; +use std::{fmt, io, marker, mem, ptr}; +use util::{cstr_cow_from_bytes, ensure_compatible_types}; /// The platform-specific counterpart of the cross-platform [`Library`](crate::Library). pub struct Library(HMODULE); @@ -83,14 +83,18 @@ impl Library { pub fn this() -> Result { unsafe { let mut handle: HMODULE = 0; - with_get_last_error(|source| crate::Error::GetModuleHandleExW { source }, || { - let result = GetModuleHandleExW(0, std::ptr::null_mut(), &mut handle); - if result == 0 { - None - } else { - Some(Library(handle)) - } - }).map_err(|e| e.unwrap_or(crate::Error::GetModuleHandleExWUnknown)) + with_get_last_error( + |source| crate::Error::GetModuleHandleExW { source }, + || { + let result = GetModuleHandleExW(0, std::ptr::null_mut(), &mut handle); + if result == 0 { + None + } else { + Some(Library(handle)) + } + }, + ) + .map_err(|e| e.unwrap_or(crate::Error::GetModuleHandleExWUnknown)) } } @@ -116,16 +120,20 @@ impl Library { 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)) + 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 @@ -152,32 +160,39 @@ 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, flags: LOAD_LIBRARY_FLAGS) -> Result { + 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 = 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)); + 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 } /// Attempts to pin the module represented by the current `Library` into memory. - /// + /// /// Calls `GetModuleHandleExW` with the flag `GET_MODULE_HANDLE_EX_FLAG_PIN` to pin the module. /// See the [MSDN documentation][msdn] for more information. - /// + /// /// [msdn]: https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexw - /// + /// /// If successful, the module will remain in memory regardless of the refcount for this `Library` pub fn pin(&self) -> Result<(), crate::Error> { const GET_MODULE_HANDLE_EX_FLAG_PIN: u32 = 0x1; @@ -223,17 +238,21 @@ impl Library { pub unsafe fn get(&self, symbol: &[u8]) -> 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)) + 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)) } /// Get a pointer to a function or static variable by ordinal number. @@ -243,18 +262,22 @@ impl Library { /// Users of this API must specify the correct type of the function or variable loaded. pub unsafe fn get_ordinal(&self, ordinal: u16) -> Result, crate::Error> { ensure_compatible_types::()?; - with_get_last_error(|source| crate::Error::GetProcAddress { source }, || { - let ordinal = ordinal as usize as *const _; - let symbol = GetProcAddress(self.0, ordinal); - if symbol.is_none() { - None - } else { - Some(Symbol { - pointer: symbol, - pd: marker::PhantomData - }) - } - }).map_err(|e| e.unwrap_or(crate::Error::GetProcAddressUnknown)) + with_get_last_error( + |source| crate::Error::GetProcAddress { source }, + || { + let ordinal = ordinal as usize as *const _; + let symbol = GetProcAddress(self.0, ordinal); + if symbol.is_none() { + None + } else { + Some(Symbol { + pointer: symbol, + pd: marker::PhantomData, + }) + } + }, + ) + .map_err(|e| e.unwrap_or(crate::Error::GetProcAddressUnknown)) } /// Convert the `Library` to a raw handle. @@ -282,13 +305,17 @@ impl Library { /// /// The underlying data structures may still get leaked if an error does occur. pub fn close(self) -> Result<(), crate::Error> { - let result = with_get_last_error(|source| crate::Error::FreeLibrary { source }, || { - if unsafe { FreeLibrary(self.0) == 0 } { - None - } else { - Some(()) - } - }).map_err(|e| e.unwrap_or(crate::Error::FreeLibraryUnknown)); + let result = with_get_last_error( + |source| crate::Error::FreeLibrary { source }, + || { + if unsafe { FreeLibrary(self.0) == 0 } { + None + } else { + Some(()) + } + }, + ) + .map_err(|e| e.unwrap_or(crate::Error::FreeLibraryUnknown)); // 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... @@ -299,7 +326,9 @@ impl Library { impl Drop for Library { fn drop(&mut self) { - unsafe { FreeLibrary(self.0); } + unsafe { + FreeLibrary(self.0); + } } } @@ -307,10 +336,8 @@ impl fmt::Debug for Library { 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; + 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)) } else { @@ -330,7 +357,7 @@ impl fmt::Debug for Library { /// `Symbol` does not outlive the `Library` that it comes from. pub struct Symbol { pointer: FARPROC, - pd: marker::PhantomData + pd: marker::PhantomData, } impl Symbol { @@ -416,20 +443,25 @@ impl Drop for ErrorModeGuard { } } -fn with_get_last_error(wrap: fn(crate::error::WindowsError) -> crate::Error, closure: F) --> Result> -where F: FnOnce() -> Option { +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(io::Error::from_raw_os_error(error as i32)))) + Some(wrap(crate::error::WindowsError( + io::Error::from_raw_os_error(error as i32), + ))) } }) } - #[allow(clippy::upper_case_acronyms)] type BOOL = i32; #[allow(clippy::upper_case_acronyms)] diff --git a/src/safe.rs b/src/safe.rs index 9788e1e35..e217ee394 100644 --- a/src/safe.rs +++ b/src/safe.rs @@ -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: &[u8]) -> Result, Error> { self.0.get(symbol).map(|from| Symbol::from_raw(from, self)) } @@ -261,7 +261,6 @@ impl<'lib, T> Symbol<'lib, T> { /// was loaded from. pub unsafe fn try_as_raw_ptr(self) -> Option<*mut raw::c_void> { Some( - #[allow(unused_unsafe)] // 1.56.0 compat unsafe { // SAFE: the calling function has the same soundness invariants as this callee. self.into_raw() @@ -317,4 +316,3 @@ impl fmt::Debug for Symbol<'_, T> { unsafe impl Send for Symbol<'_, T> {} unsafe impl Sync for Symbol<'_, T> {} - diff --git a/tests/functions.rs b/tests/functions.rs index 84d5267d9..dc6b316e7 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -2,8 +2,8 @@ extern crate windows_sys; extern crate libloading; -use std::os::raw::c_void; use libloading::{Library, Symbol}; +use std::os::raw::c_void; const TARGET_DIR: Option<&'static str> = option_env!("CARGO_TARGET_DIR"); const TARGET_TMPDIR: Option<&'static str> = option_env!("CARGO_TARGET_TMPDIR"); @@ -53,7 +53,7 @@ fn test_try_into_ptr() { 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()); - let ptr_casted : extern "C" fn(u32) -> u32 = std::mem::transmute(ptr); + let ptr_casted: extern "C" fn(u32) -> u32 = std::mem::transmute(ptr); assert_eq!(42, ptr_casted(42)); } } diff --git a/tests/windows.rs b/tests/windows.rs index c898f18f4..13a414502 100644 --- a/tests/windows.rs +++ b/tests/windows.rs @@ -13,21 +13,17 @@ use std::os::raw::c_void; // // The DLLs were kindly compiled by WindowsBunny (aka. @retep998). -#[cfg(target_arch="x86")] +#[cfg(target_arch = "x86")] fn load_ordinal_lib() -> Library { - unsafe { - Library::new("tests/nagisa32.dll").expect("nagisa32.dll") - } + unsafe { Library::new("tests/nagisa32.dll").expect("nagisa32.dll") } } -#[cfg(target_arch="x86_64")] +#[cfg(target_arch = "x86_64")] fn load_ordinal_lib() -> Library { - unsafe { - Library::new("tests/nagisa64.dll").expect("nagisa64.dll") - } + unsafe { Library::new("tests/nagisa64.dll").expect("nagisa64.dll") } } -#[cfg(any(target_arch="x86", target_arch="x86_64"))] +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] #[test] fn test_ordinal() { let lib = load_ordinal_lib(); @@ -37,18 +33,18 @@ fn test_ordinal() { } } -#[cfg(any(target_arch="x86", target_arch="x86_64"))] +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] #[test] fn test_try_into_ptr() { let lib = load_ordinal_lib(); unsafe { let windows: Symbol *const i8> = lib.get_ordinal(1).expect("function"); - let ptr : *mut c_void = windows.as_raw_ptr(); + let ptr: *mut c_void = windows.as_raw_ptr(); assert!(!ptr.is_null()); } } -#[cfg(any(target_arch="x86", target_arch="x86_64"))] +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] #[test] fn test_ordinal_missing_fails() { let lib = load_ordinal_lib();