use crate::cfg::{BlockKind, Cfg, Terminator};
use petgraph::graph::NodeIndex;
pub fn find_entry(cfg: &Cfg) -> Option<NodeIndex> {
cfg.node_indices().next()
}
pub fn find_exits(cfg: &Cfg) -> Vec<NodeIndex> {
cfg.node_indices()
.filter(|&idx| is_exit_block(cfg, idx))
.collect()
}
pub fn is_exit_block(cfg: &Cfg, block_idx: NodeIndex) -> bool {
if let Some(block) = cfg.node_weight(block_idx) {
return matches!(
&block.terminator,
Terminator::Return | Terminator::Unreachable | Terminator::Abort(_)
);
}
false
}
pub fn get_block_kind(cfg: &Cfg, block_idx: NodeIndex) -> Option<BlockKind> {
cfg.node_weight(block_idx).map(|b| b.kind)
}
pub fn in_degree(cfg: &Cfg, block_idx: NodeIndex) -> usize {
cfg.neighbors_directed(block_idx, petgraph::Direction::Incoming)
.count()
}
pub fn out_degree(cfg: &Cfg, block_idx: NodeIndex) -> usize {
cfg.neighbors_directed(block_idx, petgraph::Direction::Outgoing)
.count()
}
pub fn is_merge_point(cfg: &Cfg, block_idx: NodeIndex) -> bool {
in_degree(cfg, block_idx) > 1
}
pub fn is_branch_point(cfg: &Cfg, block_idx: NodeIndex) -> bool {
out_degree(cfg, block_idx) > 1
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cfg::{BasicBlock, EdgeType};
use petgraph::graph::DiGraph;
fn create_test_cfg() -> Cfg {
let mut g = DiGraph::new();
let b0 = g.add_node(BasicBlock {
id: 0,
db_id: None,
kind: BlockKind::Entry,
statements: vec![],
terminator: Terminator::Goto { target: 1 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b1 = g.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::SwitchInt {
targets: vec![2],
otherwise: 3,
},
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b2 = g.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Exit,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b3 = g.add_node(BasicBlock {
id: 3,
db_id: None,
kind: BlockKind::Exit,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
g.add_edge(b0, b1, EdgeType::Fallthrough);
g.add_edge(b1, b2, EdgeType::TrueBranch);
g.add_edge(b1, b3, EdgeType::FalseBranch);
g
}
#[test]
fn test_find_entry() {
let cfg = create_test_cfg();
let entry = find_entry(&cfg);
assert!(entry.is_some());
assert_eq!(entry.unwrap().index(), 0);
}
#[test]
fn test_find_exits() {
let cfg = create_test_cfg();
let exits = find_exits(&cfg);
assert_eq!(exits.len(), 2);
let exit_ids: Vec<_> = exits
.iter()
.map(|&idx| cfg.node_weight(idx).unwrap().id)
.collect();
assert!(exit_ids.contains(&2));
assert!(exit_ids.contains(&3));
}
#[test]
fn test_is_exit_block() {
let cfg = create_test_cfg();
let b0 = NodeIndex::new(0);
let b1 = NodeIndex::new(1);
let b2 = NodeIndex::new(2);
let b3 = NodeIndex::new(3);
assert!(!is_exit_block(&cfg, b0)); assert!(!is_exit_block(&cfg, b1)); assert!(is_exit_block(&cfg, b2)); assert!(is_exit_block(&cfg, b3)); }
#[test]
fn test_is_branch_point() {
let cfg = create_test_cfg();
let b0 = NodeIndex::new(0);
let b1 = NodeIndex::new(1);
let b2 = NodeIndex::new(2);
assert!(!is_branch_point(&cfg, b0)); assert!(is_branch_point(&cfg, b1)); assert!(!is_branch_point(&cfg, b2)); }
#[test]
fn test_is_merge_point() {
let cfg = create_test_cfg();
let b0 = NodeIndex::new(0);
let b1 = NodeIndex::new(1);
assert!(!is_merge_point(&cfg, b0)); assert!(!is_merge_point(&cfg, b1)); }
#[test]
fn test_is_merge_point_with_actual_merge() {
let mut g = DiGraph::new();
let b0 = g.add_node(BasicBlock {
id: 0,
db_id: None,
kind: BlockKind::Entry,
statements: vec![],
terminator: Terminator::SwitchInt {
targets: vec![1],
otherwise: 2,
},
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b1 = g.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::Goto { target: 3 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b2 = g.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::Goto { target: 3 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b3 = g.add_node(BasicBlock {
id: 3,
db_id: None,
kind: BlockKind::Exit,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
g.add_edge(b0, b1, EdgeType::TrueBranch);
g.add_edge(b0, b2, EdgeType::FalseBranch);
g.add_edge(b1, b3, EdgeType::Fallthrough);
g.add_edge(b2, b3, EdgeType::Fallthrough);
assert!(!is_merge_point(&g, b0)); assert!(!is_merge_point(&g, b1)); assert!(!is_merge_point(&g, b2)); assert!(is_merge_point(&g, b3)); }
#[test]
fn test_multiple_exits_with_unwind() {
let mut g = DiGraph::new();
let b0 = g.add_node(BasicBlock {
id: 0,
db_id: None,
kind: BlockKind::Entry,
statements: vec![],
terminator: Terminator::Call {
target: Some(1),
unwind: Some(2),
},
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b1 = g.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Exit,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b2 = g.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Exit,
statements: vec![],
terminator: Terminator::Abort("panic".to_string()),
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
g.add_edge(b0, b1, EdgeType::Call);
g.add_edge(b0, b2, EdgeType::Exception);
let exits = find_exits(&g);
assert_eq!(exits.len(), 2);
}
#[test]
fn test_empty_cfg() {
let cfg: Cfg = DiGraph::new();
assert!(find_entry(&cfg).is_none());
assert!(find_exits(&cfg).is_empty());
}
#[test]
fn test_single_block_cfg() {
let mut g = DiGraph::new();
let b0 = g.add_node(BasicBlock {
id: 0,
db_id: None,
kind: BlockKind::Entry,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
assert_eq!(find_entry(&g), Some(b0));
assert_eq!(find_exits(&g), vec![b0]);
}
#[test]
fn test_unreachable_exit() {
let mut g = DiGraph::new();
let b0 = g.add_node(BasicBlock {
id: 0,
db_id: None,
kind: BlockKind::Entry,
statements: vec![],
terminator: Terminator::Goto { target: 1 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b1 = g.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let _b2 = g.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Exit,
statements: vec![],
terminator: Terminator::Unreachable,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
g.add_edge(b0, b1, EdgeType::Fallthrough);
let exits = find_exits(&g);
assert_eq!(exits.len(), 2);
let exit_ids: Vec<_> = exits
.iter()
.map(|&idx| g.node_weight(idx).unwrap().id)
.collect();
assert!(exit_ids.contains(&1)); assert!(exit_ids.contains(&2)); }
}