// machscope — Mach-O and DWARF inspection in pure MoonBit.
//
// The public entry points live in `parser.mbt`. This file holds the shared
// vocabulary: errors, byte-order helpers and the small cursor type that every
// parser in the module reads through.
///|
/// Every way a Mach-O or DWARF buffer can be rejected.
///
/// Requests are never answered with a partially-parsed structure: a malformed
/// image surfaces as one of these variants instead of an out-of-bounds read.
/// Offsets in the payloads are absolute file offsets so that a caller can point
/// at the exact bytes that failed.
pub(all) suberror MachscopeError {
/// The buffer ended before a structure could be read completely.
UnexpectedEnd(offset~ : Int64, needed~ : Int64, available~ : Int64)
/// The magic number does not belong to any format this library understands.
BadMagic(magic~ : UInt32)
/// A field that must take one of a small set of values holds something else.
BadValue(field~ : String, value~ : Int64)
/// A size or offset would address bytes outside the buffer.
OutOfBounds(what~ : String, offset~ : Int64, size~ : Int64, limit~ : Int64)
/// A name field was not valid UTF-8 or contained a NUL inside its bounds.
BadName(offset~ : Int64)
/// A structure is well-formed on its own but contradicts another structure.
Inconsistent(what~ : String, detail~ : String)
/// A limit on nesting, entry count or decoded size was exceeded.
LimitExceeded(what~ : String, limit~ : Int64)
/// A code path this version does not implement was reached.
Unsupported(what~ : String)
}
///|
/// Renders an error as a single line naming the field and the offset that
/// failed.
///
/// The CLI prints this directly, and it is what a caller sees when a corrupt
/// file is rejected: the message has to point at a byte, not just say
/// "malformed".
pub impl Show for MachscopeError with fn output(self, logger) {
let text = match self {
UnexpectedEnd(offset~, needed~, available~) =>
"unexpected end at offset \{offset}: needed \{needed} bytes, \{available} available"
BadMagic(magic~) => "not a Mach-O file: magic 0x\{magic.to_int64()}"
BadValue(field~, value~) => "bad value for \{field}: \{value}"
OutOfBounds(what~, offset~, size~, limit~) =>
"\{what} at offset \{offset}+\{size} exceeds limit \{limit}"
BadName(offset~) => "invalid name field at offset \{offset}"
Inconsistent(what~, detail~) => "\{what} is inconsistent: \{detail}"
LimitExceeded(what~, limit~) => "\{what} exceeds the limit of \{limit}"
Unsupported(what~) => "unsupported: \{what}"
}
logger.write_string(text)
}
///|
/// Exposes the error message as a plain method.
pub extend MachscopeError with Show::{to_string, output}
///|
/// Byte order of the file being read.
///
/// Mach-O files record their own endianness in the magic number; fat headers
/// and DWARF sections inherit the order of the slice or compilation unit that
/// contains them.
pub(all) enum Endian {
/// Least significant byte first (x86, x86_64, arm, arm64).
Little
/// Most significant byte first (legacy PowerPC and m68k images).
Big
}
///|
/// Renders the byte order as the word used by the Mach-O headers.
impl Show for Endian with fn to_string(self) {
match self {
Little => "little-endian"
Big => "big-endian"
}
}
///|
/// The byte order as a short tag, for JSON and CLI output.
pub fn Endian::tag(self : Endian) -> String {
match self {
Little => "le"
Big => "be"
}
}
///|
/// Position of a byte within the buffer, together with the byte order used to
/// decode multi-byte integers at that position.
///
/// A `Cursor` is a value, not a view: advancing returns a new cursor and never
/// touches the shared buffer, so the same source bytes can be read by several
/// parsers without copying.
pub(all) struct Cursor {
/// Index of the next byte to be read.
pos : Int64
/// Byte order applied by `read_u16`, `read_u32` and `read_u64`.
endian : Endian
}