diff options
| -rw-r--r-- | Cargo.toml | 8 | ||||
| -rw-r--r-- | src/lib.rs | 91 | ||||
| -rw-r--r-- | tests/lib.rs | 57 | 
3 files changed, 135 insertions, 21 deletions
| @@ -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 @@ -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 { +        <Self as DecodeError>::description(self) +    } +} +#[cfg(feature = "std")] +impl std::error::Error for StandardPartialDecoderError { +    fn description(&self) -> &str { +        <Self as DecodeError>::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<A: Arch + ?Sized> {      fn decode_into<T: Reader<A::Address, A::Word>>(&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<T> AddressBounds for T where T: Address + Debug + Hash + PartialEq + Eq + Serialize + for<'de> Deserialize<'de> {} +#[cfg(not(feature = "use-serde"))] +impl<T> 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<A: Arch + ?Sized> {  /// ```  ///  /// 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<Unit=AddressDiff<Self::Address>> + Debug + Default + Sized; -    type DecodeError: DecodeError + Debug + Display; -    type Decoder: Decoder<Self> + 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<Unit=AddressDiff<Self::Address>> + Debug + Default + Sized;      type DecodeError: DecodeError + Debug + Display;      type Decoder: Decoder<Self> + 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<u64>; +        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<TestIsa> for TestIsaDecoder { +        fn decode_into<T: Reader<u64, u8>>(&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))); +} | 
