use crate::{
assembly::{FlowType, Instruction},
metadata::method::ExceptionHandlerFlags,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HandlerEntryInfo {
pub handler_index: usize,
pub handler_type: ExceptionHandlerFlags,
}
impl HandlerEntryInfo {
#[must_use]
pub const fn new(handler_index: usize, handler_type: ExceptionHandlerFlags) -> Self {
Self {
handler_index,
handler_type,
}
}
#[must_use]
pub const fn entry_stack_depth(&self) -> usize {
if self.handler_type.bits() == ExceptionHandlerFlags::EXCEPTION.bits()
|| self.handler_type.bits() == ExceptionHandlerFlags::FILTER.bits()
{
1 } else {
0 }
}
}
#[derive(Debug, Clone)]
pub struct BasicBlock {
pub id: usize,
pub rva: u64,
pub offset: usize,
pub size: usize,
pub instructions: Vec<Instruction>,
pub predecessors: Vec<usize>,
pub successors: Vec<usize>,
pub exceptions: Vec<usize>,
pub handler_entry: Option<HandlerEntryInfo>,
pub exception_successors: Vec<usize>,
}
impl BasicBlock {
#[must_use]
pub fn new(id: usize, rva: u64, offset: usize) -> Self {
Self {
id,
rva,
offset,
size: 0,
instructions: Vec::new(),
predecessors: Vec::new(),
successors: Vec::new(),
exceptions: Vec::new(),
handler_entry: None,
exception_successors: Vec::new(),
}
}
#[must_use]
pub fn instruction_first(&self) -> Option<&Instruction> {
self.instructions.first()
}
#[must_use]
pub fn instruction_last(&self) -> Option<&Instruction> {
self.instructions.last()
}
#[must_use]
pub fn is_entry(&self) -> bool {
self.predecessors.is_empty()
}
#[must_use]
pub fn is_exit(&self) -> bool {
if let Some(last_instr) = self.instruction_last() {
matches!(last_instr.flow_type, FlowType::Return | FlowType::Throw)
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::factories::general::disassembler::create_sample_instruction;
#[test]
fn test_basic_block_new() {
let block = BasicBlock::new(42, 0x2000, 0x1500);
assert_eq!(block.id, 42);
assert_eq!(block.rva, 0x2000);
assert_eq!(block.offset, 0x1500);
assert_eq!(block.size, 0);
assert!(block.instructions.is_empty());
assert!(block.predecessors.is_empty());
assert!(block.successors.is_empty());
assert!(block.exceptions.is_empty());
}
#[test]
fn test_basic_block_new_zero_values() {
let block = BasicBlock::new(0, 0, 0);
assert_eq!(block.id, 0);
assert_eq!(block.rva, 0);
assert_eq!(block.offset, 0);
assert_eq!(block.size, 0);
}
#[test]
fn test_basic_block_new_max_values() {
let block = BasicBlock::new(usize::MAX, u64::MAX, usize::MAX);
assert_eq!(block.id, usize::MAX);
assert_eq!(block.rva, u64::MAX);
assert_eq!(block.offset, usize::MAX);
}
#[test]
fn test_instruction_first_empty_block() {
let block = BasicBlock::new(0, 0x1000, 0x500);
assert!(block.instruction_first().is_none());
}
#[test]
fn test_instruction_first_single_instruction() {
let mut block = BasicBlock::new(0, 0x1000, 0x500);
let instr = create_sample_instruction(FlowType::Sequential);
block.instructions.push(instr);
let first = block.instruction_first().unwrap();
assert_eq!(first.flow_type, FlowType::Sequential);
}
#[test]
fn test_instruction_first_multiple_instructions() {
let mut block = BasicBlock::new(0, 0x1000, 0x500);
let first_instr = create_sample_instruction(FlowType::Sequential);
let second_instr = create_sample_instruction(FlowType::ConditionalBranch);
block.instructions.push(first_instr);
block.instructions.push(second_instr);
let first = block.instruction_first().unwrap();
assert_eq!(first.flow_type, FlowType::Sequential);
}
#[test]
fn test_instruction_last_empty_block() {
let block = BasicBlock::new(0, 0x1000, 0x500);
assert!(block.instruction_last().is_none());
}
#[test]
fn test_instruction_last_single_instruction() {
let mut block = BasicBlock::new(0, 0x1000, 0x500);
let instr = create_sample_instruction(FlowType::Return);
block.instructions.push(instr);
let last = block.instruction_last().unwrap();
assert_eq!(last.flow_type, FlowType::Return);
}
#[test]
fn test_instruction_last_multiple_instructions() {
let mut block = BasicBlock::new(0, 0x1000, 0x500);
let first_instr = create_sample_instruction(FlowType::Sequential);
let last_instr = create_sample_instruction(FlowType::ConditionalBranch);
block.instructions.push(first_instr);
block.instructions.push(last_instr);
let last = block.instruction_last().unwrap();
assert_eq!(last.flow_type, FlowType::ConditionalBranch);
}
#[test]
fn test_is_entry_new_block() {
let block = BasicBlock::new(0, 0x1000, 0x500);
assert!(block.is_entry());
}
#[test]
fn test_is_entry_with_predecessors() {
let mut block = BasicBlock::new(0, 0x1000, 0x500);
block.predecessors.push(1);
assert!(!block.is_entry());
}
#[test]
fn test_is_entry_multiple_predecessors() {
let mut block = BasicBlock::new(0, 0x1000, 0x500);
block.predecessors.push(1);
block.predecessors.push(2);
block.predecessors.push(3);
assert!(!block.is_entry());
}
#[test]
fn test_is_entry_after_removing_predecessors() {
let mut block = BasicBlock::new(0, 0x1000, 0x500);
block.predecessors.push(1);
assert!(!block.is_entry());
block.predecessors.clear();
assert!(block.is_entry());
}
#[test]
fn test_is_exit_empty_block() {
let block = BasicBlock::new(0, 0x1000, 0x500);
assert!(!block.is_exit());
}
#[test]
fn test_is_exit_return_instruction() {
let mut block = BasicBlock::new(0, 0x1000, 0x500);
let return_instr = create_sample_instruction(FlowType::Return);
block.instructions.push(return_instr);
assert!(block.is_exit());
}
#[test]
fn test_is_exit_throw_instruction() {
let mut block = BasicBlock::new(0, 0x1000, 0x500);
let throw_instr = create_sample_instruction(FlowType::Throw);
block.instructions.push(throw_instr);
assert!(block.is_exit());
}
#[test]
fn test_is_exit_non_terminating_instructions() {
let flow_types = [
FlowType::Sequential,
FlowType::ConditionalBranch,
FlowType::UnconditionalBranch,
FlowType::Call,
];
for flow_type in &flow_types {
let mut block = BasicBlock::new(0, 0x1000, 0x500);
let instr = create_sample_instruction(*flow_type);
block.instructions.push(instr);
assert!(
!block.is_exit(),
"Block with {flow_type:?} should not be exit"
);
}
}
#[test]
fn test_is_exit_multiple_instructions_last_return() {
let mut block = BasicBlock::new(0, 0x1000, 0x500);
block
.instructions
.push(create_sample_instruction(FlowType::Sequential));
block
.instructions
.push(create_sample_instruction(FlowType::Call));
block
.instructions
.push(create_sample_instruction(FlowType::Return));
assert!(block.is_exit());
}
#[test]
fn test_is_exit_multiple_instructions_last_non_terminating() {
let mut block = BasicBlock::new(0, 0x1000, 0x500);
block
.instructions
.push(create_sample_instruction(FlowType::Return));
block
.instructions
.push(create_sample_instruction(FlowType::Sequential));
assert!(!block.is_exit());
}
#[test]
fn test_basic_block_debug_format() {
let block = BasicBlock::new(5, 0x3000, 0x2000);
let debug_str = format!("{block:?}");
assert!(debug_str.contains("BasicBlock"));
assert!(debug_str.contains("id: 5"));
assert!(debug_str.contains("rva: 12288")); assert!(debug_str.contains("offset: 8192")); }
#[test]
fn test_basic_block_clone() {
let mut original = BasicBlock::new(1, 0x1000, 0x500);
original.size = 42;
original
.instructions
.push(create_sample_instruction(FlowType::Sequential));
original.predecessors.push(2);
original.successors.push(3);
original.exceptions.push(4);
let cloned = original.clone();
assert_eq!(cloned.id, original.id);
assert_eq!(cloned.rva, original.rva);
assert_eq!(cloned.offset, original.offset);
assert_eq!(cloned.size, original.size);
assert_eq!(cloned.instructions.len(), original.instructions.len());
assert_eq!(cloned.predecessors, original.predecessors);
assert_eq!(cloned.successors, original.successors);
assert_eq!(cloned.exceptions, original.exceptions);
}
#[test]
fn test_complex_control_flow_scenario() {
let mut entry_block = BasicBlock::new(0, 0x1000, 0x500);
let mut branch_block = BasicBlock::new(1, 0x1010, 0x510);
let mut exit_block = BasicBlock::new(2, 0x1020, 0x520);
entry_block.successors = vec![1, 2];
branch_block.predecessors = vec![0];
branch_block.successors = vec![2];
exit_block.predecessors = vec![0, 1];
entry_block
.instructions
.push(create_sample_instruction(FlowType::ConditionalBranch));
branch_block
.instructions
.push(create_sample_instruction(FlowType::Sequential));
exit_block
.instructions
.push(create_sample_instruction(FlowType::Return));
assert!(entry_block.is_entry());
assert!(!entry_block.is_exit());
assert!(!branch_block.is_entry());
assert!(!branch_block.is_exit());
assert!(!exit_block.is_entry());
assert!(exit_block.is_exit());
}
#[test]
fn test_exception_handling_blocks() {
let mut try_block = BasicBlock::new(0, 0x1000, 0x500);
let mut catch_block = BasicBlock::new(1, 0x1010, 0x510);
try_block.exceptions = vec![0, 1];
catch_block.exceptions = vec![0];
assert!(try_block.is_entry());
assert!(catch_block.is_entry());
assert_eq!(try_block.exceptions.len(), 2);
assert_eq!(catch_block.exceptions.len(), 1);
}
#[test]
fn test_block_size_and_offset_boundaries() {
let mut block = BasicBlock::new(0, u64::MAX, usize::MAX);
block.size = usize::MAX;
assert_eq!(block.rva, u64::MAX);
assert_eq!(block.offset, usize::MAX);
assert_eq!(block.size, usize::MAX);
}
#[test]
fn test_empty_vectors_behavior() {
let block = BasicBlock::new(0, 0x1000, 0x500);
assert!(block.instructions.is_empty());
assert!(block.predecessors.is_empty());
assert!(block.successors.is_empty());
assert!(block.exceptions.is_empty());
assert_eq!(block.instructions.capacity(), 0);
assert_eq!(block.predecessors.capacity(), 0);
assert_eq!(block.successors.capacity(), 0);
assert_eq!(block.exceptions.capacity(), 0);
}
}