use std::fmt;
use std::path::{Path, PathBuf};
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Io(std::io::Error),
ElfParse(ElfParseError),
}
#[derive(Debug)]
pub struct ElfParseError {
path: PathBuf,
source: goblin::error::Error,
}
impl ElfParseError {
pub(crate) fn new(path: impl Into<PathBuf>, source: goblin::error::Error) -> Self {
Self {
path: path.into(),
source,
}
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
}
impl fmt::Display for ElfParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"failed to parse ELF {}: {}",
self.path.display(),
self.source
)
}
}
impl std::error::Error for ElfParseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(err) => err.fmt(f),
Self::ElfParse(err) => err.fmt(f),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(err) => Some(err),
Self::ElfParse(err) => Some(err),
}
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}
impl From<ElfParseError> for Error {
fn from(err: ElfParseError) -> Self {
Self::ElfParse(err)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_and_source_follow_io_error() {
let err = Error::from(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"denied",
));
assert_eq!(err.to_string(), "denied");
assert!(std::error::Error::source(&err).is_some());
}
#[test]
fn elf_parse_error_preserves_path_and_source_chain() {
let parser_error = goblin::elf::Elf::parse(&[]).expect_err("reject empty ELF");
let err = Error::from(ElfParseError::new("/tmp/broken.so", parser_error));
assert!(err.to_string().starts_with("failed to parse ELF "));
let source = std::error::Error::source(&err).expect("ELF error source");
let parse = source
.downcast_ref::<ElfParseError>()
.expect("structured ELF parse error");
assert_eq!(parse.path(), Path::new("/tmp/broken.so"));
assert!(source
.source()
.is_some_and(|source| { source.downcast_ref::<goblin::error::Error>().is_some() }));
}
#[test]
fn errors_are_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Error>();
assert_send_sync::<ElfParseError>();
}
}