use std::collections::BTreeSet;
use crate::instruction::{Instruction, OpCode, Operand};
pub(in super::super::super) fn collect_control_flow_edges(
instructions: &[Instruction],
known_offsets: &BTreeSet<usize>,
) -> Vec<(usize, usize)> {
let mut edges = Vec::new();
for instruction in instructions {
match instruction.opcode {
OpCode::Jmp
| OpCode::Jmp_L
| OpCode::Jmpif
| OpCode::Jmpif_L
| OpCode::Jmpifnot
| OpCode::Jmpifnot_L
| OpCode::JmpEq
| OpCode::JmpEq_L
| OpCode::JmpNe
| OpCode::JmpNe_L
| OpCode::JmpGt
| OpCode::JmpGt_L
| OpCode::JmpGe
| OpCode::JmpGe_L
| OpCode::JmpLt
| OpCode::JmpLt_L
| OpCode::JmpLe
| OpCode::JmpLe_L
| OpCode::Endtry
| OpCode::EndtryL => {
if let Some(target) = relative_target(instruction, known_offsets) {
edges.push((instruction.offset, target));
}
}
OpCode::Try => {
if let Some(Operand::Bytes(bytes)) = &instruction.operand {
if bytes.len() == 2 {
for delta in [bytes[0] as i8 as isize, bytes[1] as i8 as isize] {
if let Some(target) =
relative_target_with_delta(instruction.offset, delta, known_offsets)
{
edges.push((instruction.offset, target));
}
}
}
}
}
OpCode::TryL => {
if let Some(Operand::Bytes(bytes)) = &instruction.operand {
if bytes.len() == 8 {
let catch_delta =
i32::from_le_bytes(bytes[0..4].try_into().expect("slice length"))
as isize;
let finally_delta =
i32::from_le_bytes(bytes[4..8].try_into().expect("slice length"))
as isize;
for delta in [catch_delta, finally_delta] {
if let Some(target) =
relative_target_with_delta(instruction.offset, delta, known_offsets)
{
edges.push((instruction.offset, target));
}
}
}
}
}
_ => {}
}
}
edges
}
pub(in super::super::super) fn relative_target(
instruction: &Instruction,
known_offsets: &BTreeSet<usize>,
) -> Option<usize> {
let delta = match &instruction.operand {
Some(Operand::Jump(value)) => *value as isize,
Some(Operand::Jump32(value)) => *value as isize,
_ => return None,
};
relative_target_with_delta(instruction.offset, delta, known_offsets)
}
fn relative_target_with_delta(
base: usize,
delta: isize,
known_offsets: &BTreeSet<usize>,
) -> Option<usize> {
let target = base as isize + delta;
if target < 0 {
return None;
}
let target = target as usize;
known_offsets.contains(&target).then_some(target)
}