Skip to main content

macho_core/
error.rs

1//! Structured parser errors with location and ordered context.
2
3use std::fmt;
4
5const INVALID_FORMAT_CODE: &str = "parse.format.invalid";
6const OUT_OF_BOUNDS_CODE: &str = "parse.bounds.exceeded";
7const INVALID_ADDRESS_CODE: &str = "parse.address.invalid";
8const INVALID_LOAD_COMMAND_CODE: &str = "parse.load_command.invalid";
9const LIMIT_EXCEEDED_CODE: &str = "parse.limit.exceeded";
10const UNSUPPORTED_INPUT_CODE: &str = "parse.input.unsupported";
11const VALIDATION_FAILED_CODE: &str = "parse.validation.failed";
12
13/// Stable category for a structural parse failure.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[non_exhaustive]
16pub enum ParseErrorKind {
17    /// The input does not encode a recognized or internally consistent format.
18    InvalidFormat,
19    /// A byte range lies outside the supplied input.
20    OutOfBounds,
21    /// An address or offset cannot be mapped safely.
22    InvalidAddress,
23    /// A load command is malformed.
24    InvalidLoadCommand,
25    /// A safe parser limit was exhausted.
26    LimitExceeded,
27    /// The input is structurally valid but unsupported by this parser.
28    Unsupported,
29    /// Strict structural validation rejected the parsed model.
30    Validation,
31}
32
33/// Byte range associated with a parser failure.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct OffsetSpan {
36    /// File- or slice-relative byte offset, described by the surrounding context.
37    pub offset: u64,
38    /// Length in bytes.
39    pub len: u64,
40}
41
42/// Ordered structural context retained while errors cross parser boundaries.
43#[derive(Debug, Clone, PartialEq, Eq)]
44#[non_exhaustive]
45pub enum ContextFrame {
46    /// A fat-container architecture table entry.
47    /// The FatArchitecture field.
48    FatArchitecture {
49        /// Zero-based architecture index in the containing fat binary.
50        index: usize,
51    },
52    /// A load command within one Mach-O image.
53    /// The LoadCommand field.
54    LoadCommand {
55        /// Zero-based command index in the selected Mach-O image.
56        index: usize,
57    },
58    /// A named parsing operation.
59    /// The Operation field.
60    Operation {
61        /// Stable operation name supplied by the layer adding context.
62        name: &'static str,
63    },
64}
65
66/// Structured failure returned by core parsing and structural accessors.
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct ParseError {
69    /// Machine-inspectable error category.
70    pub kind: ParseErrorKind,
71    /// Optional byte location.
72    pub location: Option<OffsetSpan>,
73    /// Context ordered from the innermost operation outward.
74    pub context: Vec<ContextFrame>,
75    message: String,
76}
77
78impl ParseError {
79    /// Construct a parser error with a typed category and human context.
80    pub fn new(kind: ParseErrorKind, message: impl Into<String>) -> Self {
81        Self {
82            kind,
83            location: None,
84            context: Vec::new(),
85            message: message.into(),
86        }
87    }
88
89    /// Construct an invalid-format error.
90    pub fn format(message: impl Into<String>) -> Self {
91        Self::new(ParseErrorKind::InvalidFormat, message)
92    }
93
94    /// Construct an out-of-bounds error with the attempted range.
95    pub fn bounds(offset: u64, needed: u64, available: u64) -> Self {
96        Self::new(
97            ParseErrorKind::OutOfBounds,
98            format!("offset {offset:#x}, needed {needed} bytes, have {available}"),
99        )
100        .with_location(OffsetSpan {
101            offset,
102            len: needed,
103        })
104    }
105
106    /// Construct an invalid-address error.
107    pub fn address(message: impl Into<String>) -> Self {
108        Self::new(ParseErrorKind::InvalidAddress, message)
109    }
110
111    /// Construct an invalid-load-command error.
112    pub fn command(message: impl Into<String>) -> Self {
113        Self::new(ParseErrorKind::InvalidLoadCommand, message)
114    }
115
116    /// Construct a limit-exhaustion error.
117    pub fn limit(message: impl Into<String>) -> Self {
118        Self::new(ParseErrorKind::LimitExceeded, message)
119    }
120
121    /// Construct an unsupported-input error.
122    pub fn unsupported(message: impl Into<String>) -> Self {
123        Self::new(ParseErrorKind::Unsupported, message)
124    }
125
126    /// Construct a strict-validation error.
127    pub fn validation(message: impl Into<String>) -> Self {
128        Self::new(ParseErrorKind::Validation, message)
129    }
130
131    /// Attach a byte location.
132    pub fn with_location(mut self, location: OffsetSpan) -> Self {
133        self.location = Some(location);
134        self
135    }
136
137    /// Add an outer context frame without flattening the original error.
138    pub fn with_context(mut self, frame: ContextFrame) -> Self {
139        self.context.push(frame);
140        self
141    }
142
143    /// Human-readable detail without the category prefix.
144    pub fn message(&self) -> &str {
145        &self.message
146    }
147
148    /// Stable lowercase dotted diagnostic code for this category.
149    pub const fn code(&self) -> &'static str {
150        match self.kind {
151            ParseErrorKind::InvalidFormat => INVALID_FORMAT_CODE,
152            ParseErrorKind::OutOfBounds => OUT_OF_BOUNDS_CODE,
153            ParseErrorKind::InvalidAddress => INVALID_ADDRESS_CODE,
154            ParseErrorKind::InvalidLoadCommand => INVALID_LOAD_COMMAND_CODE,
155            ParseErrorKind::LimitExceeded => LIMIT_EXCEEDED_CODE,
156            ParseErrorKind::Unsupported => UNSUPPORTED_INPUT_CODE,
157            ParseErrorKind::Validation => VALIDATION_FAILED_CODE,
158        }
159    }
160}
161
162impl fmt::Display for ParseError {
163    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
164        write!(formatter, "{}: {}", self.code(), self.message)?;
165        if let Some(location) = self.location {
166            write!(
167                formatter,
168                " at {:#x}..{:#x}",
169                location.offset,
170                location.offset.saturating_add(location.len)
171            )?;
172        }
173        Ok(())
174    }
175}
176
177impl std::error::Error for ParseError {}
178
179/// Result returned by core parsing and structural accessors.
180pub type ParseResult<T> = core::result::Result<T, ParseError>;
181
182pub(crate) type Error = ParseError;
183pub(crate) type Result<T> = ParseResult<T>;