use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone)]
pub struct CallGraph {
pub nodes: Vec<FunctionNode>,
pub edges: Vec<CallEdge>,
}
#[derive(Debug, Clone)]
pub struct FunctionNode {
pub address: u32,
pub name: String,
pub is_entry_point: bool,
pub callers: Vec<usize>,
pub callees: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct CallEdge {
pub caller: usize,
pub callee: usize,
pub call_sites: Vec<u32>,
}
pub struct InterProceduralAnalyzer;
impl InterProceduralAnalyzer {
pub fn build_call_graph(functions: &[crate::recompiler::ghidra::FunctionInfo]) -> CallGraph {
let mut nodes = Vec::new();
let mut edges = Vec::new();
let mut address_to_index: HashMap<u32, usize> = HashMap::new();
for (idx, func) in functions.iter().enumerate() {
address_to_index.insert(func.address, idx);
nodes.push(FunctionNode {
address: func.address,
name: func.name.clone(),
is_entry_point: idx == 0, callers: vec![],
callees: vec![],
});
}
CallGraph { nodes, edges }
}
pub fn find_unreachable_functions(call_graph: &CallGraph) -> Vec<usize> {
let mut reachable = HashSet::new();
let mut queue = Vec::new();
for (idx, node) in call_graph.nodes.iter().enumerate() {
if node.is_entry_point {
queue.push(idx);
reachable.insert(idx);
}
}
while let Some(node_idx) = queue.pop() {
for &callee in &call_graph.nodes[node_idx].callees {
if !reachable.contains(&callee) {
reachable.insert(callee);
queue.push(callee);
}
}
}
(0..call_graph.nodes.len())
.filter(|idx| !reachable.contains(idx))
.collect()
}
}