From 288ab8fa823d2fbe567e60253c8bad9b2c9b7fd4 Mon Sep 17 00:00:00 2001 From: iximeow Date: Sat, 3 Jul 2021 21:55:23 -0700 Subject: support std::error::Error included is mandataing a `description` method on `DecodeError` implementors - already approximately required by the Debug and Display anyway. also include a StandardPartialDecoderError for incomplete decoders to use. i expect that one of the last steps of a decoder's 1.0 release will be to move away from this. --- Cargo.toml | 8 +++++- src/lib.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++++++------------ tests/lib.rs | 57 +++++++++++++++++++++++++++++++++++-- 3 files changed, 135 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cb49e90..7674eb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,11 +15,17 @@ version = "0.0.5" "serde" = { version = "1.0", optional = true } "serde_derive" = { version = "1.0", optional = true } +[dev-dependencies] +"anyhow" = "*" +"thiserror" = "*" + [profile.release] lto = true [features] -default = ["use-serde", "colors", "address-parse"] +default = ["std", "use-serde", "colors", "address-parse"] + +std = [] # enables the (optional) use of Serde for bounds on # Arch and Arch::Address diff --git a/src/lib.rs b/src/lib.rs index 121cd1b..038f311 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,7 @@ pub use reader::{Reader, ReadError, U8Reader, U16le, U16be, U32le, U32be, U64le, /// it is permissible for an implementor of `DecodeError` to have items that return `false` for /// all these functions; decoders are permitted to error in way that `yaxpeax-arch` does not know /// about. -pub trait DecodeError { +pub trait DecodeError: PartialEq { /// did the decoder fail because it reached the end of input? fn data_exhausted(&self) -> bool; /// did the decoder error because the instruction's opcode is invalid? @@ -46,6 +46,8 @@ pub trait DecodeError { /// similar to [`DecodeError::bad_opcode`], this is a subjective distinction and best-effort on /// the part of implementors. fn bad_operand(&self) -> bool; + /// a human-friendly description of this decode error. + fn description(&self) -> &'static str; } /// a minimal enum implementing `DecodeError`. this is intended to be enough for a low effort, @@ -57,13 +59,41 @@ pub enum StandardDecodeError { InvalidOperand, } +/// a slightly less minimal enum `DecodeError`. similar to `StandardDecodeError`, this is an +/// anti-boilerplate measure. it additionally provides `IncompleteDecoder`, making it suitable to +/// represent error kinds for decoders that are ... not yet complete. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum StandardPartialDecoderError { + ExhaustedInput, + InvalidOpcode, + InvalidOperand, + IncompleteDecoder, +} + +#[cfg(feature = "std")] +extern crate std; +#[cfg(feature = "std")] +impl std::error::Error for StandardDecodeError { + fn description(&self) -> &str { + ::description(self) + } +} +#[cfg(feature = "std")] +impl std::error::Error for StandardPartialDecoderError { + fn description(&self) -> &str { + ::description(self) + } +} + impl fmt::Display for StandardDecodeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - StandardDecodeError::ExhaustedInput => write!(f, "exhausted input"), - StandardDecodeError::InvalidOpcode => write!(f, "invalid opcode"), - StandardDecodeError::InvalidOperand => write!(f, "invalid operand"), - } + f.write_str(self.description()) + } +} + +impl fmt::Display for StandardPartialDecoderError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.description()) } } @@ -71,6 +101,27 @@ impl DecodeError for StandardDecodeError { fn data_exhausted(&self) -> bool { *self == StandardDecodeError::ExhaustedInput } fn bad_opcode(&self) -> bool { *self == StandardDecodeError::InvalidOpcode } fn bad_operand(&self) -> bool { *self == StandardDecodeError::InvalidOperand } + fn description(&self) -> &'static str { + match self { + StandardDecodeError::ExhaustedInput => "exhausted input", + StandardDecodeError::InvalidOpcode => "invalid opcode", + StandardDecodeError::InvalidOperand => "invalid operand", + } + } +} + +impl DecodeError for StandardPartialDecoderError { + fn data_exhausted(&self) -> bool { *self == StandardPartialDecoderError::ExhaustedInput } + fn bad_opcode(&self) -> bool { *self == StandardPartialDecoderError::InvalidOpcode } + fn bad_operand(&self) -> bool { *self == StandardPartialDecoderError::InvalidOperand } + fn description(&self) -> &'static str { + match self { + StandardPartialDecoderError::ExhaustedInput => "exhausted input", + StandardPartialDecoderError::InvalidOpcode => "invalid opcode", + StandardPartialDecoderError::InvalidOperand => "invalid operand", + StandardPartialDecoderError::IncompleteDecoder => "incomplete decoder", + } + } } /// an interface to decode [`Arch::Instruction`] words from a reader of [`Arch::Word`]s. errors are @@ -95,6 +146,21 @@ pub trait Decoder { fn decode_into>(&self, inst: &mut A::Instruction, words: &mut T) -> Result<(), A::DecodeError>; } +#[cfg(feature = "use-serde")] +pub trait AddressBounds: Address + Debug + Hash + PartialEq + Eq + Serialize + for<'de> Deserialize<'de> {} +#[cfg(not(feature = "use-serde"))] +pub trait AddressBounds: Address + Debug + Hash + PartialEq + Eq {} + +#[cfg(feature = "use-serde")] +impl AddressBounds for T where T: Address + Debug + Hash + PartialEq + Eq + Serialize + for<'de> Deserialize<'de> {} +#[cfg(not(feature = "use-serde"))] +impl AddressBounds for T where T: Address + Debug + Hash + PartialEq + Eq {} + +#[cfg(feature = "std")] +trait DecodeErrorBounds: DecodeError + std::error::Error + Debug + Display {} +#[cfg(not(feature = "std"))] +trait DecodeErrorBounds: DecodeError + Debug + Display {} + /// a collection of associated type parameters that constitute the definitions for an instruction /// set. `Arch` provides an `Instruction` and its associated `Operand`s, which is guaranteed to be /// decodable by this `Arch::Decoder`. `Arch::Decoder` can always be constructed with a `Default` @@ -110,20 +176,9 @@ pub trait Decoder { /// ``` /// /// in some library built on top of `yaxpeax-arch`. -#[cfg(feature="use-serde")] -pub trait Arch { - type Word: Debug + Display + PartialEq + Eq; - type Address: Address + Debug + Hash + PartialEq + Eq + Serialize + for<'de> Deserialize<'de>; - type Instruction: Instruction + LengthedInstruction> + Debug + Default + Sized; - type DecodeError: DecodeError + Debug + Display; - type Decoder: Decoder + Default; - type Operand; -} - -#[cfg(not(feature="use-serde"))] pub trait Arch { type Word: Debug + Display + PartialEq + Eq; - type Address: Address + Debug + Hash + PartialEq + Eq; + type Address: AddressBounds; type Instruction: Instruction + LengthedInstruction> + Debug + Default + Sized; type DecodeError: DecodeError + Debug + Display; type Decoder: Decoder + Default; diff --git a/tests/lib.rs b/tests/lib.rs index 15477f7..8e097de 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,5 +1,3 @@ -#![no_std] - use yaxpeax_arch::AddressBase; #[test] @@ -10,3 +8,58 @@ fn test_u16() { } } } + +#[test] +fn error_can_bail() { + use yaxpeax_arch::{Arch, AddressDiff, Decoder, Reader, LengthedInstruction, Instruction, StandardDecodeError, U8Reader}; + struct TestIsa {} + #[derive(Debug, Default)] + struct TestInst {} + impl Arch for TestIsa { + type Word = u8; + type Address = u64; + type Instruction = TestInst; + type Decoder = TestIsaDecoder; + type DecodeError = StandardDecodeError; + type Operand = (); + } + + impl Instruction for TestInst { + fn well_defined(&self) -> bool { true } + } + + impl LengthedInstruction for TestInst { + type Unit = AddressDiff; + fn len(&self) -> Self::Unit { AddressDiff::from_const(1) } + fn min_size() -> Self::Unit { AddressDiff::from_const(1) } + } + + struct TestIsaDecoder {} + + impl Default for TestIsaDecoder { + fn default() -> Self { + TestIsaDecoder {} + } + } + + impl Decoder for TestIsaDecoder { + fn decode_into>(&self, _inst: &mut TestInst, _words: &mut T) -> Result<(), StandardDecodeError> { + + Err(StandardDecodeError::ExhaustedInput) + } + } + + #[derive(Debug, PartialEq, thiserror::Error)] + pub enum Error { + #[error("decode error")] + TestDecode(#[from] StandardDecodeError), + } + + fn exercise_eq() -> Result<(), Error> { + let mut reader = U8Reader::new(&[]); + TestIsaDecoder::default().decode(&mut reader)?; + Ok(()) + } + + assert_eq!(exercise_eq(), Err(Error::TestDecode(StandardDecodeError::ExhaustedInput))); +} -- cgit v1.1