use crate::metadata::token::Token;
use std::fmt::{self, UpperHex};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OperandType {
None,
Int8,
UInt8,
Int16,
UInt16,
Int32,
UInt32,
Int64,
UInt64,
Float32,
Float64,
Token,
Switch,
}
impl OperandType {
#[must_use]
pub const fn size(&self) -> Option<usize> {
match self {
OperandType::None => Some(0),
OperandType::Int8 | OperandType::UInt8 => Some(1),
OperandType::Int16 | OperandType::UInt16 => Some(2),
OperandType::Int32
| OperandType::UInt32
| OperandType::Float32
| OperandType::Token => Some(4),
OperandType::Int64 | OperandType::UInt64 | OperandType::Float64 => Some(8),
OperandType::Switch => None, }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Immediate {
Int8(i8),
UInt8(u8),
Int16(i16),
UInt16(u16),
Int32(i32),
UInt32(u32),
Int64(i64),
UInt64(u64),
Float32(f32),
Float64(f64),
}
impl UpperHex for Immediate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Immediate::Int8(value) => write!(f, "{value:02X}"),
Immediate::UInt8(value) => write!(f, "{value:02X}"),
Immediate::Int16(value) => write!(f, "{value:04X}"),
Immediate::UInt16(value) => write!(f, "{value:04X}"),
Immediate::Int32(value) => write!(f, "{value:08X}"),
Immediate::UInt32(value) => write!(f, "{value:08X}"),
Immediate::Int64(value) => write!(f, "{value:016X}"),
Immediate::UInt64(value) => write!(f, "{value:016X}"),
Immediate::Float32(value) => write!(f, "{:08X}", value.to_bits()),
Immediate::Float64(value) => write!(f, "{:016X}", value.to_bits()),
}
}
}
impl From<Immediate> for u64 {
fn from(val: Immediate) -> Self {
match val {
#[allow(clippy::cast_sign_loss)]
Immediate::Int8(value) => value as u64,
Immediate::UInt8(value) => u64::from(value),
#[allow(clippy::cast_sign_loss)]
Immediate::Int16(value) => value as u64,
Immediate::UInt16(value) => u64::from(value),
#[allow(clippy::cast_sign_loss)]
Immediate::Int32(value) => value as u64,
Immediate::UInt32(value) => u64::from(value),
#[allow(clippy::cast_sign_loss)]
Immediate::Int64(value) => value as u64,
Immediate::UInt64(value) => value,
Immediate::Float32(value) => u64::from(value.to_bits()),
Immediate::Float64(value) => value.to_bits(),
}
}
}
#[derive(Debug, Clone)]
pub enum Operand {
None,
Immediate(Immediate),
Target(u64),
Token(Token),
Local(u16),
Argument(u16),
Switch(Vec<i32>),
}
impl Operand {
#[must_use]
pub fn as_string(&self) -> Option<String> {
match self {
Operand::None => None,
Operand::Immediate(imm) => Some(format!("{imm:?}")),
Operand::Target(t) => Some(format!("0x{t:08X}")),
Operand::Token(t) => Some(format!("0x{:08X}", t.value())),
Operand::Local(l) => Some(format!("V_{l}")),
Operand::Argument(a) => Some(format!("A_{a}")),
Operand::Switch(targets) => Some(format!("switch({})", targets.len())),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlowType {
Sequential,
ConditionalBranch,
UnconditionalBranch,
Call,
Return,
Switch,
Throw,
EndFinally,
Leave,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StackBehavior {
pub pops: u8,
pub pushes: u8,
pub net_effect: i8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InstructionCategory {
Arithmetic,
BitwiseLogical,
Comparison,
ControlFlow,
Conversion,
LoadStore,
ObjectModel,
Prefix,
Misc,
}
#[derive(Clone)]
pub struct Instruction {
pub rva: u64,
pub offset: u64,
pub size: u64,
pub opcode: u8,
pub prefix: u8,
pub mnemonic: &'static str,
pub category: InstructionCategory,
pub flow_type: FlowType,
pub operand: Operand,
pub stack_behavior: StackBehavior,
pub branch_targets: Vec<u64>,
}
impl Instruction {
#[must_use]
pub fn is_branch(&self) -> bool {
matches!(
self.flow_type,
FlowType::ConditionalBranch | FlowType::UnconditionalBranch | FlowType::Switch
)
}
#[must_use]
pub fn is_terminal(&self) -> bool {
matches!(
self.flow_type,
FlowType::ConditionalBranch
| FlowType::UnconditionalBranch
| FlowType::Return
| FlowType::Switch
| FlowType::Throw
| FlowType::Leave
)
}
#[must_use]
pub fn get_targets(&self) -> Vec<u64> {
match self.flow_type {
FlowType::ConditionalBranch | FlowType::UnconditionalBranch | FlowType::Switch => {
self.branch_targets.clone()
}
_ => Vec::new(),
}
}
#[must_use]
pub fn get_u8_operand(&self) -> Option<u8> {
match &self.operand {
Operand::Immediate(imm) => {
let val: u64 = (*imm).into();
u8::try_from(val).ok()
}
Operand::Local(idx) | Operand::Argument(idx) => u8::try_from(*idx).ok(),
_ => None,
}
}
#[must_use]
pub fn get_i8_operand(&self) -> Option<i8> {
match &self.operand {
Operand::Immediate(Immediate::Int8(v)) => Some(*v),
Operand::Immediate(Immediate::UInt8(v)) => Some(i8::from_ne_bytes(v.to_ne_bytes())),
Operand::Immediate(imm) => {
let val: u64 = (*imm).into();
u8::try_from(val)
.ok()
.map(|v| i8::from_ne_bytes(v.to_ne_bytes()))
}
_ => None,
}
}
#[must_use]
pub fn get_u16_operand(&self) -> Option<u16> {
match &self.operand {
Operand::Immediate(imm) => {
let val: u64 = (*imm).into();
u16::try_from(val).ok()
}
Operand::Local(idx) | Operand::Argument(idx) => Some(*idx),
_ => None,
}
}
#[must_use]
pub fn get_i32_operand(&self) -> Option<i32> {
match &self.operand {
Operand::Immediate(Immediate::Int32(v)) => Some(*v),
Operand::Immediate(Immediate::UInt32(v)) => Some(i32::from_ne_bytes(v.to_ne_bytes())),
Operand::Immediate(Immediate::Int16(v)) => Some(i32::from(*v)),
Operand::Immediate(Immediate::UInt16(v)) => Some(i32::from(*v)),
Operand::Immediate(Immediate::Int8(v)) => Some(i32::from(*v)),
Operand::Immediate(Immediate::UInt8(v)) => Some(i32::from(*v)),
Operand::Immediate(imm) => {
let val: u64 = (*imm).into();
u32::try_from(val)
.ok()
.map(|v| i32::from_ne_bytes(v.to_ne_bytes()))
}
_ => None,
}
}
#[must_use]
pub fn get_i64_operand(&self) -> Option<i64> {
match &self.operand {
Operand::Immediate(Immediate::Int64(v)) => Some(*v),
Operand::Immediate(Immediate::UInt64(v)) => Some(i64::from_ne_bytes(v.to_ne_bytes())),
Operand::Immediate(Immediate::Int32(v)) => Some(i64::from(*v)),
Operand::Immediate(Immediate::UInt32(v)) => Some(i64::from(*v)),
Operand::Immediate(Immediate::Int16(v)) => Some(i64::from(*v)),
Operand::Immediate(Immediate::UInt16(v)) => Some(i64::from(*v)),
Operand::Immediate(Immediate::Int8(v)) => Some(i64::from(*v)),
Operand::Immediate(Immediate::UInt8(v)) => Some(i64::from(*v)),
_ => None,
}
}
#[must_use]
pub fn get_f32_operand(&self) -> Option<f32> {
match &self.operand {
Operand::Immediate(Immediate::Float32(v)) => Some(*v),
Operand::Immediate(imm) => {
let val: u64 = (*imm).into();
u32::try_from(val).ok().map(f32::from_bits)
}
_ => None,
}
}
#[must_use]
pub fn get_f64_operand(&self) -> Option<f64> {
match &self.operand {
Operand::Immediate(Immediate::Float64(v)) => Some(*v),
Operand::Immediate(imm) => {
let val: u64 = (*imm).into();
Some(f64::from_bits(val))
}
_ => None,
}
}
#[must_use]
pub fn get_branch_target(&self) -> Option<u64> {
match &self.operand {
Operand::Target(target) => Some(*target),
_ => self.branch_targets.first().copied(),
}
}
#[must_use]
pub fn get_token_operand(&self) -> Option<Token> {
match &self.operand {
Operand::Token(token) => Some(*token),
_ => None,
}
}
#[must_use]
pub fn get_switch_offsets(&self) -> Option<&[i32]> {
match &self.operand {
Operand::Switch(targets) => Some(targets),
_ => None,
}
}
#[must_use]
pub fn is_prefix(&self) -> bool {
matches!(
self.mnemonic,
"tail." | "volatile." | "unaligned." | "constrained." | "readonly."
)
}
}
impl fmt::Debug for Instruction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:016X} - ", self.rva)?;
if self.prefix != 0 {
write!(f, "{:02X}:", self.prefix)?;
}
write!(f, "{:02X} - {:<12}", self.opcode, self.mnemonic)?;
match &self.operand {
Operand::None => {
}
Operand::Immediate(imm) => {
write!(f, " 0x{imm:X}")?;
}
Operand::Target(target) => {
write!(f, " -> 0x{target:08X}")?;
}
Operand::Token(token) => {
write!(f, " token:0x{:08X}", token.value())?;
}
Operand::Local(local) => {
write!(f, " local:{local}")?;
}
Operand::Argument(arg) => {
write!(f, " arg:{arg}")?;
}
Operand::Switch(items) => {
write!(f, " switch[{}]:(", items.len())?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "0x{item:08X}")?;
if i >= 5 && items.len() > 6 {
write!(f, ", ...{} more", items.len() - 6)?;
break;
}
}
write!(f, ")")?;
}
}
write!(f, " | ")?;
write!(f, "{:?}", self.category)?;
if self.flow_type != FlowType::Sequential {
write!(f, " | {:?}", self.flow_type)?;
}
if self.stack_behavior.net_effect != 0 {
write!(f, " | stack:{:+}", self.stack_behavior.net_effect)?;
}
write!(f, " | size:{}", self.size)?;
if !self.branch_targets.is_empty() {
write!(f, " | targets:[")?;
for (i, target) in self.branch_targets.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "0x{target:08X}")?;
if i >= 3 && self.branch_targets.len() > 4 {
write!(f, ", ...{} more", self.branch_targets.len() - 4)?;
break;
}
}
write!(f, "]")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_operand_type_variants() {
let types = [
OperandType::None,
OperandType::Int8,
OperandType::UInt8,
OperandType::Int16,
OperandType::UInt16,
OperandType::Int32,
OperandType::UInt32,
OperandType::Int64,
OperandType::UInt64,
OperandType::Float32,
OperandType::Float64,
OperandType::Token,
OperandType::Switch,
];
for op_type in types.iter() {
assert_eq!(*op_type, *op_type); assert!(!format!("{op_type:?}").is_empty()); }
}
#[test]
fn test_immediate_upper_hex_formatting() {
assert_eq!(format!("{:X}", Immediate::Int8(-1)), "FF");
assert_eq!(format!("{:X}", Immediate::UInt8(255)), "FF");
assert_eq!(format!("{:X}", Immediate::Int16(-1)), "FFFF");
assert_eq!(format!("{:X}", Immediate::UInt16(65535)), "FFFF");
assert_eq!(format!("{:X}", Immediate::Int32(42)), "0000002A");
assert_eq!(format!("{:X}", Immediate::UInt32(0xDEADBEEF)), "DEADBEEF");
assert_eq!(format!("{:X}", Immediate::Int64(-1)), "FFFFFFFFFFFFFFFF");
assert_eq!(
format!("{:X}", Immediate::UInt64(0x123456789ABCDEF0)),
"123456789ABCDEF0"
); let float32_bits = 42.5f32.to_bits();
assert_eq!(
format!("{:X}", Immediate::Float32(42.5f32)),
format!("{:08X}", float32_bits)
);
let float64_bits = 123.456f64.to_bits();
assert_eq!(
format!("{:X}", Immediate::Float64(123.456)),
format!("{:016X}", float64_bits)
);
}
#[test]
fn test_immediate_variants_and_conversions() {
let immediates = [
Immediate::Int8(-42),
Immediate::UInt8(42),
Immediate::Int16(-1000),
Immediate::UInt16(1000),
Immediate::Int32(-100000),
Immediate::UInt32(100000),
Immediate::Int64(-1000000000),
Immediate::UInt64(1000000000),
Immediate::Float32(3.04),
Immediate::Float64(2.0008),
];
for imm in immediates.iter() {
assert!(!format!("{imm:?}").is_empty());
let cloned = *imm;
assert_eq!(*imm, cloned);
let as_u64: u64 = (*imm).into();
assert!(as_u64 < u64::MAX);
}
}
#[test]
fn test_immediate_to_u64_conversion() {
assert_eq!(u64::from(Immediate::UInt8(255)), 255u64);
assert_eq!(u64::from(Immediate::UInt16(65535)), 65535u64);
assert_eq!(u64::from(Immediate::UInt32(4294967295)), 4294967295u64);
assert_eq!(u64::from(Immediate::UInt64(u64::MAX)), u64::MAX);
assert_eq!(u64::from(Immediate::Int8(-1)), (-1i8) as u64);
assert_eq!(u64::from(Immediate::Int32(-1)), (-1i32) as u64);
assert_eq!(u64::from(Immediate::Float32(42.0)), 1109917696u64); assert_eq!(u64::from(Immediate::Float64(100.0)), 4636737291354636288u64);
}
#[test]
fn test_operand_variants() {
let operands = [
Operand::None,
Operand::Immediate(Immediate::Int32(42)),
Operand::Target(0x1000),
Operand::Token(Token::new(0x06000001)),
Operand::Local(5),
Operand::Argument(3),
Operand::Switch(vec![0x1000, 0x2000, 0x3000]),
];
for operand in operands.iter() {
assert!(!format!("{operand:?}").is_empty());
let cloned = operand.clone();
match (operand, &cloned) {
(Operand::None, Operand::None) => {}
(Operand::Immediate(a), Operand::Immediate(b)) => assert_eq!(a, b),
(Operand::Target(a), Operand::Target(b)) => assert_eq!(a, b),
(Operand::Token(a), Operand::Token(b)) => assert_eq!(a.value(), b.value()),
(Operand::Local(a), Operand::Local(b)) => assert_eq!(a, b),
(Operand::Argument(a), Operand::Argument(b)) => assert_eq!(a, b),
(Operand::Switch(a), Operand::Switch(b)) => assert_eq!(a, b),
_ => panic!("Clone didn't produce same variant"),
}
}
}
#[test]
fn test_flow_type_variants() {
let flow_types = [
FlowType::Sequential,
FlowType::ConditionalBranch,
FlowType::UnconditionalBranch,
FlowType::Call,
FlowType::Return,
FlowType::Switch,
FlowType::Throw,
FlowType::EndFinally,
FlowType::Leave,
];
for flow_type in flow_types.iter() {
assert_eq!(*flow_type, *flow_type); assert!(!format!("{flow_type:?}").is_empty()); }
}
#[test]
fn test_stack_behavior_creation_and_properties() {
let stack_behavior = StackBehavior {
pops: 2,
pushes: 1,
net_effect: -1,
};
assert_eq!(stack_behavior.pops, 2);
assert_eq!(stack_behavior.pushes, 1);
assert_eq!(stack_behavior.net_effect, -1);
assert_eq!(stack_behavior, stack_behavior); assert!(!format!("{stack_behavior:?}").is_empty());
let cloned = stack_behavior;
assert_eq!(stack_behavior, cloned);
}
#[test]
fn test_instruction_category_variants() {
let categories = [
InstructionCategory::Arithmetic,
InstructionCategory::BitwiseLogical,
InstructionCategory::Comparison,
InstructionCategory::ControlFlow,
InstructionCategory::Conversion,
InstructionCategory::LoadStore,
InstructionCategory::ObjectModel,
InstructionCategory::Prefix,
InstructionCategory::Misc,
];
for category in categories.iter() {
assert_eq!(*category, *category); assert!(!format!("{category:?}").is_empty()); }
}
#[test]
fn test_instruction_creation() {
let instruction = Instruction {
rva: 0x1000,
offset: 0x500,
size: 5,
opcode: 0x38,
prefix: 0,
mnemonic: "br",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::UnconditionalBranch,
operand: Operand::Target(0x2000),
stack_behavior: StackBehavior {
pops: 0,
pushes: 0,
net_effect: 0,
},
branch_targets: vec![0x2000],
};
assert_eq!(instruction.rva, 0x1000);
assert_eq!(instruction.offset, 0x500);
assert_eq!(instruction.size, 5);
assert_eq!(instruction.opcode, 0x38);
assert_eq!(instruction.prefix, 0);
assert_eq!(instruction.mnemonic, "br");
assert_eq!(instruction.category, InstructionCategory::ControlFlow);
assert_eq!(instruction.flow_type, FlowType::UnconditionalBranch);
assert_eq!(instruction.branch_targets, vec![0x2000]);
}
#[test]
fn test_instruction_is_branch() {
let branch_instruction = Instruction {
rva: 0x1000,
offset: 0,
size: 5,
opcode: 0x38,
prefix: 0,
mnemonic: "br",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::UnconditionalBranch,
operand: Operand::Target(0x2000),
stack_behavior: StackBehavior {
pops: 0,
pushes: 0,
net_effect: 0,
},
branch_targets: vec![0x2000],
};
assert!(branch_instruction.is_branch());
let conditional_branch = Instruction {
rva: 0x1000,
offset: 0,
size: 2,
opcode: 0x2C,
prefix: 0,
mnemonic: "brtrue.s",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::ConditionalBranch,
operand: Operand::Target(0x1010),
stack_behavior: StackBehavior {
pops: 1,
pushes: 0,
net_effect: -1,
},
branch_targets: vec![0x1010],
};
assert!(conditional_branch.is_branch());
let switch_instruction = Instruction {
rva: 0x1000,
offset: 0,
size: 10,
opcode: 0x45,
prefix: 0,
mnemonic: "switch",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::Switch,
operand: Operand::Switch(vec![0x1100, 0x1200, 0x1300]),
stack_behavior: StackBehavior {
pops: 1,
pushes: 0,
net_effect: -1,
},
branch_targets: vec![0x1100, 0x1200, 0x1300],
};
assert!(switch_instruction.is_branch());
let add_instruction = Instruction {
rva: 0x1000,
offset: 0,
size: 1,
opcode: 0x58,
prefix: 0,
mnemonic: "add",
category: InstructionCategory::Arithmetic,
flow_type: FlowType::Sequential,
operand: Operand::None,
stack_behavior: StackBehavior {
pops: 2,
pushes: 1,
net_effect: -1,
},
branch_targets: vec![],
};
assert!(!add_instruction.is_branch());
}
#[test]
fn test_instruction_is_terminal() {
let ret_instruction = Instruction {
rva: 0x1000,
offset: 0,
size: 1,
opcode: 0x2A,
prefix: 0,
mnemonic: "ret",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::Return,
operand: Operand::None,
stack_behavior: StackBehavior {
pops: 1,
pushes: 0,
net_effect: -1,
},
branch_targets: vec![],
};
assert!(ret_instruction.is_terminal());
let throw_instruction = Instruction {
rva: 0x1000,
offset: 0,
size: 1,
opcode: 0x7A,
prefix: 0,
mnemonic: "throw",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::Throw,
operand: Operand::None,
stack_behavior: StackBehavior {
pops: 1,
pushes: 0,
net_effect: -1,
},
branch_targets: vec![],
};
assert!(throw_instruction.is_terminal());
let branch_instruction = Instruction {
rva: 0x1000,
offset: 0,
size: 5,
opcode: 0x38,
prefix: 0,
mnemonic: "br",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::UnconditionalBranch,
operand: Operand::Target(0x2000),
stack_behavior: StackBehavior {
pops: 0,
pushes: 0,
net_effect: 0,
},
branch_targets: vec![0x2000],
};
assert!(branch_instruction.is_terminal());
let leave_instruction = Instruction {
rva: 0x1000,
offset: 0,
size: 5,
opcode: 0xDD,
prefix: 0,
mnemonic: "leave",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::Leave,
operand: Operand::Target(0x2000),
stack_behavior: StackBehavior {
pops: 0,
pushes: 0,
net_effect: 0,
},
branch_targets: vec![0x2000],
};
assert!(leave_instruction.is_terminal());
let add_instruction = Instruction {
rva: 0x1000,
offset: 0,
size: 1,
opcode: 0x58,
prefix: 0,
mnemonic: "add",
category: InstructionCategory::Arithmetic,
flow_type: FlowType::Sequential,
operand: Operand::None,
stack_behavior: StackBehavior {
pops: 2,
pushes: 1,
net_effect: -1,
},
branch_targets: vec![],
};
assert!(!add_instruction.is_terminal());
let call_instruction = Instruction {
rva: 0x1000,
offset: 0,
size: 5,
opcode: 0x28,
prefix: 0,
mnemonic: "call",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::Call,
operand: Operand::Token(Token::new(0x0A000001)),
stack_behavior: StackBehavior {
pops: 1,
pushes: 1,
net_effect: 0,
},
branch_targets: vec![],
};
assert!(!call_instruction.is_terminal());
}
#[test]
fn test_instruction_get_targets() {
let branch_instruction = Instruction {
rva: 0x1000,
offset: 0,
size: 5,
opcode: 0x38,
prefix: 0,
mnemonic: "br",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::UnconditionalBranch,
operand: Operand::Target(0x2000),
stack_behavior: StackBehavior {
pops: 0,
pushes: 0,
net_effect: 0,
},
branch_targets: vec![0x2000],
};
assert_eq!(branch_instruction.get_targets(), vec![0x2000]);
let conditional_branch = Instruction {
rva: 0x1000,
offset: 0,
size: 2,
opcode: 0x2C,
prefix: 0,
mnemonic: "brtrue.s",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::ConditionalBranch,
operand: Operand::Target(0x1010),
stack_behavior: StackBehavior {
pops: 1,
pushes: 0,
net_effect: -1,
},
branch_targets: vec![0x1010],
};
assert_eq!(conditional_branch.get_targets(), vec![0x1010]);
let switch_instruction = Instruction {
rva: 0x1000,
offset: 0,
size: 10,
opcode: 0x45,
prefix: 0,
mnemonic: "switch",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::Switch,
operand: Operand::Switch(vec![0x1100, 0x1200, 0x1300]),
stack_behavior: StackBehavior {
pops: 1,
pushes: 0,
net_effect: -1,
},
branch_targets: vec![0x1100, 0x1200, 0x1300],
};
let targets = switch_instruction.get_targets();
assert_eq!(targets.len(), 3);
assert!(targets.contains(&0x1100));
assert!(targets.contains(&0x1200));
assert!(targets.contains(&0x1300));
let add_instruction = Instruction {
rva: 0x1000,
offset: 0,
size: 1,
opcode: 0x58,
prefix: 0,
mnemonic: "add",
category: InstructionCategory::Arithmetic,
flow_type: FlowType::Sequential,
operand: Operand::None,
stack_behavior: StackBehavior {
pops: 2,
pushes: 1,
net_effect: -1,
},
branch_targets: vec![],
};
assert_eq!(add_instruction.get_targets(), Vec::<u64>::new());
let branch_with_immediate = Instruction {
rva: 0x1000,
offset: 0,
size: 2,
opcode: 0x38,
prefix: 0,
mnemonic: "br",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::UnconditionalBranch,
operand: Operand::Immediate(Immediate::Int32(42)), stack_behavior: StackBehavior {
pops: 0,
pushes: 0,
net_effect: 0,
},
branch_targets: vec![],
};
assert_eq!(branch_with_immediate.get_targets(), Vec::<u64>::new());
}
#[test]
fn test_instruction_debug_format() {
let add_instruction = Instruction {
rva: 0x1000,
offset: 0,
size: 1,
opcode: 0x58,
prefix: 0,
mnemonic: "add",
category: InstructionCategory::Arithmetic,
flow_type: FlowType::Sequential,
operand: Operand::None,
stack_behavior: StackBehavior {
pops: 2,
pushes: 1,
net_effect: -1,
},
branch_targets: vec![],
};
let debug_str = format!("{add_instruction:?}");
assert!(debug_str.contains("0000000000001000"));
assert!(debug_str.contains("58"));
assert!(debug_str.contains("add"));
assert!(debug_str.contains("Arithmetic"));
assert!(debug_str.contains("stack:-1"));
assert!(debug_str.contains("size:1"));
let immediate_instruction = Instruction {
rva: 0x2000,
offset: 0,
size: 5,
opcode: 0x20,
prefix: 0,
mnemonic: "ldc.i4",
category: InstructionCategory::LoadStore,
flow_type: FlowType::Sequential,
operand: Operand::Immediate(Immediate::Int32(42)),
stack_behavior: StackBehavior {
pops: 0,
pushes: 1,
net_effect: 1,
},
branch_targets: vec![],
};
let debug_str = format!("{immediate_instruction:?}");
assert!(debug_str.contains("0000000000002000"));
assert!(debug_str.contains("20"));
assert!(debug_str.contains("ldc.i4"));
assert!(debug_str.contains("0x0000002A")); assert!(debug_str.contains("LoadStore"));
assert!(debug_str.contains("stack:+1"));
let branch_instruction = Instruction {
rva: 0x3000,
offset: 0,
size: 5,
opcode: 0x38,
prefix: 0,
mnemonic: "br",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::UnconditionalBranch,
operand: Operand::Target(0x4000),
stack_behavior: StackBehavior {
pops: 0,
pushes: 0,
net_effect: 0,
},
branch_targets: vec![0x4000],
};
let debug_str = format!("{branch_instruction:?}");
assert!(debug_str.contains("0000000000003000"));
assert!(debug_str.contains("38"));
assert!(debug_str.contains("br"));
assert!(debug_str.contains("-> 0x00004000"));
assert!(debug_str.contains("ControlFlow"));
assert!(debug_str.contains("UnconditionalBranch"));
assert!(debug_str.contains("targets:[0x00004000]"));
let token_instruction = Instruction {
rva: 0x5000,
offset: 0,
size: 5,
opcode: 0x28,
prefix: 0,
mnemonic: "call",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::Call,
operand: Operand::Token(Token::new(0x0A000001)),
stack_behavior: StackBehavior {
pops: 1,
pushes: 1,
net_effect: 0,
},
branch_targets: vec![],
};
let debug_str = format!("{token_instruction:?}");
assert!(debug_str.contains("0000000000005000"));
assert!(debug_str.contains("28"));
assert!(debug_str.contains("call"));
assert!(debug_str.contains("token:0x0A000001"));
assert!(debug_str.contains("ControlFlow"));
assert!(debug_str.contains("Call"));
let local_instruction = Instruction {
rva: 0x6000,
offset: 0,
size: 2,
opcode: 0x11,
prefix: 0,
mnemonic: "ldloc.s",
category: InstructionCategory::LoadStore,
flow_type: FlowType::Sequential,
operand: Operand::Local(5),
stack_behavior: StackBehavior {
pops: 0,
pushes: 1,
net_effect: 1,
},
branch_targets: vec![],
};
let debug_str = format!("{local_instruction:?}");
assert!(debug_str.contains("0000000000006000"));
assert!(debug_str.contains("11"));
assert!(debug_str.contains("ldloc.s"));
assert!(debug_str.contains("local:5"));
assert!(debug_str.contains("LoadStore"));
assert!(debug_str.contains("stack:+1"));
let arg_instruction = Instruction {
rva: 0x7000,
offset: 0,
size: 2,
opcode: 0x0E,
prefix: 0,
mnemonic: "ldarg.s",
category: InstructionCategory::LoadStore,
flow_type: FlowType::Sequential,
operand: Operand::Argument(3),
stack_behavior: StackBehavior {
pops: 0,
pushes: 1,
net_effect: 1,
},
branch_targets: vec![],
};
let debug_str = format!("{arg_instruction:?}");
assert!(debug_str.contains("0000000000007000"));
assert!(debug_str.contains("0E"));
assert!(debug_str.contains("ldarg.s"));
assert!(debug_str.contains("arg:3"));
assert!(debug_str.contains("LoadStore"));
let switch_instruction = Instruction {
rva: 0x8000,
offset: 0,
size: 15,
opcode: 0x45,
prefix: 0,
mnemonic: "switch",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::Switch,
operand: Operand::Switch(vec![0x8100, 0x8200, 0x8300]),
stack_behavior: StackBehavior {
pops: 1,
pushes: 0,
net_effect: -1,
},
branch_targets: vec![0x8100, 0x8200, 0x8300],
};
let debug_str = format!("{switch_instruction:?}");
assert!(debug_str.contains("0000000000008000"));
assert!(debug_str.contains("45"));
assert!(debug_str.contains("switch"));
assert!(debug_str.contains("switch[3]:(0x00008100, 0x00008200, 0x00008300)"));
assert!(debug_str.contains("ControlFlow"));
assert!(debug_str.contains("Switch"));
assert!(debug_str.contains("stack:-1"));
assert!(debug_str.contains("targets:[0x00008100, 0x00008200, 0x00008300]"));
let prefixed_instruction = Instruction {
rva: 0x9000,
offset: 0,
size: 3,
opcode: 0x6F,
prefix: 0xFE,
mnemonic: "callvirt",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::Call,
operand: Operand::Token(Token::new(0x0A000002)),
stack_behavior: StackBehavior {
pops: 1,
pushes: 1,
net_effect: 0,
},
branch_targets: vec![],
};
let debug_str = format!("{prefixed_instruction:?}");
assert!(debug_str.contains("0000000000009000"));
assert!(debug_str.contains("FE:6F"));
assert!(debug_str.contains("callvirt"));
assert!(debug_str.contains("token:0x0A000002"));
let float_instruction = Instruction {
rva: 0xA000,
offset: 0,
size: 9,
opcode: 0x23,
prefix: 0,
mnemonic: "ldc.r8",
category: InstructionCategory::LoadStore,
flow_type: FlowType::Sequential,
operand: Operand::Immediate(Immediate::Float64(123.456)),
stack_behavior: StackBehavior {
pops: 0,
pushes: 1,
net_effect: 1,
},
branch_targets: vec![],
};
let debug_str = format!("{float_instruction:?}");
assert!(debug_str.contains("000000000000A000"));
assert!(debug_str.contains("23"));
assert!(debug_str.contains("ldc.r8"));
assert!(debug_str.contains("0x405EDD2F1A9FBE77")); }
#[test]
fn test_instruction_clone() {
let original = Instruction {
rva: 0x1000,
offset: 0x500,
size: 5,
opcode: 0x38,
prefix: 0,
mnemonic: "br",
category: InstructionCategory::ControlFlow,
flow_type: FlowType::UnconditionalBranch,
operand: Operand::Target(0x2000),
stack_behavior: StackBehavior {
pops: 0,
pushes: 0,
net_effect: 0,
},
branch_targets: vec![0x2000],
};
let cloned = original.clone();
assert_eq!(original.rva, cloned.rva);
assert_eq!(original.offset, cloned.offset);
assert_eq!(original.size, cloned.size);
assert_eq!(original.opcode, cloned.opcode);
assert_eq!(original.prefix, cloned.prefix);
assert_eq!(original.mnemonic, cloned.mnemonic);
assert_eq!(original.category, cloned.category);
assert_eq!(original.flow_type, cloned.flow_type);
assert_eq!(original.stack_behavior, cloned.stack_behavior);
assert_eq!(original.branch_targets, cloned.branch_targets);
match (&original.operand, &cloned.operand) {
(Operand::Target(a), Operand::Target(b)) => assert_eq!(a, b),
_ => panic!("Operand clone failed"),
}
}
#[test]
fn test_edge_cases_and_boundary_values() {
let max_immediates = [
Immediate::Int8(i8::MAX),
Immediate::Int8(i8::MIN),
Immediate::UInt8(u8::MAX),
Immediate::Int16(i16::MAX),
Immediate::Int16(i16::MIN),
Immediate::UInt16(u16::MAX),
Immediate::Int32(i32::MAX),
Immediate::Int32(i32::MIN),
Immediate::UInt32(u32::MAX),
Immediate::Int64(i64::MAX),
Immediate::Int64(i64::MIN),
Immediate::UInt64(u64::MAX),
Immediate::Float32(f32::MAX),
Immediate::Float32(f32::MIN),
Immediate::Float64(f64::MAX),
Immediate::Float64(f64::MIN),
];
for imm in max_immediates.iter() {
let _: u64 = (*imm).into(); assert!(!format!("{imm:?}").is_empty());
}
let empty_switch = Operand::Switch(vec![]);
assert!(!format!("{empty_switch:?}").is_empty());
let large_switch = Operand::Switch((0..10).collect());
let debug_str = format!("{large_switch:?}");
assert!(debug_str.contains("Switch"));
assert!(debug_str.contains("["));
assert!(debug_str.contains("]"));
let zero_stack = StackBehavior {
pops: 0,
pushes: 0,
net_effect: 0,
};
assert_eq!(zero_stack.pops, 0);
assert_eq!(zero_stack.pushes, 0);
assert_eq!(zero_stack.net_effect, 0);
}
#[test]
fn test_stack_behavior_calculations() {
let scenarios = [
(0, 1, 1), (1, 0, -1), (2, 1, -1), (1, 1, 0), (0, 0, 0), (3, 2, -1), ];
for (pops, pushes, expected_net) in scenarios.iter() {
let stack_behavior = StackBehavior {
pops: *pops,
pushes: *pushes,
net_effect: *expected_net,
};
assert_eq!(stack_behavior.pops, *pops);
assert_eq!(stack_behavior.pushes, *pushes);
assert_eq!(stack_behavior.net_effect, *expected_net);
assert_eq!(stack_behavior.net_effect, (*pushes as i8) - (*pops as i8));
}
}
}