about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-12-12 17:28:52 +0000
committerbors <bors@rust-lang.org>2021-12-12 17:28:52 +0000
commit6bda5b331cfe7e04e1fe348c58a928fc2b650f4f (patch)
treefa337d4fa5d7143dfe8fc6aed1b3c77fd287af74 /compiler
parent753e569c9c2a4e3ef394ef7abd0802bf57f66bce (diff)
parent923f939791a08d3f58566b0fc755381de031f43e (diff)
downloadrust-6bda5b331cfe7e04e1fe348c58a928fc2b650f4f.tar.gz
rust-6bda5b331cfe7e04e1fe348c58a928fc2b650f4f.zip
Auto merge of #90716 - euclio:libloading, r=cjgillot
replace dynamic library module with libloading

This PR deletes the `rustc_metadata::dynamic_lib` module in favor of the popular and better tested [`libloading` crate](https://github.com/nagisa/rust_libloading/).

We don't benefit from `libloading`'s symbol lifetimes since we end up leaking the loaded library in all cases, but the call-sites look much nicer by improving error handling and abstracting away some transmutes. We also can remove `rustc_metadata`'s direct dependencies on `libc` and `winapi`.

This PR also adds an exception for `libloading` (and its license) to tidy, so this will need sign-off from the compiler team.
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_codegen_llvm/Cargo.toml1
-rw-r--r--compiler/rustc_codegen_llvm/src/llvm_util.rs17
-rw-r--r--compiler/rustc_interface/Cargo.toml1
-rw-r--r--compiler/rustc_interface/src/util.rs46
-rw-r--r--compiler/rustc_metadata/Cargo.toml5
-rw-r--r--compiler/rustc_metadata/src/creader.rs21
-rw-r--r--compiler/rustc_metadata/src/dynamic_lib.rs194
-rw-r--r--compiler/rustc_metadata/src/dynamic_lib/tests.rs18
-rw-r--r--compiler/rustc_metadata/src/lib.rs1
-rw-r--r--compiler/rustc_plugin_impl/Cargo.toml1
-rw-r--r--compiler/rustc_plugin_impl/src/load.rs42
11 files changed, 57 insertions, 290 deletions
diff --git a/compiler/rustc_codegen_llvm/Cargo.toml b/compiler/rustc_codegen_llvm/Cargo.toml
index f77b0bc8cc9..3b410c2cb4a 100644
--- a/compiler/rustc_codegen_llvm/Cargo.toml
+++ b/compiler/rustc_codegen_llvm/Cargo.toml
@@ -11,6 +11,7 @@ doctest = false
 bitflags = "1.0"
 cstr = "0.2"
 libc = "0.2"
+libloading = "0.7.1"
 measureme = "10.0.0"
 tracing = "0.1"
 rustc_middle = { path = "../rustc_middle" }
diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs
index 3393c9baa28..79a261244d3 100644
--- a/compiler/rustc_codegen_llvm/src/llvm_util.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs
@@ -1,9 +1,9 @@
 use crate::back::write::create_informational_target_machine;
 use crate::{llvm, llvm_util};
 use libc::c_int;
+use libloading::Library;
 use rustc_codegen_ssa::target_features::supported_target_features;
 use rustc_data_structures::fx::FxHashSet;
-use rustc_metadata::dynamic_lib::DynamicLibrary;
 use rustc_middle::bug;
 use rustc_session::config::PrintRequest;
 use rustc_session::Session;
@@ -13,7 +13,6 @@ use std::ffi::{CStr, CString};
 use tracing::debug;
 
 use std::mem;
-use std::path::Path;
 use std::ptr;
 use std::slice;
 use std::str;
@@ -120,14 +119,14 @@ unsafe fn configure_llvm(sess: &Session) {
 
     llvm::LLVMInitializePasses();
 
+    // Register LLVM plugins by loading them into the compiler process.
     for plugin in &sess.opts.debugging_opts.llvm_plugins {
-        let path = Path::new(plugin);
-        let res = DynamicLibrary::open(path);
-        match res {
-            Ok(_) => debug!("LLVM plugin loaded succesfully {} ({})", path.display(), plugin),
-            Err(e) => bug!("couldn't load plugin: {}", e),
-        }
-        mem::forget(res);
+        let lib = Library::new(plugin).unwrap_or_else(|e| bug!("couldn't load plugin: {}", e));
+        debug!("LLVM plugin loaded successfully {:?} ({})", lib, plugin);
+
+        // Intentionally leak the dynamic library. We can't ever unload it
+        // since the library can make things that will live arbitrarily long.
+        mem::forget(lib);
     }
 
     rustc_llvm::initialize_available_targets();
diff --git a/compiler/rustc_interface/Cargo.toml b/compiler/rustc_interface/Cargo.toml
index 07af2201f5f..f5823e521b9 100644
--- a/compiler/rustc_interface/Cargo.toml
+++ b/compiler/rustc_interface/Cargo.toml
@@ -8,6 +8,7 @@ doctest = false
 
 [dependencies]
 libc = "0.2"
+libloading = "0.7.1"
 tracing = "0.1"
 rustc-rayon-core = "0.3.1"
 rayon = { version = "0.3.1", package = "rustc-rayon" }
diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs
index 04e183a9ba5..b04f91634cc 100644
--- a/compiler/rustc_interface/src/util.rs
+++ b/compiler/rustc_interface/src/util.rs
@@ -1,3 +1,4 @@
+use libloading::Library;
 use rustc_ast::mut_visit::{visit_clobber, MutVisitor, *};
 use rustc_ast::ptr::P;
 use rustc_ast::{self as ast, AttrVec, BlockCheckMode};
@@ -7,7 +8,6 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::jobserver;
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::registry::Registry;
-use rustc_metadata::dynamic_lib::DynamicLibrary;
 #[cfg(parallel_compiler)]
 use rustc_middle::ty::tls;
 use rustc_parse::validate_attr;
@@ -39,6 +39,9 @@ use std::sync::{Arc, Mutex};
 use std::thread;
 use tracing::info;
 
+/// Function pointer type that constructs a new CodegenBackend.
+pub type MakeBackendFn = fn() -> Box<dyn CodegenBackend>;
+
 /// Adds `target_feature = "..."` cfgs for a variety of platform
 /// specific features (SSE, NEON etc.).
 ///
@@ -211,28 +214,24 @@ pub fn setup_callbacks_and_run_in_thread_pool_with_globals<F: FnOnce() -> R + Se
     })
 }
 
-fn load_backend_from_dylib(path: &Path) -> fn() -> Box<dyn CodegenBackend> {
-    let lib = DynamicLibrary::open(path).unwrap_or_else(|err| {
-        let err = format!("couldn't load codegen backend {:?}: {:?}", path, err);
+fn load_backend_from_dylib(path: &Path) -> MakeBackendFn {
+    let lib = unsafe { Library::new(path) }.unwrap_or_else(|err| {
+        let err = format!("couldn't load codegen backend {:?}: {}", path, err);
         early_error(ErrorOutputType::default(), &err);
     });
-    unsafe {
-        match lib.symbol("__rustc_codegen_backend") {
-            Ok(f) => {
-                mem::forget(lib);
-                mem::transmute::<*mut u8, _>(f)
-            }
-            Err(e) => {
-                let err = format!(
-                    "couldn't load codegen backend as it \
-                                   doesn't export the `__rustc_codegen_backend` \
-                                   symbol: {:?}",
-                    e
-                );
-                early_error(ErrorOutputType::default(), &err);
-            }
-        }
-    }
+
+    let backend_sym = unsafe { lib.get::<MakeBackendFn>(b"__rustc_codegen_backend") }
+        .unwrap_or_else(|e| {
+            let err = format!("couldn't load codegen backend: {}", e);
+            early_error(ErrorOutputType::default(), &err);
+        });
+
+    // Intentionally leak the dynamic library. We can't ever unload it
+    // since the library can make things that will live arbitrarily long.
+    let backend_sym = unsafe { backend_sym.into_raw() };
+    mem::forget(lib);
+
+    *backend_sym
 }
 
 /// Get the codegen backend based on the name and specified sysroot.
@@ -380,10 +379,7 @@ fn sysroot_candidates() -> Vec<PathBuf> {
     }
 }
 
-pub fn get_codegen_sysroot(
-    maybe_sysroot: &Option<PathBuf>,
-    backend_name: &str,
-) -> fn() -> Box<dyn CodegenBackend> {
+pub fn get_codegen_sysroot(maybe_sysroot: &Option<PathBuf>, backend_name: &str) -> MakeBackendFn {
     // For now we only allow this function to be called once as it'll dlopen a
     // few things, which seems to work best if we only do that once. In
     // general this assertion never trips due to the once guard in `get_codegen_backend`,
diff --git a/compiler/rustc_metadata/Cargo.toml b/compiler/rustc_metadata/Cargo.toml
index dec77d996f3..59796dd6529 100644
--- a/compiler/rustc_metadata/Cargo.toml
+++ b/compiler/rustc_metadata/Cargo.toml
@@ -7,7 +7,7 @@ edition = "2021"
 doctest = false
 
 [dependencies]
-libc = "0.2"
+libloading = "0.7.1"
 odht = { version = "0.3.1", features = ["nightly"] }
 snap = "1"
 tracing = "0.1"
@@ -27,6 +27,3 @@ rustc_ast = { path = "../rustc_ast" }
 rustc_expand = { path = "../rustc_expand" }
 rustc_span = { path = "../rustc_span" }
 rustc_session = { path = "../rustc_session" }
-
-[target.'cfg(windows)'.dependencies]
-winapi = { version = "0.3", features = ["errhandlingapi", "libloaderapi"] }
diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs
index 2626a2e189c..e304682a2d4 100644
--- a/compiler/rustc_metadata/src/creader.rs
+++ b/compiler/rustc_metadata/src/creader.rs
@@ -1,6 +1,5 @@
 //! Validates all used crates and extern libraries and loads their metadata
 
-use crate::dynamic_lib::DynamicLibrary;
 use crate::locator::{CrateError, CrateLocator, CratePaths};
 use crate::rmeta::{CrateDep, CrateMetadata, CrateNumMap, CrateRoot, MetadataBlob};
 
@@ -676,25 +675,19 @@ impl<'a> CrateLoader<'a> {
     ) -> Result<&'static [ProcMacro], CrateError> {
         // Make sure the path contains a / or the linker will search for it.
         let path = env::current_dir().unwrap().join(path);
-        let lib = match DynamicLibrary::open(&path) {
-            Ok(lib) => lib,
-            Err(s) => return Err(CrateError::DlOpen(s)),
-        };
+        let lib = unsafe { libloading::Library::new(path) }
+            .map_err(|err| CrateError::DlOpen(err.to_string()))?;
 
-        let sym = self.sess.generate_proc_macro_decls_symbol(stable_crate_id);
-        let decls = unsafe {
-            let sym = match lib.symbol(&sym) {
-                Ok(f) => f,
-                Err(s) => return Err(CrateError::DlSym(s)),
-            };
-            *(sym as *const &[ProcMacro])
-        };
+        let sym_name = self.sess.generate_proc_macro_decls_symbol(stable_crate_id);
+        let sym = unsafe { lib.get::<*const &[ProcMacro]>(sym_name.as_bytes()) }
+            .map_err(|err| CrateError::DlSym(err.to_string()))?;
 
         // 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(decls)
+        Ok(unsafe { **sym })
     }
 
     fn inject_panic_runtime(&mut self, krate: &ast::Crate) {
diff --git a/compiler/rustc_metadata/src/dynamic_lib.rs b/compiler/rustc_metadata/src/dynamic_lib.rs
deleted file mode 100644
index e8929cd5c02..00000000000
--- a/compiler/rustc_metadata/src/dynamic_lib.rs
+++ /dev/null
@@ -1,194 +0,0 @@
-//! Dynamic library facilities.
-//!
-//! A simple wrapper over the platform's dynamic library facilities
-
-use std::ffi::CString;
-use std::path::Path;
-
-pub struct DynamicLibrary {
-    handle: *mut u8,
-}
-
-impl Drop for DynamicLibrary {
-    fn drop(&mut self) {
-        unsafe { dl::close(self.handle) }
-    }
-}
-
-impl DynamicLibrary {
-    /// Lazily open a dynamic library.
-    pub fn open(filename: &Path) -> Result<DynamicLibrary, String> {
-        let maybe_library = dl::open(filename.as_os_str());
-
-        // The dynamic library must not be constructed if there is
-        // an error opening the library so the destructor does not
-        // run.
-        match maybe_library {
-            Err(err) => Err(err),
-            Ok(handle) => Ok(DynamicLibrary { handle }),
-        }
-    }
-
-    /// Accesses the value at the symbol of the dynamic library.
-    pub unsafe fn symbol<T>(&self, symbol: &str) -> Result<*mut T, String> {
-        // This function should have a lifetime constraint of 'a on
-        // T but that feature is still unimplemented
-
-        let raw_string = CString::new(symbol).unwrap();
-        let maybe_symbol_value = dl::symbol(self.handle, raw_string.as_ptr());
-
-        // The value must not be constructed if there is an error so
-        // the destructor does not run.
-        match maybe_symbol_value {
-            Err(err) => Err(err),
-            Ok(symbol_value) => Ok(symbol_value as *mut T),
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests;
-
-#[cfg(unix)]
-mod dl {
-    use std::ffi::{CString, OsStr};
-    use std::os::unix::prelude::*;
-
-    // As of the 2017 revision of the POSIX standard (IEEE 1003.1-2017), it is
-    // implementation-defined whether `dlerror` is thread-safe (in which case it returns the most
-    // recent error in the calling thread) or not thread-safe (in which case it returns the most
-    // recent error in *any* thread).
-    //
-    // There's no easy way to tell what strategy is used by a given POSIX implementation, so we
-    // lock around all calls that can modify `dlerror` in this module lest we accidentally read an
-    // error from a different thread. This is bulletproof when we are the *only* code using the
-    // dynamic library APIs at a given point in time. However, it's still possible for us to race
-    // with other code (see #74469) on platforms where `dlerror` is not thread-safe.
-    mod error {
-        use std::ffi::CStr;
-        use std::lazy::SyncLazy;
-        use std::sync::{Mutex, MutexGuard};
-
-        pub fn lock() -> MutexGuard<'static, Guard> {
-            static LOCK: SyncLazy<Mutex<Guard>> = SyncLazy::new(|| Mutex::new(Guard));
-            LOCK.lock().unwrap()
-        }
-
-        #[non_exhaustive]
-        pub struct Guard;
-
-        impl Guard {
-            pub fn get(&mut self) -> Result<(), String> {
-                let msg = unsafe { libc::dlerror() };
-                if msg.is_null() {
-                    Ok(())
-                } else {
-                    let msg = unsafe { CStr::from_ptr(msg as *const _) };
-                    Err(msg.to_string_lossy().into_owned())
-                }
-            }
-
-            pub fn clear(&mut self) {
-                let _ = unsafe { libc::dlerror() };
-            }
-        }
-    }
-
-    pub(super) fn open(filename: &OsStr) -> Result<*mut u8, String> {
-        let s = CString::new(filename.as_bytes()).unwrap();
-
-        let mut dlerror = error::lock();
-        let ret = unsafe { libc::dlopen(s.as_ptr(), libc::RTLD_LAZY | libc::RTLD_LOCAL) };
-
-        if !ret.is_null() {
-            return Ok(ret.cast());
-        }
-
-        // A null return from `dlopen` indicates that an error has definitely occurred, so if
-        // nothing is in `dlerror`, we are racing with another thread that has stolen our error
-        // message. See the explanation on the `dl::error` module for more information.
-        dlerror.get().and_then(|()| Err("Unknown error".to_string()))
-    }
-
-    pub(super) unsafe fn symbol(
-        handle: *mut u8,
-        symbol: *const libc::c_char,
-    ) -> Result<*mut u8, String> {
-        let mut dlerror = error::lock();
-
-        // Unlike `dlopen`, it's possible for `dlsym` to return null without overwriting `dlerror`.
-        // Because of this, we clear `dlerror` before calling `dlsym` to avoid picking up a stale
-        // error message by accident.
-        dlerror.clear();
-
-        let ret = libc::dlsym(handle as *mut libc::c_void, symbol);
-
-        if !ret.is_null() {
-            return Ok(ret.cast());
-        }
-
-        // If `dlsym` returns null but there is nothing in `dlerror` it means one of two things:
-        // - We tried to load a symbol mapped to address 0. This is not technically an error but is
-        //   unlikely to occur in practice and equally unlikely to be handled correctly by calling
-        //   code. Therefore we treat it as an error anyway.
-        // - An error has occurred, but we are racing with another thread that has stolen our error
-        //   message. See the explanation on the `dl::error` module for more information.
-        dlerror.get().and_then(|()| Err("Tried to load symbol mapped to address 0".to_string()))
-    }
-
-    pub(super) unsafe fn close(handle: *mut u8) {
-        libc::dlclose(handle as *mut libc::c_void);
-    }
-}
-
-#[cfg(windows)]
-mod dl {
-    use std::ffi::OsStr;
-    use std::io;
-    use std::os::windows::prelude::*;
-    use std::ptr;
-
-    use winapi::shared::minwindef::HMODULE;
-    use winapi::um::errhandlingapi::SetThreadErrorMode;
-    use winapi::um::libloaderapi::{FreeLibrary, GetProcAddress, LoadLibraryW};
-    use winapi::um::winbase::SEM_FAILCRITICALERRORS;
-
-    pub(super) fn open(filename: &OsStr) -> Result<*mut u8, String> {
-        // disable "dll load failed" error dialog.
-        let prev_error_mode = unsafe {
-            let new_error_mode = SEM_FAILCRITICALERRORS;
-            let mut prev_error_mode = 0;
-            let result = SetThreadErrorMode(new_error_mode, &mut prev_error_mode);
-            if result == 0 {
-                return Err(io::Error::last_os_error().to_string());
-            }
-            prev_error_mode
-        };
-
-        let filename_str: Vec<_> = filename.encode_wide().chain(Some(0)).collect();
-        let result = unsafe { LoadLibraryW(filename_str.as_ptr()) } as *mut u8;
-        let result = ptr_result(result);
-
-        unsafe {
-            SetThreadErrorMode(prev_error_mode, ptr::null_mut());
-        }
-
-        result
-    }
-
-    pub(super) unsafe fn symbol(
-        handle: *mut u8,
-        symbol: *const libc::c_char,
-    ) -> Result<*mut u8, String> {
-        let ptr = GetProcAddress(handle as HMODULE, symbol) as *mut u8;
-        ptr_result(ptr)
-    }
-
-    pub(super) unsafe fn close(handle: *mut u8) {
-        FreeLibrary(handle as HMODULE);
-    }
-
-    fn ptr_result<T>(ptr: *mut T) -> Result<*mut T, String> {
-        if ptr.is_null() { Err(io::Error::last_os_error().to_string()) } else { Ok(ptr) }
-    }
-}
diff --git a/compiler/rustc_metadata/src/dynamic_lib/tests.rs b/compiler/rustc_metadata/src/dynamic_lib/tests.rs
deleted file mode 100644
index 7090bbf61c7..00000000000
--- a/compiler/rustc_metadata/src/dynamic_lib/tests.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-use super::*;
-
-#[test]
-fn test_errors_do_not_crash() {
-    use std::path::Path;
-
-    if !cfg!(unix) {
-        return;
-    }
-
-    // Open /dev/null as a library to get an error, and make sure
-    // that only causes an error, and not a crash.
-    let path = Path::new("/dev/null");
-    match DynamicLibrary::open(&path) {
-        Err(_) => {}
-        Ok(_) => panic!("Successfully opened the empty library."),
-    }
-}
diff --git a/compiler/rustc_metadata/src/lib.rs b/compiler/rustc_metadata/src/lib.rs
index 6cf0dd8b1ad..0bf6c266b80 100644
--- a/compiler/rustc_metadata/src/lib.rs
+++ b/compiler/rustc_metadata/src/lib.rs
@@ -28,7 +28,6 @@ mod native_libs;
 mod rmeta;
 
 pub mod creader;
-pub mod dynamic_lib;
 pub mod locator;
 
 pub use rmeta::{encode_metadata, EncodedMetadata, METADATA_HEADER};
diff --git a/compiler/rustc_plugin_impl/Cargo.toml b/compiler/rustc_plugin_impl/Cargo.toml
index 4e666e7e93d..f5071eb6e8f 100644
--- a/compiler/rustc_plugin_impl/Cargo.toml
+++ b/compiler/rustc_plugin_impl/Cargo.toml
@@ -8,6 +8,7 @@ edition = "2021"
 doctest = false
 
 [dependencies]
+libloading = "0.7.1"
 rustc_middle = { path = "../rustc_middle" }
 rustc_errors = { path = "../rustc_errors" }
 rustc_hir = { path = "../rustc_hir" }
diff --git a/compiler/rustc_plugin_impl/src/load.rs b/compiler/rustc_plugin_impl/src/load.rs
index c21075a443c..618682da4e5 100644
--- a/compiler/rustc_plugin_impl/src/load.rs
+++ b/compiler/rustc_plugin_impl/src/load.rs
@@ -1,6 +1,7 @@
 //! Used by `rustc` when loading a plugin.
 
 use crate::Registry;
+use libloading::Library;
 use rustc_ast::Crate;
 use rustc_errors::struct_span_err;
 use rustc_metadata::locator;
@@ -56,37 +57,28 @@ fn load_plugin(
     ident: Ident,
 ) {
     let lib = locator::find_plugin_registrar(sess, metadata_loader, ident.span, ident.name);
-    let fun = dylink_registrar(sess, ident.span, lib);
+    let fun = dylink_registrar(lib).unwrap_or_else(|err| {
+        // This is fatal: there are almost certainly macros we need inside this crate, so
+        // continuing would spew "macro undefined" errors.
+        sess.span_fatal(ident.span, &err.to_string());
+    });
     plugins.push(fun);
 }
 
-// Dynamically link a registrar function into the compiler process.
-fn dylink_registrar(sess: &Session, span: Span, path: PathBuf) -> PluginRegistrarFn {
-    use rustc_metadata::dynamic_lib::DynamicLibrary;
-
+/// Dynamically link a registrar function into the compiler process.
+fn dylink_registrar(lib_path: PathBuf) -> Result<PluginRegistrarFn, libloading::Error> {
     // Make sure the path contains a / or the linker will search for it.
-    let path = env::current_dir().unwrap().join(&path);
+    let lib_path = env::current_dir().unwrap().join(&lib_path);
 
-    let lib = match DynamicLibrary::open(&path) {
-        Ok(lib) => lib,
-        // this is fatal: there are almost certainly macros we need
-        // inside this crate, so continue would spew "macro undefined"
-        // errors
-        Err(err) => sess.span_fatal(span, &err),
-    };
+    let lib = unsafe { Library::new(&lib_path) }?;
 
-    unsafe {
-        let registrar = match lib.symbol("__rustc_plugin_registrar") {
-            Ok(registrar) => mem::transmute::<*mut u8, PluginRegistrarFn>(registrar),
-            // again fatal if we can't register macros
-            Err(err) => sess.span_fatal(span, &err),
-        };
+    let registrar_sym = unsafe { lib.get::<PluginRegistrarFn>(b"__rustc_plugin_registrar") }?;
 
-        // Intentionally leak the dynamic library. We can't ever unload it
-        // since the library can make things that will live arbitrarily long
-        // (e.g., an Rc cycle or a thread).
-        mem::forget(lib);
+    // Intentionally leak the dynamic library. We can't ever unload it
+    // since the library can make things that will live arbitrarily long
+    // (e.g., an Rc cycle or a thread).
+    let registrar_sym = unsafe { registrar_sym.into_raw() };
+    mem::forget(lib);
 
-        registrar
-    }
+    Ok(*registrar_sym)
 }