diff options
-rw-r--r-- | CHANGELOG | 15 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | Makefile | 14 | ||||
-rw-r--r-- | src/lib.rs | 19 | ||||
-rw-r--r-- | src/long_mode/mod.rs | 75 | ||||
-rw-r--r-- | src/protected_mode/mod.rs | 75 | ||||
-rw-r--r-- | src/real_mode/mod.rs | 74 | ||||
-rw-r--r-- | test/long_mode/display.rs | 4 | ||||
-rw-r--r-- | test/long_mode/evex_generated.rs | 50 | ||||
-rw-r--r-- | test/long_mode/mod.rs | 49 | ||||
-rw-r--r-- | test/long_mode/regspec.rs | 2 | ||||
-rw-r--r-- | test/protected_mode/display.rs | 4 | ||||
-rw-r--r-- | test/protected_mode/evex_generated.rs | 50 | ||||
-rw-r--r-- | test/protected_mode/mod.rs | 49 | ||||
-rw-r--r-- | test/protected_mode/regspec.rs | 2 | ||||
-rw-r--r-- | test/real_mode/mod.rs | 49 |
16 files changed, 376 insertions, 156 deletions
@@ -1,3 +1,18 @@ +## 1.1.5 +* `Makefile` at the crate root now exercises `yaxpeax-x86` builds and tests under: + - default features (fmt, std) + - no-std + fmt + - no-std and no-fmt +* fix several issues prohibiting builds of the crate with no-std +fmt + configurations; the required Display impl for field annotation would simply not + compile in no-fmt builds. it is now a minimal implementation to comply with the + goals of `no-fmt`: "avoid as much extra code and data for formatting + instructions as possible". two dozen bytes for a likely-DCE'd codepath should + be ok. +* adjust test structure so that exhaustive tests can be `#[ignored]` and + explicitly run anyway for completeness. this means the ignored at&t tests now + are both ignored and appear to succeed when run. + ## 1.1.4 * fix reachable unreachable under `DisplayStyle::C` in 64-, 32-, and 16-bit modes * add fuzz target to cover `DisplayStyle::C` formatter for 64-, 32-, and 16-bit modes @@ -15,6 +15,7 @@ yaxpeax-arch = { version = "0.2.7", default-features = false, features = [] } "serde" = { version = "1.0", optional = true } "serde_json" = { version = "1.0", optional = true } "serde_derive" = { version = "1.0", optional = true } +"cfg-if" = "1.0.0" [dev-dependencies] rand = "0.8.4" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c045d31 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +test: test-fast test-exhaustive + +test-fast: test-std test-no-std test-fmt-no-std + +test-exhaustive: + cargo test -- --ignored + cargo test --no-default-features -- --ignored + +test-std: + cargo test +test-no-std: + cargo test --no-default-features +test-fmt-no-std: + cargo test --no-default-features --features "fmt" @@ -12,6 +12,7 @@ //! //! let inst = decoder.decode_slice(&[0x33, 0xc0]).unwrap(); //! +//! #[cfg(features="fmt")] //! assert_eq!("xor eax, eax", inst.to_string()); //! ``` //! @@ -31,13 +32,16 @@ //! //! let inst = decoder.decode_slice(&[0x33, 0x01]).unwrap(); //! +//! #[cfg(features="fmt")] //! assert_eq!("xor eax, dword [rcx]", inst.to_string()); //! //! assert_eq!(Operand::Register(RegSpec::eax()), inst.operand(0)); +//! #[cfg(features="fmt")] //! assert_eq!("eax", inst.operand(0).to_string()); //! assert_eq!(Operand::RegDeref(RegSpec::rcx()), inst.operand(1)); //! //! // an operand in isolation does not know the size of memory it references, if any +//! #[cfg(features="fmt")] //! assert_eq!("[rcx]", inst.operand(1).to_string()); //! //! // and for memory operands, the size must be read from the instruction itself: @@ -45,6 +49,7 @@ //! assert_eq!("dword", mem_size.size_name()); //! //! // `MemoryAccessSize::size_name()` is how its `Display` impl works, as well: +//! #[cfg(features="fmt")] //! assert_eq!("dword", mem_size.to_string()); //! ``` //! @@ -54,12 +59,23 @@ //! mod decoder { //! use yaxpeax_arch::{Arch, AddressDisplay, Decoder, Reader, ReaderBuilder}; //! +//! // have to play some games so this example works right even without `fmt` enabled! +//! #[cfg(feature="fmt")] +//! trait InstBound: std::fmt::Display {} +//! #[cfg(not(feature="fmt"))] +//! trait InstBound {} +//! +//! #[cfg(feature="fmt")] +//! impl <T: std::fmt::Display> InstBound for T {} +//! #[cfg(not(feature="fmt"))] +//! impl <T> InstBound for T {} +//! //! pub fn decode_stream< //! 'data, //! A: yaxpeax_arch::Arch, //! U: ReaderBuilder<A::Address, A::Word>, //! >(data: U) where -//! A::Instruction: std::fmt::Display, +//! A::Instruction: InstBound, //! { //! let mut reader = ReaderBuilder::read_from(data); //! let mut address: A::Address = reader.total_offset(); @@ -69,6 +85,7 @@ //! loop { //! match decode_res { //! Ok(ref inst) => { +//! #[cfg(feature="fmt")] //! println!("{}: {}", address.show(), inst); //! decode_res = decoder.decode(&mut reader); //! address = reader.total_offset(); diff --git a/src/long_mode/mod.rs b/src/long_mode/mod.rs index d2cb2f7..6475d4e 100644 --- a/src/long_mode/mod.rs +++ b/src/long_mode/mod.rs @@ -888,11 +888,13 @@ const REGISTER_CLASS_NAMES: &[&'static str] = &[ /// } /// /// if let Operand::Register(regspec) = instruction.operand(0) { +/// #[cfg(feature="fmt")] /// println!("first operand is {}", regspec); /// show_register_class_info(regspec.class()); /// } /// /// if let Operand::Register(regspec) = instruction.operand(1) { +/// #[cfg(feature="fmt")] /// println!("first operand is {}", regspec); /// show_register_class_info(regspec.class()); /// } @@ -7422,43 +7424,54 @@ impl InnerDescription { } } -impl fmt::Display for InnerDescription { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - InnerDescription::RexPrefix(bits) => { - write!(f, "rex prefix: {}{}{}{}", - if bits & 0x8 != 0 { "w" } else { "-" }, - if bits & 0x4 != 0 { "r" } else { "-" }, - if bits & 0x2 != 0 { "x" } else { "-" }, - if bits & 0x1 != 0 { "b" } else { "-" }, - ) - } - InnerDescription::SegmentPrefix(segment) => { - write!(f, "segment override: {}", segment) - } - InnerDescription::Misc(text) => { - f.write_str(text) - } - InnerDescription::Number(text, num) => { - write!(f, "{}: {:#x}", text, num) - } - InnerDescription::Opcode(opc) => { - write!(f, "opcode `{}`", opc) - } - InnerDescription::OperandCode(OperandCodeWrapper { code }) => { - write!(f, "operand code `{:?}`", code) - } - InnerDescription::RegisterNumber(name, num, reg) => { - write!(f, "`{}` (`{}` selects register number {})", reg, name, num) +cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + impl fmt::Display for InnerDescription { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + InnerDescription::RexPrefix(bits) => { + write!(f, "rex prefix: {}{}{}{}", + if bits & 0x8 != 0 { "w" } else { "-" }, + if bits & 0x4 != 0 { "r" } else { "-" }, + if bits & 0x2 != 0 { "x" } else { "-" }, + if bits & 0x1 != 0 { "b" } else { "-" }, + ) + } + InnerDescription::SegmentPrefix(segment) => { + write!(f, "segment override: {}", segment) + } + InnerDescription::Misc(text) => { + f.write_str(text) + } + InnerDescription::Number(text, num) => { + write!(f, "{}: {:#x}", text, num) + } + InnerDescription::Opcode(opc) => { + write!(f, "opcode `{}`", opc) + } + InnerDescription::OperandCode(OperandCodeWrapper { code }) => { + write!(f, "operand code `{:?}`", code) + } + InnerDescription::RegisterNumber(name, num, reg) => { + write!(f, "`{}` (`{}` selects register number {})", reg, name, num) + } + InnerDescription::Boundary(desc) => { + write!(f, "{}", desc) + } + } } - InnerDescription::Boundary(desc) => { - write!(f, "{}", desc) + } + } else { + impl fmt::Display for InnerDescription { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("non-fmt build") } } } } -#[derive(Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature="fmt", derive(Debug))] +#[derive(Clone, PartialEq, Eq)] pub struct FieldDescription { desc: InnerDescription, id: u32, diff --git a/src/protected_mode/mod.rs b/src/protected_mode/mod.rs index 0a6fcf8..dd63d32 100644 --- a/src/protected_mode/mod.rs +++ b/src/protected_mode/mod.rs @@ -833,11 +833,13 @@ const REGISTER_CLASS_NAMES: &[&'static str] = &[ /// } /// /// if let Operand::Register(regspec) = instruction.operand(0) { +/// #[cfg(feature="fmt")] /// println!("first operand is {}", regspec); /// show_register_class_info(regspec.class()); /// } /// /// if let Operand::Register(regspec) = instruction.operand(1) { +/// #[cfg(feature="fmt")] /// println!("first operand is {}", regspec); /// show_register_class_info(regspec.class()); /// } @@ -7386,7 +7388,8 @@ fn read_0f3a_opcode(opcode: u8, prefixes: &mut Prefixes) -> OpcodeRecord { }; } -/// the actual description for a selection of bits involved in decoding an [`long_mode::Instruction`]. +/// the actual description for a selection of bits involved in decoding a +/// [`protected_mode::Instruction`]. /// /// some prefixes are only identified as an `InnerDescription::Misc` string, while some are full /// `InnerDescription::SegmentPrefix(Segment)`. generally, strings should be considered unstable @@ -7439,37 +7442,47 @@ impl InnerDescription { } } -impl fmt::Display for InnerDescription { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - InnerDescription::RexPrefix(bits) => { - write!(f, "rex prefix: {}{}{}{}", - if bits & 0x8 != 0 { "w" } else { "-" }, - if bits & 0x4 != 0 { "r" } else { "-" }, - if bits & 0x2 != 0 { "x" } else { "-" }, - if bits & 0x1 != 0 { "b" } else { "-" }, - ) - } - InnerDescription::SegmentPrefix(segment) => { - write!(f, "segment override: {}", segment) - } - InnerDescription::Misc(text) => { - f.write_str(text) - } - InnerDescription::Number(text, num) => { - write!(f, "{}: {:#x}", text, num) - } - InnerDescription::Opcode(opc) => { - write!(f, "opcode `{}`", opc) - } - InnerDescription::OperandCode(OperandCodeWrapper { code }) => { - write!(f, "operand code `{:?}`", code) - } - InnerDescription::RegisterNumber(name, num, reg) => { - write!(f, "`{}` (`{}` selects register number {})", reg, name, num) +cfg_if::cfg_if! { + if #[cfg(feature = "fmt")] { + impl fmt::Display for InnerDescription { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + InnerDescription::RexPrefix(bits) => { + write!(f, "rex prefix: {}{}{}{}", + if bits & 0x8 != 0 { "w" } else { "-" }, + if bits & 0x4 != 0 { "r" } else { "-" }, + if bits & 0x2 != 0 { "x" } else { "-" }, + if bits & 0x1 != 0 { "b" } else { "-" }, + ) + } + InnerDescription::SegmentPrefix(segment) => { + write!(f, "segment override: {}", segment) + } + InnerDescription::Misc(text) => { + f.write_str(text) + } + InnerDescription::Number(text, num) => { + write!(f, "{}: {:#x}", text, num) + } + InnerDescription::Opcode(opc) => { + write!(f, "opcode `{}`", opc) + } + InnerDescription::OperandCode(OperandCodeWrapper { code }) => { + write!(f, "operand code `{:?}`", code) + } + InnerDescription::RegisterNumber(name, num, reg) => { + write!(f, "`{}` (`{}` selects register number {})", reg, name, num) + } + InnerDescription::Boundary(desc) => { + write!(f, "{}", desc) + } + } } - InnerDescription::Boundary(desc) => { - write!(f, "{}", desc) + } + } else { + impl fmt::Display for InnerDescription { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("non-fmt build") } } } diff --git a/src/real_mode/mod.rs b/src/real_mode/mod.rs index 4b8ad6d..d7fda17 100644 --- a/src/real_mode/mod.rs +++ b/src/real_mode/mod.rs @@ -833,11 +833,13 @@ const REGISTER_CLASS_NAMES: &[&'static str] = &[ /// } /// /// if let Operand::Register(regspec) = instruction.operand(0) { +/// #[cfg(feature="fmt")] /// println!("first operand is {}", regspec); /// show_register_class_info(regspec.class()); /// } /// /// if let Operand::Register(regspec) = instruction.operand(1) { +/// #[cfg(feature="fmt")] /// println!("first operand is {}", regspec); /// show_register_class_info(regspec.class()); /// } @@ -7388,7 +7390,7 @@ fn read_0f3a_opcode(opcode: u8, prefixes: &mut Prefixes) -> OpcodeRecord { }; } -/// the actual description for a selection of bits involved in decoding an [`long_mode::Instruction`]. +/// the actual description for a selection of bits involved in decoding a [`real_mode::Instruction`]. /// /// some prefixes are only identified as an `InnerDescription::Misc` string, while some are full /// `InnerDescription::SegmentPrefix(Segment)`. generally, strings should be considered unstable @@ -7441,37 +7443,47 @@ impl InnerDescription { } } -impl fmt::Display for InnerDescription { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - InnerDescription::RexPrefix(bits) => { - write!(f, "rex prefix: {}{}{}{}", - if bits & 0x8 != 0 { "w" } else { "-" }, - if bits & 0x4 != 0 { "r" } else { "-" }, - if bits & 0x2 != 0 { "x" } else { "-" }, - if bits & 0x1 != 0 { "b" } else { "-" }, - ) - } - InnerDescription::SegmentPrefix(segment) => { - write!(f, "segment override: {}", segment) - } - InnerDescription::Misc(text) => { - f.write_str(text) - } - InnerDescription::Number(text, num) => { - write!(f, "{}: {:#x}", text, num) - } - InnerDescription::Opcode(opc) => { - write!(f, "opcode `{}`", opc) - } - InnerDescription::OperandCode(OperandCodeWrapper { code }) => { - write!(f, "operand code `{:?}`", code) - } - InnerDescription::RegisterNumber(name, num, reg) => { - write!(f, "`{}` (`{}` selects register number {})", reg, name, num) +cfg_if::cfg_if! { + if #[cfg(feature = "fmt")] { + impl fmt::Display for InnerDescription { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + InnerDescription::RexPrefix(bits) => { + write!(f, "rex prefix: {}{}{}{}", + if bits & 0x8 != 0 { "w" } else { "-" }, + if bits & 0x4 != 0 { "r" } else { "-" }, + if bits & 0x2 != 0 { "x" } else { "-" }, + if bits & 0x1 != 0 { "b" } else { "-" }, + ) + } + InnerDescription::SegmentPrefix(segment) => { + write!(f, "segment override: {}", segment) + } + InnerDescription::Misc(text) => { + f.write_str(text) + } + InnerDescription::Number(text, num) => { + write!(f, "{}: {:#x}", text, num) + } + InnerDescription::Opcode(opc) => { + write!(f, "opcode `{}`", opc) + } + InnerDescription::OperandCode(OperandCodeWrapper { code }) => { + write!(f, "operand code `{:?}`", code) + } + InnerDescription::RegisterNumber(name, num, reg) => { + write!(f, "`{}` (`{}` selects register number {})", reg, name, num) + } + InnerDescription::Boundary(desc) => { + write!(f, "{}", desc) + } + } } - InnerDescription::Boundary(desc) => { - write!(f, "{}", desc) + } + } else { + impl fmt::Display for InnerDescription { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("non-fmt build") } } } diff --git a/test/long_mode/display.rs b/test/long_mode/display.rs index fedb183..22fd787 100644 --- a/test/long_mode/display.rs +++ b/test/long_mode/display.rs @@ -41,9 +41,13 @@ fn test_display_under(decoder: &InstDecoder, style: DisplayStyle, data: &[u8], e // decided i do not like at&t syntax much at all. not going to write a formatter for it. some test // cases will live on in case someone else feels like adding one, or i get mad enough to do it. +#[allow(unreachable_code)] #[ignore] #[test] fn test_instructions_atnt() { + // `ignore` is now used to avoid running (slow!) exhaustive tests in a default `cargo test`. + // running exhaustive tests now runs these tests, which fail. so instead, return early. + return; // just modrm test_display(&[0x33, 0x08], "xor (%rax), %ecx"); test_display(&[0x33, 0x20], "xor (%rax), %esp"); diff --git a/test/long_mode/evex_generated.rs b/test/long_mode/evex_generated.rs index dcb4e01..46d99a7 100644 --- a/test/long_mode/evex_generated.rs +++ b/test/long_mode/evex_generated.rs @@ -11,7 +11,17 @@ fn test_invalid(data: &[u8]) { fn test_invalid_under(decoder: &InstDecoder, data: &[u8]) { let mut reader = U8Reader::new(data); if let Ok(inst) = decoder.decode(&mut reader) { - panic!("decoded {:?} from {:02x?} under decoder {}", inst.opcode(), data, decoder); + // realistically, the chances an error only shows up under non-fmt builds seems unlikely, + // but try to report *something* in such cases. + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + panic!("decoded {:?} from {:02x?} under decoder {}", inst.opcode(), data, decoder); + } else { + // don't warn about the unused inst here + let _ = inst; + panic!("decoded instruction <non-fmt build> from {:02x?} under decoder <non-fmt build>", data); + } + } } else { // this is fine } @@ -27,25 +37,39 @@ fn test_display_under(decoder: &InstDecoder, data: &[u8], expected: &'static str for b in data { write!(hex, "{:02x}", b).unwrap(); } - let mut reader = U8Reader::new(data); + let mut reader = yaxpeax_arch::U8Reader::new(data); match decoder.decode(&mut reader) { Ok(instr) => { - let text = format!("{}", instr); - assert!( - text == expected, - "display error for {}:\n decoded: {:?} under decoder {}\n displayed: {}\n expected: {}\n", - hex, - instr, - decoder, - text, - expected - ); + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + let text = format!("{}", instr); + assert!( + text == expected, + "display error for {}:\n decoded: {:?} under decoder {}\n displayed: {}\n expected: {}\n", + hex, + instr, + decoder, + text, + expected + ); + } else { + eprintln!("non-fmt build cannot compare text equality") + } + } // while we're at it, test that the instruction is as long, and no longer, than its // input assert_eq!((0u64.wrapping_offset(instr.len()).to_linear()) as usize, data.len(), "instruction length is incorrect, wanted instruction {}", expected); }, Err(e) => { - assert!(false, "decode error ({}) for {} under decoder {}:\n expected: {}\n", e, hex, decoder, expected); + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + assert!(false, "decode error ({}) for {} under decoder {}:\n expected: {}\n", e, hex, decoder, expected); + } else { + // avoid the unused `e` warning + let _ = e; + assert!(false, "decode error (<non-fmt build>) for {} under decoder <non-fmt build>:\n expected: {}\n", hex, expected); + } + } } } } diff --git a/test/long_mode/mod.rs b/test/long_mode/mod.rs index 97e9a74..2a11b79 100644 --- a/test/long_mode/mod.rs +++ b/test/long_mode/mod.rs @@ -2,6 +2,7 @@ extern crate rand; mod regspec; mod operand; +#[cfg(feature="fmt")] mod display; mod evex_generated; mod reuse_test; @@ -18,7 +19,17 @@ fn test_invalid(data: &[u8]) { fn test_invalid_under(decoder: &InstDecoder, data: &[u8]) { let mut reader = yaxpeax_arch::U8Reader::new(data); if let Ok(inst) = decoder.decode(&mut reader) { - panic!("decoded {:?} from {:02x?} under decoder {}", inst.opcode(), data, decoder); + // realistically, the chances an error only shows up under non-fmt builds seems unlikely, + // but try to report *something* in such cases. + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + panic!("decoded {:?} from {:02x?} under decoder {}", inst.opcode(), data, decoder); + } else { + // don't warn about the unused inst here + let _ = inst; + panic!("decoded instruction <non-fmt build> from {:02x?} under decoder <non-fmt build>", data); + } + } } else { // this is fine } @@ -36,22 +47,36 @@ fn test_display_under(decoder: &InstDecoder, data: &[u8], expected: &'static str let mut reader = yaxpeax_arch::U8Reader::new(data); match decoder.decode(&mut reader) { Ok(instr) => { - let text = format!("{}", instr); - assert!( - text == expected, - "display error for {}:\n decoded: {:?} under decoder {}\n displayed: {}\n expected: {}\n", - hex, - instr, - decoder, - text, - expected - ); + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + let text = format!("{}", instr); + assert!( + text == expected, + "display error for {}:\n decoded: {:?} under decoder {}\n displayed: {}\n expected: {}\n", + hex, + instr, + decoder, + text, + expected + ); + } else { + eprintln!("non-fmt build cannot compare text equality") + } + } // while we're at it, test that the instruction is as long, and no longer, than its // input assert_eq!((0u64.wrapping_offset(instr.len()).to_linear()) as usize, data.len(), "instruction length is incorrect, wanted instruction {}", expected); }, Err(e) => { - assert!(false, "decode error ({}) for {} under decoder {}:\n expected: {}\n", e, hex, decoder, expected); + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + assert!(false, "decode error ({}) for {} under decoder {}:\n expected: {}\n", e, hex, decoder, expected); + } else { + // avoid the unused `e` warning + let _ = e; + assert!(false, "decode error (<non-fmt build>) for {} under decoder <non-fmt build>:\n expected: {}\n", hex, expected); + } + } } } } diff --git a/test/long_mode/regspec.rs b/test/long_mode/regspec.rs index f92ec89..220435f 100644 --- a/test/long_mode/regspec.rs +++ b/test/long_mode/regspec.rs @@ -11,6 +11,7 @@ fn test_hash() { let _: HashMap<RegSpec, u64> = HashMap::new(); } +#[cfg(features="fmt")] #[test] fn test_labels() { assert_eq!(RegSpec::rip().name(), "rip"); @@ -21,6 +22,7 @@ fn test_labels() { assert_eq!(RegSpec::al().name(), "al"); } +#[cfg(features="fmt")] #[test] fn test_bank_names() { assert_eq!(RegSpec::al().class().name(), "byte"); diff --git a/test/protected_mode/display.rs b/test/protected_mode/display.rs index 5f0c68d..f9e0d6a 100644 --- a/test/protected_mode/display.rs +++ b/test/protected_mode/display.rs @@ -41,9 +41,13 @@ fn test_display_under(decoder: &InstDecoder, style: DisplayStyle, data: &[u8], e // decided i do not like at&t syntax much at all. not going to write a formatter for it. some test // cases will live on in case someone else feels like adding one, or i get mad enough to do it. +#[allow(unreachable_code)] #[ignore] #[test] fn test_instructions_atnt() { + // `ignore` is now used to avoid running (slow!) exhaustive tests in a default `cargo test`. + // running exhaustive tests now runs these tests, which fail. so instead, return early. + return; // just modrm test_display(&[0x33, 0x08], "xor (%eax), %ecx"); test_display(&[0x33, 0x20], "xor (%eax), %esp"); diff --git a/test/protected_mode/evex_generated.rs b/test/protected_mode/evex_generated.rs index 236edec..9c3a06e 100644 --- a/test/protected_mode/evex_generated.rs +++ b/test/protected_mode/evex_generated.rs @@ -11,7 +11,17 @@ fn test_invalid(data: &[u8]) { fn test_invalid_under(decoder: &InstDecoder, data: &[u8]) { let mut reader = U8Reader::new(data); if let Ok(inst) = decoder.decode(&mut reader) { - panic!("decoded {:?} from {:02x?} under decoder {}", inst.opcode(), data, decoder); + // realistically, the chances an error only shows up under non-fmt builds seems unlikely, + // but try to report *something* in such cases. + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + panic!("decoded {:?} from {:02x?} under decoder {}", inst.opcode(), data, decoder); + } else { + // don't warn about the unused inst here + let _ = inst; + panic!("decoded instruction <non-fmt build> from {:02x?} under decoder <non-fmt build>", data); + } + } } else { // this is fine } @@ -27,25 +37,39 @@ fn test_display_under(decoder: &InstDecoder, data: &[u8], expected: &'static str for b in data { write!(hex, "{:02x}", b).unwrap(); } - let mut reader = U8Reader::new(data); + let mut reader = yaxpeax_arch::U8Reader::new(data); match decoder.decode(&mut reader) { Ok(instr) => { - let text = format!("{}", instr); - assert!( - text == expected, - "display error for {}:\n decoded: {:?} under decoder {}\n displayed: {}\n expected: {}\n", - hex, - instr, - decoder, - text, - expected - ); + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + let text = format!("{}", instr); + assert!( + text == expected, + "display error for {}:\n decoded: {:?} under decoder {}\n displayed: {}\n expected: {}\n", + hex, + instr, + decoder, + text, + expected + ); + } else { + eprintln!("non-fmt build cannot compare text equality") + } + } // while we're at it, test that the instruction is as long, and no longer, than its // input assert_eq!((0u32.wrapping_offset(instr.len()).to_linear()) as usize, data.len(), "instruction length is incorrect, wanted instruction {}", expected); }, Err(e) => { - assert!(false, "decode error ({}) for {} under decoder {}:\n expected: {}\n", e, hex, decoder, expected); + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + assert!(false, "decode error ({}) for {} under decoder {}:\n expected: {}\n", e, hex, decoder, expected); + } else { + // avoid the unused `e` warning + let _ = e; + assert!(false, "decode error (<non-fmt build>) for {} under decoder <non-fmt build>:\n expected: {}\n", hex, expected); + } + } } } } diff --git a/test/protected_mode/mod.rs b/test/protected_mode/mod.rs index 744d006..9cda6be 100644 --- a/test/protected_mode/mod.rs +++ b/test/protected_mode/mod.rs @@ -1,5 +1,6 @@ mod regspec; mod operand; +#[cfg(feature="fmt")] mod display; mod evex_generated; @@ -15,7 +16,17 @@ fn test_invalid(data: &[u8]) { fn test_invalid_under(decoder: &InstDecoder, data: &[u8]) { let mut reader = yaxpeax_arch::U8Reader::new(data); if let Ok(inst) = decoder.decode(&mut reader) { - panic!("decoded {:?} from {:02x?} under decoder {}", inst.opcode(), data, decoder); + // realistically, the chances an error only shows up under non-fmt builds seems unlikely, + // but try to report *something* in such cases. + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + panic!("decoded {:?} from {:02x?} under decoder {}", inst.opcode(), data, decoder); + } else { + // don't warn about the unused inst here + let _ = inst; + panic!("decoded instruction <non-fmt build> from {:02x?} under decoder <non-fmt build>", data); + } + } } else { // this is fine } @@ -33,22 +44,36 @@ fn test_display_under(decoder: &InstDecoder, data: &[u8], expected: &'static str let mut reader = yaxpeax_arch::U8Reader::new(data); match decoder.decode(&mut reader) { Ok(instr) => { - let text = format!("{}", instr); - assert!( - text == expected, - "display error for {}:\n decoded: {:?} under decoder {}\n displayed: {}\n expected: {}\n", - hex, - instr, - decoder, - text, - expected - ); + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + let text = format!("{}", instr); + assert!( + text == expected, + "display error for {}:\n decoded: {:?} under decoder {}\n displayed: {}\n expected: {}\n", + hex, + instr, + decoder, + text, + expected + ); + } else { + eprintln!("non-fmt build cannot compare text equality") + } + } // while we're at it, test that the instruction is as long, and no longer, than its // input assert_eq!((0u32.wrapping_offset(instr.len()).to_linear()) as usize, data.len(), "instruction length is incorrect, wanted instruction {}", expected); }, Err(e) => { - assert!(false, "decode error ({}) for {} under decoder {}:\n expected: {}\n", e, hex, decoder, expected); + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + assert!(false, "decode error ({}) for {} under decoder {}:\n expected: {}\n", e, hex, decoder, expected); + } else { + // avoid the unused `e` warning + let _ = e; + assert!(false, "decode error (<non-fmt build>) for {} under decoder <non-fmt build>:\n expected: {}\n", hex, expected); + } + } } } } diff --git a/test/protected_mode/regspec.rs b/test/protected_mode/regspec.rs index 7d43c7a..aeca086 100644 --- a/test/protected_mode/regspec.rs +++ b/test/protected_mode/regspec.rs @@ -11,6 +11,7 @@ fn test_hash() { let _: HashMap<RegSpec, u64> = HashMap::new(); } +#[cfg(features="fmt")] #[test] fn test_labels() { assert_eq!(RegSpec::eip().name(), "eip"); @@ -19,6 +20,7 @@ fn test_labels() { assert_eq!(RegSpec::al().name(), "al"); } +#[cfg(features="fmt")] #[test] fn test_bank_names() { assert_eq!(RegSpec::al().class().name(), "byte"); diff --git a/test/real_mode/mod.rs b/test/real_mode/mod.rs index 60f4c47..dcd124c 100644 --- a/test/real_mode/mod.rs +++ b/test/real_mode/mod.rs @@ -12,7 +12,17 @@ fn test_invalid(data: &[u8]) { fn test_invalid_under(decoder: &InstDecoder, data: &[u8]) { let mut reader = U8Reader::new(data); if let Ok(inst) = decoder.decode(&mut reader) { - panic!("decoded {:?} from {:02x?} under decoder {}", inst.opcode(), data, decoder); + // realistically, the chances an error only shows up under non-fmt builds seems unlikely, + // but try to report *something* in such cases. + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + panic!("decoded {:?} from {:02x?} under decoder {}", inst.opcode(), data, decoder); + } else { + // don't warn about the unused inst here + let _ = inst; + panic!("decoded instruction <non-fmt build> from {:02x?} under decoder <non-fmt build>", data); + } + } } else { // this is fine } @@ -31,22 +41,37 @@ fn test_display_under(decoder: &InstDecoder, data: &[u8], expected: &'static str let mut reader = U8Reader::new(data); match decoder.decode(&mut reader) { Ok(instr) => { - let text = format!("{}", instr); - assert!( - text == expected, - "display error for {}:\n decoded: {:?} under decoder {}\n displayed: {}\n expected: {}\n", - hex, - instr, - decoder, - text, - expected - ); + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + let text = format!("{}", instr); + assert!( + text == expected, + "display error for {}:\n decoded: {:?} under decoder {}\n displayed: {}\n expected: {}\n", + hex, + instr, + decoder, + text, + expected + ); + } else { + eprintln!("non-fmt build cannot compare text equality") + } + } // while we're at it, test that the instruction is as long, and no longer, than its // input assert_eq!((0u32.wrapping_offset(instr.len()).to_linear()) as usize, data.len(), "instruction length is incorrect, wanted instruction {}", expected); }, Err(e) => { - assert!(false, "decode error ({}) for {} under decoder {}:\n expected: {}\n", e, hex, decoder, expected); + cfg_if::cfg_if! { + if #[cfg(feature="fmt")] { + assert!(false, "decode error ({}) for {} under decoder {}:\n expected: {}\n", e, hex, decoder, expected); + } else { + // avoid the unused `e` warning + let _ = e; + assert!(false, "decode error (<non-fmt build>) for {} under decoder <non-fmt build>:\n expected: {}\n", hex, expected); + } + } + } } } |