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.
This commit is contained in:
bors 2021-12-12 17:28:52 +00:00
commit 6bda5b331c
15 changed files with 91 additions and 313 deletions

View File

@ -1926,6 +1926,16 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "libloading"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0"
dependencies = [
"cfg-if 1.0.0",
"winapi",
]
[[package]]
name = "libm"
version = "0.1.4"
@ -3694,6 +3704,7 @@ dependencies = [
"bitflags",
"cstr",
"libc",
"libloading",
"measureme 10.0.0",
"rustc-demangle",
"rustc_arena",
@ -3978,6 +3989,7 @@ name = "rustc_interface"
version = "0.0.0"
dependencies = [
"libc",
"libloading",
"rustc-rayon",
"rustc-rayon-core",
"rustc_ast",
@ -4090,7 +4102,7 @@ dependencies = [
name = "rustc_metadata"
version = "0.0.0"
dependencies = [
"libc",
"libloading",
"odht",
"rustc_ast",
"rustc_attr",
@ -4110,7 +4122,6 @@ dependencies = [
"smallvec",
"snap",
"tracing",
"winapi",
]
[[package]]
@ -4283,6 +4294,7 @@ dependencies = [
name = "rustc_plugin_impl"
version = "0.0.0"
dependencies = [
"libloading",
"rustc_ast",
"rustc_errors",
"rustc_hir",

View File

@ -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" }

View File

@ -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();

View File

@ -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" }

View File

@ -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`,

View File

@ -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"] }

View File

@ -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) {

View File

@ -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) }
}
}

View File

@ -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."),
}
}

View File

@ -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};

View File

@ -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" }

View File

@ -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)
}

View File

@ -1,9 +1,25 @@
-include ../tools.mk
# ignore-windows-msvc
NM=nm -D
ifeq ($(UNAME),Darwin)
NM=nm -gU
endif
ifdef IS_WINDOWS
NM=nm -g
endif
# This overrides the LD_LIBRARY_PATH for RUN
TARGET_RPATH_DIR:=$(TARGET_RPATH_DIR):$(TMPDIR)
all:
$(RUSTC) dylib.rs -o $(TMPDIR)/libdylib.so -C prefer-dynamic
$(RUSTC) main.rs -C prefer-dynamic
$(call RUN,main)
[ "$$($(NM) $(TMPDIR)/libdylib.so | grep -v __imp_ | grep -c fun1)" -eq "1" ]
[ "$$($(NM) $(TMPDIR)/libdylib.so | grep -v __imp_ | grep -c fun2)" -eq "1" ]
[ "$$($(NM) $(TMPDIR)/libdylib.so | grep -v __imp_ | grep -c fun3)" -eq "1" ]
[ "$$($(NM) $(TMPDIR)/libdylib.so | grep -v __imp_ | grep -c fun4)" -eq "1" ]
[ "$$($(NM) $(TMPDIR)/libdylib.so | grep -v __imp_ | grep -c fun5)" -eq "1" ]

View File

@ -1,18 +0,0 @@
#![feature(rustc_private)]
extern crate rustc_metadata;
use rustc_metadata::dynamic_lib::DynamicLibrary;
use std::path::Path;
pub fn main() {
unsafe {
let path = Path::new("libdylib.so");
let a = DynamicLibrary::open(&path).unwrap();
assert!(a.symbol::<isize>("fun1").is_ok());
assert!(a.symbol::<isize>("fun2").is_ok());
assert!(a.symbol::<isize>("fun3").is_ok());
assert!(a.symbol::<isize>("fun4").is_ok());
assert!(a.symbol::<isize>("fun5").is_ok());
}
}

View File

@ -15,6 +15,7 @@ const LICENSES: &[&str] = &[
"Apache-2.0 OR MIT",
"Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", // wasi license
"MIT",
"ISC",
"Unlicense/MIT",
"Unlicense OR MIT",
"0BSD OR MIT OR Apache-2.0", // adler license
@ -53,7 +54,6 @@ const EXCEPTIONS_CRANELIFT: &[(&str, &str)] = &[
("cranelift-module", "Apache-2.0 WITH LLVM-exception"),
("cranelift-native", "Apache-2.0 WITH LLVM-exception"),
("cranelift-object", "Apache-2.0 WITH LLVM-exception"),
("libloading", "ISC"),
("mach", "BSD-2-Clause"),
("regalloc", "Apache-2.0 WITH LLVM-exception"),
("target-lexicon", "Apache-2.0 WITH LLVM-exception"),
@ -129,6 +129,7 @@ const PERMITTED_DEPENDENCIES: &[&str] = &[
"jobserver",
"lazy_static",
"libc",
"libloading",
"libz-sys",
"lock_api",
"log",