use crate::decompiler::analysis::{MethodRef, MethodTable};
use crate::decompiler::cfg::{BasicBlock, BlockId, Cfg, EdgeKind, Terminator};
use crate::instruction::Instruction;
use super::MethodView;
pub(crate) fn extract_method_cfgs(
whole: &Cfg,
table: &MethodTable,
instructions: &[Instruction],
) -> Vec<MethodView> {
let mut out = Vec::new();
for (start, end, method) in table.methods() {
let method_instructions: Vec<_> = instructions
.iter()
.filter(|i| i.offset >= start && i.offset < end)
.cloned()
.collect();
if let Some(view) = extract_one(whole, start, end, method.clone(), method_instructions) {
out.push(view);
}
}
out
}
fn extract_one(
whole: &Cfg,
start: usize,
end: usize,
method: MethodRef,
instructions: Vec<Instruction>,
) -> Option<MethodView> {
let mut selected: Vec<&BasicBlock> = whole
.blocks()
.filter(|b| b.instruction_range.start < end && b.start_offset >= start)
.collect();
if selected.is_empty() {
return None;
}
let entry_existing = selected.iter().find(|b| b.start_offset == start).copied();
selected.sort_by_key(|b| b.id.0);
let max_id = whole.blocks().map(|b| b.id.0).max().unwrap_or(0);
let entry_id = BlockId(max_id + 1);
let mut sub = Cfg::new();
for b in &selected {
let mut nb = (*b).clone();
nb.terminator = rewrite_terminator(&nb.terminator, &selected);
sub.add_block(nb);
}
let entry_terminator = match entry_existing {
Some(e) if matches!(e.terminator, Terminator::Fallthrough { .. }) => {
Terminator::Fallthrough { target: e.id }
}
Some(e) => Terminator::Jump { target: e.id },
None => Terminator::Return,
};
sub.add_block(BasicBlock::new(
entry_id,
start,
start,
start..start,
entry_terminator,
));
if let Some(eid) = entry_existing.map(|e| e.id) {
sub.add_edge(entry_id, eid, EdgeKind::Unconditional);
}
for b in &selected {
for s in b.terminator.successors() {
if sub.block(s).is_some() && s != b.id {
sub.add_edge(b.id, s, EdgeKind::Unconditional);
}
}
}
Some(MethodView {
method,
cfg: sub,
instructions,
})
}
fn rewrite_terminator(term: &Terminator, selected: &[&BasicBlock]) -> Terminator {
let in_range = |bid: BlockId| selected.iter().any(|b| b.id == bid);
match term {
Terminator::Jump { target } if !in_range(*target) => Terminator::Return,
Terminator::Branch {
then_target,
else_target,
} if !in_range(*then_target) || !in_range(*else_target) => Terminator::Return,
_ => term.clone(),
}
}