aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoriximeow <me@iximeow.net>2021-07-03 21:55:23 -0700
committeriximeow <me@iximeow.net>2021-07-03 21:55:23 -0700
commit288ab8fa823d2fbe567e60253c8bad9b2c9b7fd4 (patch)
tree51d3fd061bc5199f85503353e178c7f56cc228b5
parentb49987bdbd2b5a08163a45ef3dc1868754d84165 (diff)
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.
-rw-r--r--Cargo.toml8
-rw-r--r--src/lib.rs91
-rw-r--r--tests/lib.rs57
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 {
+ <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)));
+}