use std::fmt;
use macho_core::{ContextFrame, OffsetSpan, ParseError};
#[cfg(feature = "fixups")]
use macho_dyld::DyldError;
const INVALID_FORMAT_CODE: &str = "cpp.format.invalid";
const OUT_OF_BOUNDS_CODE: &str = "cpp.bounds.exceeded";
const INVALID_ADDRESS_CODE: &str = "cpp.address.invalid";
const UNSUPPORTED_INPUT_CODE: &str = "cpp.input.unsupported";
const CORE_FAILED_CODE: &str = "cpp.core.failed";
#[cfg(feature = "fixups")]
const DYLD_FAILED_CODE: &str = "cpp.dyld.failed";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CppErrorKind {
InvalidFormat,
OutOfBounds,
InvalidAddress,
Unsupported,
Core,
#[cfg(feature = "fixups")]
Dyld,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum CppErrorSource {
Parse(Box<ParseError>),
#[cfg(feature = "fixups")]
Dyld(Box<DyldError>),
}
#[derive(Debug)]
pub struct CppError {
pub kind: CppErrorKind,
pub location: Option<OffsetSpan>,
pub context: Vec<ContextFrame>,
message: String,
pub source: Option<CppErrorSource>,
}
impl CppError {
pub fn new(kind: CppErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
location: None,
context: Vec::new(),
message: message.into(),
source: None,
}
}
pub fn format(message: impl Into<String>) -> Self {
Self::new(CppErrorKind::InvalidFormat, message)
}
pub fn address(message: impl Into<String>) -> Self {
Self::new(CppErrorKind::InvalidAddress, message)
}
pub fn unsupported(message: impl Into<String>) -> Self {
Self::new(CppErrorKind::Unsupported, message)
}
pub fn bounds(offset: u64, needed: u64, available: u64) -> Self {
let mut error = Self::new(
CppErrorKind::OutOfBounds,
format!("offset {offset:#x}, needed {needed} bytes, have {available}"),
);
error.location = Some(OffsetSpan {
offset,
len: needed,
});
error
}
pub fn with_context(mut self, frame: ContextFrame) -> Self {
self.context.push(frame);
self
}
pub fn message(&self) -> &str {
&self.message
}
pub const fn code(&self) -> &'static str {
match self.kind {
CppErrorKind::InvalidFormat => INVALID_FORMAT_CODE,
CppErrorKind::OutOfBounds => OUT_OF_BOUNDS_CODE,
CppErrorKind::InvalidAddress => INVALID_ADDRESS_CODE,
CppErrorKind::Unsupported => UNSUPPORTED_INPUT_CODE,
CppErrorKind::Core => CORE_FAILED_CODE,
#[cfg(feature = "fixups")]
CppErrorKind::Dyld => DYLD_FAILED_CODE,
}
}
}
impl From<ParseError> for CppError {
fn from(source: ParseError) -> Self {
let mut context = source.context.clone();
context.push(ContextFrame::Operation { name: "cpp" });
Self {
kind: CppErrorKind::Core,
location: source.location,
context,
message: source.message().to_owned(),
source: Some(CppErrorSource::Parse(Box::new(source))),
}
}
}
#[cfg(feature = "fixups")]
impl From<DyldError> for CppError {
fn from(source: DyldError) -> Self {
let mut context = source.context.clone();
context.push(ContextFrame::Operation { name: "cpp" });
Self {
kind: CppErrorKind::Dyld,
location: source.location,
context,
message: source.message().to_owned(),
source: Some(CppErrorSource::Dyld(Box::new(source))),
}
}
}
impl fmt::Display for CppError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}: {}", self.code(), self.message)
}
}
impl std::error::Error for CppError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_ref().map(|source| match source {
CppErrorSource::Parse(source) => source as _,
#[cfg(feature = "fixups")]
CppErrorSource::Dyld(source) => source as _,
})
}
}
pub type Result<T> = std::result::Result<T, CppError>;
#[cfg(feature = "fixups")]
pub(crate) type Error = CppError;