#![allow(unsafe_code)]
use std::collections::HashSet;
use std::ffi::c_void;
use std::path::{Path, PathBuf};
use lean_rs_sys::lean_object;
use object::{Object, ObjectSection, ObjectSymbol, SectionKind, SymbolSection};
use super::initializer::{InitializerName, RawInitializer, call_initializer};
use super::loaded::LeanModule;
#[cfg(doc)]
use crate::error::HostStage;
use crate::error::{LeanError, LeanResult};
use crate::runtime::LeanRuntime;
pub struct LeanLibrary<'lean> {
library: libloading::Library,
path: PathBuf,
runtime: &'lean LeanRuntime,
globals: HashSet<String>,
}
impl<'lean> LeanLibrary<'lean> {
pub fn open(runtime: &'lean LeanRuntime, path: impl AsRef<Path>) -> LeanResult<Self> {
let path = path.as_ref();
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.module.library.open",
path = %crate::error::redact::short_path(path),
)
.entered();
let globals = classify_globals(path)?;
let library = unsafe { libloading::Library::new(path) }.map_err(|err| {
LeanError::module_init(format!("failed to open Lean library '{}': {err}", path.display()))
})?;
Ok(Self {
library,
path: path.to_path_buf(),
runtime,
globals,
})
}
pub fn open_globally(runtime: &'lean LeanRuntime, path: impl AsRef<Path>) -> LeanResult<Self> {
let path = path.as_ref();
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.module.library.open_globally",
path = %crate::error::redact::short_path(path),
)
.entered();
let globals = classify_globals(path)?;
let library = open_with_global_visibility(path)?;
Ok(Self {
library,
path: path.to_path_buf(),
runtime,
globals,
})
}
pub fn initialize_module<'lib>(&'lib self, package: &str, module: &str) -> LeanResult<LeanModule<'lean, 'lib>> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.module.library.initialize",
package = package,
module = module,
)
.entered();
let name = InitializerName::from_lake_names(package, module)?;
let init = self.lookup_initializer(&name)?;
call_initializer(self.runtime, init, &name)?;
Ok(LeanModule::new(self, name))
}
pub(crate) fn globals(&self) -> &HashSet<String> {
&self.globals
}
pub(crate) fn runtime(&self) -> &'lean LeanRuntime {
self.runtime
}
pub(crate) fn path(&self) -> &Path {
&self.path
}
pub fn resolve_function_symbol(&self, name: &str) -> LeanResult<*mut c_void> {
let symbol: libloading::Symbol<'_, *mut c_void> =
unsafe { self.library.get(name.as_bytes()) }.map_err(|err| {
LeanError::symbol_lookup(format!(
"unknown exported symbol '{}' in '{}': {err}",
name,
self.path.display()
))
})?;
Ok(*symbol)
}
pub fn resolve_optional_function_symbol(&self, name: &str) -> Option<*mut c_void> {
match unsafe { self.library.get::<*mut c_void>(name.as_bytes()) } {
Ok(symbol) => Some(*symbol),
Err(_) => None,
}
}
pub(crate) fn resolve_global_symbol(&self, name: &str) -> LeanResult<*mut *mut lean_object> {
let symbol: libloading::Symbol<'_, *mut *mut lean_object> = unsafe { self.library.get(name.as_bytes()) }
.map_err(|err| {
LeanError::symbol_lookup(format!(
"unknown global symbol '{}' in '{}': {err}",
name,
self.path.display()
))
})?;
Ok(*symbol)
}
fn lookup_initializer(&self, name: &InitializerName) -> LeanResult<RawInitializer> {
let modern: Result<libloading::Symbol<'_, RawInitializer>, _> =
unsafe { self.library.get(name.symbol_bytes()) };
if let Ok(symbol) = modern {
return Ok(*symbol);
}
let legacy: Result<libloading::Symbol<'_, RawInitializer>, _> =
unsafe { self.library.get(name.legacy_symbol_bytes()) };
match legacy {
Ok(symbol) => Ok(*symbol),
Err(err) => Err(LeanError::linking(format!(
"missing initializer symbol in '{}': tried '{}' and '{}': {err}",
self.path.display(),
name.symbol_str(),
name.legacy_symbol_str(),
))),
}
}
}
impl std::fmt::Debug for LeanLibrary<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LeanLibrary")
.field("path", &self.path)
.field("globals_count", &self.globals.len())
.finish()
}
}
fn classify_globals(path: &Path) -> LeanResult<HashSet<String>> {
let bytes = std::fs::read(path)
.map_err(|err| LeanError::module_init(format!("failed to read Lean library '{}': {err}", path.display())))?;
let file = object::File::parse(&*bytes)
.map_err(|err| LeanError::module_init(format!("failed to parse object file '{}': {err}", path.display())))?;
let strip_underscore = matches!(file.format(), object::BinaryFormat::MachO | object::BinaryFormat::Wasm);
let mut globals = HashSet::new();
for symbol in file.symbols() {
if !symbol.is_global() || symbol.is_undefined() {
continue;
}
let SymbolSection::Section(section_index) = symbol.section() else {
continue;
};
let Ok(section) = file.section_by_index(section_index) else {
continue;
};
if !is_data_section(section.kind()) {
continue;
}
let Ok(name) = symbol.name() else {
continue;
};
let normalised = if strip_underscore {
name.strip_prefix('_').unwrap_or(name)
} else {
name
};
if normalised.is_empty() {
continue;
}
globals.insert(normalised.to_owned());
}
Ok(globals)
}
fn open_with_global_visibility(path: &Path) -> LeanResult<libloading::Library> {
#[cfg(unix)]
{
let unix_library = unsafe {
libloading::os::unix::Library::open(
Some(path),
libloading::os::unix::RTLD_LAZY | libloading::os::unix::RTLD_GLOBAL,
)
}
.map_err(|err| {
LeanError::module_init(format!(
"failed to open Lean library '{}' with RTLD_GLOBAL: {err}",
path.display()
))
})?;
Ok(unix_library.into())
}
#[cfg(not(unix))]
{
let library = unsafe { libloading::Library::new(path) }.map_err(|err| {
LeanError::module_init(format!("failed to open Lean library '{}': {err}", path.display()))
})?;
Ok(library)
}
}
fn is_data_section(kind: SectionKind) -> bool {
matches!(
kind,
SectionKind::Data
| SectionKind::ReadOnlyData
| SectionKind::ReadOnlyDataWithRel
| SectionKind::UninitializedData
| SectionKind::Common
| SectionKind::Tls
| SectionKind::UninitializedTls
)
}