| use std::error::Error; |
| use std::path::Path; |
| use std::time::Duration; |
| |
| use rustc_fs_util::try_canonicalize; |
| use rustc_proc_macro::bridge::client::Client as ProcMacroClient; |
| use rustc_session::StableCrateId; |
| use tracing::debug; |
| |
| use crate::locator::CrateError; |
| |
| fn format_dlopen_err(e: &(dyn std::error::Error + 'static)) -> String { |
| e.sources().map(|e| format!(": {e}")).collect() |
| } |
| |
| fn attempt_load_dylib(path: &Path) -> Result<libloading::Library, libloading::Error> { |
| #[cfg(target_os = "aix")] |
| if let Some(ext) = path.extension() |
| && ext.eq("a") |
| { |
| // On AIX, we ship all libraries as .a big_af archive |
| // the expected format is lib<name>.a(libname.so) for the actual |
| // dynamic library |
| let library_name = path.file_stem().expect("expect a library name"); |
| let mut archive_member = std::ffi::OsString::from("a("); |
| archive_member.push(library_name); |
| archive_member.push(".so)"); |
| let new_path = path.with_extension(archive_member); |
| |
| // On AIX, we need RTLD_MEMBER to dlopen an archived shared |
| let flags = libc::RTLD_LAZY | libc::RTLD_LOCAL | libc::RTLD_MEMBER; |
| return unsafe { libloading::os::unix::Library::open(Some(&new_path), flags) } |
| .map(|lib| lib.into()); |
| } |
| |
| unsafe { libloading::Library::new(&path) } |
| } |
| |
| // On Windows the compiler would sometimes intermittently fail to open the |
| // proc-macro DLL with `Error::LoadLibraryExW`. It is suspected that something in the |
| // system still holds a lock on the file, so we retry a few times before calling it |
| // an error. |
| fn load_dylib(path: &Path, max_attempts: usize) -> Result<libloading::Library, String> { |
| assert!(max_attempts > 0); |
| |
| let mut last_error = None; |
| |
| for attempt in 0..max_attempts { |
| debug!("Attempt to load proc-macro `{}`.", path.display()); |
| match attempt_load_dylib(path) { |
| Ok(lib) => { |
| if attempt > 0 { |
| debug!( |
| "Loaded proc-macro `{}` after {} attempts.", |
| path.display(), |
| attempt + 1 |
| ); |
| } |
| return Ok(lib); |
| } |
| Err(err) => { |
| // Only try to recover from this specific error. |
| if !matches!(err, libloading::Error::LoadLibraryExW { .. }) { |
| debug!("Failed to load proc-macro `{}`. Not retrying", path.display()); |
| let err = format_dlopen_err(&err); |
| // We include the path of the dylib in the error ourselves, so |
| // if it's in the error, we strip it. |
| if let Some(err) = err.strip_prefix(&format!(": {}", path.display())) { |
| return Err(err.to_string()); |
| } |
| return Err(err); |
| } |
| |
| last_error = Some(err); |
| std::thread::sleep(Duration::from_millis(100)); |
| debug!("Failed to load proc-macro `{}`. Retrying.", path.display()); |
| } |
| } |
| } |
| |
| debug!("Failed to load proc-macro `{}` even after {} attempts.", path.display(), max_attempts); |
| |
| let last_error = last_error.unwrap(); |
| let message = if let Some(src) = last_error.source() { |
| format!("{} ({src}) (retried {max_attempts} times)", format_dlopen_err(&last_error)) |
| } else { |
| format!("{} (retried {max_attempts} times)", format_dlopen_err(&last_error)) |
| }; |
| Err(message) |
| } |
| |
| pub enum DylibError { |
| DlOpen(String, String), |
| DlSym(String, String), |
| } |
| |
| impl From<DylibError> for CrateError { |
| fn from(err: DylibError) -> CrateError { |
| match err { |
| DylibError::DlOpen(path, err) => CrateError::DlOpen(path, err), |
| DylibError::DlSym(path, err) => CrateError::DlSym(path, err), |
| } |
| } |
| } |
| |
| pub unsafe fn load_symbol_from_dylib<T: Copy>( |
| path: &Path, |
| sym_name: &str, |
| ) -> Result<T, DylibError> { |
| // Make sure the path contains a / or the linker will search for it. |
| let path = try_canonicalize(path).unwrap(); |
| let lib = |
| load_dylib(&path, 5).map_err(|err| DylibError::DlOpen(path.display().to_string(), err))?; |
| |
| let sym = unsafe { lib.get::<T>(sym_name.as_bytes()) } |
| .map_err(|err| DylibError::DlSym(path.display().to_string(), format_dlopen_err(&err)))?; |
| |
| // Intentionally leak the dynamic library. We can't ever unload it |
| // since the library can make things that will live arbitrarily long. |
| let sym = unsafe { sym.into_raw() }; |
| std::mem::forget(lib); |
| |
| Ok(*sym) |
| } |
| |
| pub(crate) fn dlsym_proc_macros( |
| path: &Path, |
| stable_crate_id: StableCrateId, |
| ) -> Result<&'static [ProcMacroClient], DylibError> { |
| let sym_name = rustc_session::generate_proc_macro_decls_symbol(stable_crate_id); |
| debug!("trying to dlsym proc_macros {} for symbol `{}`", path.display(), sym_name); |
| |
| unsafe { |
| // FIXME(bjorn3) this depends on the unstable slice memory layout |
| let result = crate::load_symbol_from_dylib::<*const &[ProcMacroClient]>(path, &sym_name); |
| match result { |
| Ok(result) => { |
| debug!("loaded dlsym proc_macros {} for symbol `{}`", path.display(), sym_name); |
| Ok(*result) |
| } |
| Err(err) => { |
| debug!("failed to dlsym proc_macros {} for symbol `{}`", path.display(), sym_name); |
| Err(err.into()) |
| } |
| } |
| } |
| } |