From 2097524c851b15e89091fd3775817a06f0eeae4f Mon Sep 17 00:00:00 2001 From: iximeow Date: Sun, 24 Apr 2022 17:39:21 -0700 Subject: fix a few issues preventing no-std builds from ... building this includes a `Makefile` that exercises the various crate configs. most annoyingly, several doc comments needed to grow `#[cfg(feature="fmt")]` blocks so docs continue to build with that feature enabled or disabled. carved out a way to run exhaustive tests; they should be written as `#[ignore]`, and then the makefile will run even ignored tests on the expectation that this will run the exhaustive (but slower) suite. exhaustive tests are not yet written. they'll probably involve spanning 4 byte sequences from 0 to 2^32-1. --- CHANGELOG | 15 +++++++ Cargo.toml | 1 + Makefile | 14 +++++++ src/lib.rs | 19 ++++++++- src/long_mode/mod.rs | 75 ++++++++++++++++++++--------------- src/protected_mode/mod.rs | 75 ++++++++++++++++++++--------------- src/real_mode/mod.rs | 74 +++++++++++++++++++--------------- test/long_mode/display.rs | 4 ++ test/long_mode/evex_generated.rs | 50 +++++++++++++++++------ test/long_mode/mod.rs | 49 +++++++++++++++++------ test/long_mode/regspec.rs | 2 + test/protected_mode/display.rs | 4 ++ test/protected_mode/evex_generated.rs | 50 +++++++++++++++++------ test/protected_mode/mod.rs | 49 +++++++++++++++++------ test/protected_mode/regspec.rs | 2 + test/real_mode/mod.rs | 49 +++++++++++++++++------ 16 files changed, 376 insertions(+), 156 deletions(-) create mode 100644 Makefile diff --git a/CHANGELOG b/CHANGELOG index 74ee269..4227196 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 877d99c..a440d12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/lib.rs b/src/lib.rs index b8bd4f7..9825fd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 InstBound for T {} +//! #[cfg(not(feature="fmt"))] +//! impl InstBound for T {} +//! //! pub fn decode_stream< //! 'data, //! A: yaxpeax_arch::Arch, //! U: ReaderBuilder, //! >(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 from {:02x?} under decoder ", 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 () for {} under decoder :\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 from {:02x?} under decoder ", 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 () for {} under decoder :\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 = 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 from {:02x?} under decoder ", 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 () for {} under decoder :\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 from {:02x?} under decoder ", 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 () for {} under decoder :\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 = 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 from {:02x?} under decoder ", 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 () for {} under decoder :\n expected: {}\n", hex, expected); + } + } + } } } -- cgit v1.1