use ahash::{AHashMap, AHashSet};
use crate::errors::SqliteGraphError;
use crate::graph::SqliteGraph;
use crate::progress::ProgressCallback;
#[derive(Debug, Clone)]
pub struct PostDominatorResult {
pub post_dom: AHashMap<i64, AHashSet<i64>>,
pub ipdom: AHashMap<i64, Option<i64>>,
}
impl PostDominatorResult {
pub fn post_dominates(&self, post_dominator: i64, node: i64) -> bool {
self.post_dom
.get(&node)
.map(|set| set.contains(&post_dominator))
.unwrap_or(false)
}
pub fn immediate_post_dominator(&self, node: i64) -> Option<i64> {
self.ipdom.get(&node).copied().flatten()
}
pub fn is_exit(&self, node: i64) -> bool {
self.ipdom
.get(&node)
.map(|ipdom| ipdom.is_none())
.unwrap_or(false)
}
}
pub fn post_dominators(
graph: &SqliteGraph,
exit: i64,
) -> Result<PostDominatorResult, SqliteGraphError> {
let preds = build_predecessor_map(graph)?;
let all_nodes = graph.all_entity_ids()?;
if !all_nodes.contains(&exit) {
return Err(SqliteGraphError::not_found(format!(
"Exit node {} not found in graph",
exit
)));
}
let mut post_dom = initialize_post_dominators(&all_nodes, exit);
let order = reverse_postorder_reversed(&preds, exit, &all_nodes)?;
let max_iterations = 1000;
let mut iteration = 0;
loop {
let changed = iterate_post_dominators(&preds, &mut post_dom, &order)?;
if !changed {
break;
}
iteration += 1;
if iteration >= max_iterations {
return Err(SqliteGraphError::query(format!(
"Post-dominator computation failed to converge after {} iterations",
max_iterations
)));
}
}
let ipdom = extract_immediate_post_dominators(&post_dom, exit);
Ok(PostDominatorResult { post_dom, ipdom })
}
pub fn post_dominators_with_progress<F>(
graph: &SqliteGraph,
exit: i64,
progress: &F,
) -> Result<PostDominatorResult, SqliteGraphError>
where
F: ProgressCallback,
{
let preds = build_predecessor_map(graph)?;
let all_nodes = graph.all_entity_ids()?;
if !all_nodes.contains(&exit) {
return Err(SqliteGraphError::not_found(format!(
"Exit node {} not found in graph",
exit
)));
}
let mut post_dom = initialize_post_dominators(&all_nodes, exit);
let order = reverse_postorder_reversed(&preds, exit, &all_nodes)?;
let max_iterations = 1000;
let mut iteration = 0;
loop {
let changed = iterate_post_dominators(&preds, &mut post_dom, &order)?;
progress.on_progress(
iteration + 1,
None,
&format!(
"Post-dominator iteration {}: {} nodes processed",
iteration + 1,
order.len()
),
);
if !changed {
break;
}
iteration += 1;
if iteration >= max_iterations {
return Err(SqliteGraphError::query(format!(
"Post-dominator computation failed to converge after {} iterations",
max_iterations
)));
}
}
progress.on_complete();
let ipdom = extract_immediate_post_dominators(&post_dom, exit);
Ok(PostDominatorResult { post_dom, ipdom })
}
pub fn post_dominators_auto_exit(
graph: &SqliteGraph,
) -> Result<PostDominatorResult, SqliteGraphError> {
let all_nodes = graph.all_entity_ids()?;
if all_nodes.is_empty() {
return Ok(PostDominatorResult {
post_dom: AHashMap::new(),
ipdom: AHashMap::new(),
});
}
let mut exits = Vec::new();
for &node in &all_nodes {
let outgoing = graph.fetch_outgoing(node)?;
if outgoing.is_empty() {
exits.push(node);
}
}
if exits.is_empty() {
return Err(SqliteGraphError::query(
"Cannot compute post-dominators: graph has no exit nodes (all nodes have outgoing edges)",
));
}
if exits.len() == 1 {
post_dominators(graph, exits[0])
} else {
post_dominators_with_virtual_exit(graph, &exits)
}
}
fn build_predecessor_map(graph: &SqliteGraph) -> Result<AHashMap<i64, Vec<i64>>, SqliteGraphError> {
let mut preds = AHashMap::new();
let all_nodes = graph.all_entity_ids()?;
for &node in &all_nodes {
let outgoing = graph.fetch_outgoing(node)?;
for &succ in &outgoing {
preds.entry(succ).or_insert_with(Vec::new).push(node);
}
preds.entry(node).or_insert_with(Vec::new);
}
Ok(preds)
}
fn initialize_post_dominators(all_nodes: &[i64], exit: i64) -> AHashMap<i64, AHashSet<i64>> {
let mut post_dom = AHashMap::new();
let universal: AHashSet<i64> = all_nodes.iter().copied().collect();
for &node in all_nodes {
if node == exit {
let mut exit_dom = AHashSet::new();
exit_dom.insert(exit);
post_dom.insert(exit, exit_dom);
} else {
post_dom.insert(node, universal.clone());
}
}
post_dom
}
fn reverse_postorder_reversed(
preds: &AHashMap<i64, Vec<i64>>,
exit: i64,
_all_nodes: &[i64],
) -> Result<Vec<i64>, SqliteGraphError> {
let mut visited = AHashSet::new();
let mut seen = AHashSet::new();
let mut postorder = Vec::new();
let mut stack = vec![(exit, false)]; seen.insert(exit);
while let Some((node, visited_predecessors)) = stack.pop() {
if visited_predecessors {
visited.insert(node);
postorder.push(node);
} else {
stack.push((node, true));
if let Some(predecessors) = preds.get(&node) {
for pred in predecessors.iter().rev() {
if !visited.contains(pred) && seen.insert(*pred) {
stack.push((*pred, false));
}
}
}
}
}
postorder.reverse();
Ok(postorder)
}
fn iterate_post_dominators(
preds: &AHashMap<i64, Vec<i64>>,
post_dom: &mut AHashMap<i64, AHashSet<i64>>,
order: &[i64],
) -> Result<bool, SqliteGraphError> {
let mut changed = false;
for &node in order.iter().skip(1) {
if node == order.first().copied().unwrap_or(node) {
continue;
}
let mut successors = Vec::new();
for (&succ_node, pred_list) in preds.iter() {
if pred_list.contains(&node) {
successors.push(succ_node);
}
}
if successors.is_empty() {
continue;
}
let mut new_post_dom_set: Option<AHashSet<i64>> = None;
for &succ in &successors {
if let Some(succ_post_dom) = post_dom.get(&succ) {
if let Some(current) = &mut new_post_dom_set {
current.retain(|n| succ_post_dom.contains(n));
} else {
new_post_dom_set = Some(succ_post_dom.clone());
}
}
}
if let Some(mut intersected) = new_post_dom_set {
intersected.insert(node);
let old_post_dom_set = post_dom.get(&node);
if old_post_dom_set
.map(|old| old != &intersected)
.unwrap_or(true)
{
post_dom.insert(node, intersected);
changed = true;
}
}
}
Ok(changed)
}
fn extract_immediate_post_dominators(
post_dom: &AHashMap<i64, AHashSet<i64>>,
exit: i64,
) -> AHashMap<i64, Option<i64>> {
let mut ipdom = AHashMap::new();
ipdom.insert(exit, None);
for (&node, post_dom_set) in post_dom {
if node == exit {
continue;
}
let strict_post_dominators: Vec<i64> = post_dom_set
.iter()
.copied()
.filter(|&d| d != node)
.collect();
if strict_post_dominators.is_empty() {
ipdom.insert(node, None);
continue;
}
let mut immediate_post_dominator = None;
let mut max_post_dom_size = 0;
for &candidate in &strict_post_dominators {
if let Some(candidate_post_dom) = post_dom.get(&candidate) {
let is_dominated_by_all_others = strict_post_dominators
.iter()
.all(|&other| other == candidate || candidate_post_dom.contains(&other));
if is_dominated_by_all_others {
immediate_post_dominator = Some(candidate);
break;
}
let post_dom_size = candidate_post_dom.len();
if post_dom_size > max_post_dom_size {
max_post_dom_size = post_dom_size;
immediate_post_dominator = Some(candidate);
}
}
}
ipdom.insert(node, immediate_post_dominator);
}
ipdom
}
fn post_dominators_with_virtual_exit(
graph: &SqliteGraph,
exits: &[i64],
) -> Result<PostDominatorResult, SqliteGraphError> {
let mut preds = build_predecessor_map(graph)?;
let all_nodes = graph.all_entity_ids()?;
let virtual_exit = -1i64;
preds
.entry(virtual_exit)
.or_insert_with(Vec::new)
.extend(exits.iter().copied());
let mut extended_nodes = all_nodes.clone();
extended_nodes.push(virtual_exit);
let mut post_dom = initialize_post_dominators(&extended_nodes, virtual_exit);
let order = reverse_postorder_reversed(&preds, virtual_exit, &extended_nodes)?;
let max_iterations = 1000;
let mut iteration = 0;
loop {
let changed = iterate_post_dominators(&preds, &mut post_dom, &order)?;
if !changed {
break;
}
iteration += 1;
if iteration >= max_iterations {
return Err(SqliteGraphError::query(format!(
"Post-dominator computation failed to converge after {} iterations",
max_iterations
)));
}
}
let mut ipdom = extract_immediate_post_dominators(&post_dom, virtual_exit);
post_dom.remove(&virtual_exit);
ipdom.remove(&virtual_exit);
for (_, set) in post_dom.iter_mut() {
set.remove(&virtual_exit);
}
for (_, ipdom_val) in ipdom.iter_mut() {
if *ipdom_val == Some(virtual_exit) {
*ipdom_val = None;
}
}
Ok(PostDominatorResult { post_dom, ipdom })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{GraphEdge, GraphEntity};
fn create_linear_chain() -> SqliteGraph {
let graph = SqliteGraph::open_in_memory().expect("Failed to create graph");
for i in 0..4 {
let entity = GraphEntity {
id: 0,
kind: "node".to_string(),
name: format!("node_{}", i),
file_path: Some(format!("node_{}.rs", i)),
data: serde_json::json!({"index": i}),
};
graph
.insert_entity(&entity)
.expect("Failed to insert entity");
}
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
for i in 0..entity_ids.len().saturating_sub(1) {
let edge = GraphEdge {
id: 0,
from_id: entity_ids[i],
to_id: entity_ids[i + 1],
edge_type: "next".to_string(),
data: serde_json::json!({}),
};
graph.insert_edge(&edge).expect("Failed to insert edge");
}
graph
}
fn create_diamond_cfg() -> SqliteGraph {
let graph = SqliteGraph::open_in_memory().expect("Failed to create graph");
for i in 0..4 {
let entity = GraphEntity {
id: 0,
kind: "node".to_string(),
name: format!("node_{}", i),
file_path: Some(format!("node_{}.rs", i)),
data: serde_json::json!({"index": i}),
};
graph
.insert_entity(&entity)
.expect("Failed to insert entity");
}
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let edges = vec![(0, 1), (0, 2), (1, 3), (2, 3)];
for (from_idx, to_idx) in edges {
let edge = GraphEdge {
id: 0,
from_id: entity_ids[from_idx],
to_id: entity_ids[to_idx],
edge_type: "next".to_string(),
data: serde_json::json!({}),
};
graph.insert_edge(&edge).expect("Failed to insert edge");
}
graph
}
fn create_loop_cfg() -> SqliteGraph {
let graph = SqliteGraph::open_in_memory().expect("Failed to create graph");
for i in 0..3 {
let entity = GraphEntity {
id: 0,
kind: "node".to_string(),
name: format!("node_{}", i),
file_path: Some(format!("node_{}.rs", i)),
data: serde_json::json!({"index": i}),
};
graph
.insert_entity(&entity)
.expect("Failed to insert entity");
}
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let edges = vec![(0, 1), (1, 2), (2, 1)];
for (from_idx, to_idx) in edges {
let edge = GraphEdge {
id: 0,
from_id: entity_ids[from_idx],
to_id: entity_ids[to_idx],
edge_type: "next".to_string(),
data: serde_json::json!({}),
};
graph.insert_edge(&edge).expect("Failed to insert edge");
}
graph
}
fn create_multiple_exits_cfg() -> SqliteGraph {
let graph = SqliteGraph::open_in_memory().expect("Failed to create graph");
for i in 0..5 {
let entity = GraphEntity {
id: 0,
kind: "node".to_string(),
name: format!("node_{}", i),
file_path: Some(format!("node_{}.rs", i)),
data: serde_json::json!({"index": i}),
};
graph
.insert_entity(&entity)
.expect("Failed to insert entity");
}
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let edges = vec![(0, 1), (1, 2), (0, 3), (3, 4)];
for (from_idx, to_idx) in edges {
let edge = GraphEdge {
id: 0,
from_id: entity_ids[from_idx],
to_id: entity_ids[to_idx],
edge_type: "next".to_string(),
data: serde_json::json!({}),
};
graph.insert_edge(&edge).expect("Failed to insert edge");
}
graph
}
fn create_single_exit_cfg() -> SqliteGraph {
let graph = SqliteGraph::open_in_memory().expect("Failed to create graph");
for i in 0..4 {
let entity = GraphEntity {
id: 0,
kind: "node".to_string(),
name: format!("node_{}", i),
file_path: Some(format!("node_{}.rs", i)),
data: serde_json::json!({"index": i}),
};
graph
.insert_entity(&entity)
.expect("Failed to insert entity");
}
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
for i in 0..entity_ids.len().saturating_sub(1) {
let edge = GraphEdge {
id: 0,
from_id: entity_ids[i],
to_id: entity_ids[i + 1],
edge_type: "next".to_string(),
data: serde_json::json!({}),
};
graph.insert_edge(&edge).expect("Failed to insert edge");
}
graph
}
#[test]
fn test_post_dominators_linear_chain() {
let graph = create_linear_chain();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let exit = entity_ids[3];
let result = post_dominators(&graph, exit).expect("Failed to compute post-dominators");
for &node in &entity_ids {
assert!(
result.post_dominates(exit, node),
"Exit should post-dominate node {}",
node
);
}
assert!(
result.post_dominates(entity_ids[2], entity_ids[0]),
"Node 3 should post-dominate node 1"
);
assert!(
result.post_dominates(entity_ids[2], entity_ids[1]),
"Node 3 should post-dominate node 2"
);
assert!(
result.post_dominates(entity_ids[2], entity_ids[2]),
"Node 3 should post-dominate itself"
);
assert!(
!result.post_dominates(entity_ids[2], entity_ids[3]),
"Node 3 should NOT post-dominate exit 4"
);
assert!(
result.post_dominates(entity_ids[1], entity_ids[0]),
"Node 2 should post-dominate node 1"
);
assert!(
result.post_dominates(entity_ids[1], entity_ids[1]),
"Node 2 should post-dominate itself"
);
assert!(
!result.post_dominates(entity_ids[1], entity_ids[2]),
"Node 2 should NOT post-dominate node 3"
);
assert!(
!result.post_dominates(entity_ids[1], entity_ids[3]),
"Node 2 should NOT post-dominate exit 4"
);
assert!(
result.post_dominates(entity_ids[0], entity_ids[0]),
"Node 1 should post-dominate itself"
);
assert!(
!result.post_dominates(entity_ids[0], entity_ids[1]),
"Node 1 should NOT post-dominate node 2"
);
assert!(
!result.post_dominates(entity_ids[0], entity_ids[2]),
"Node 1 should NOT post-dominate node 3"
);
assert!(
!result.post_dominates(entity_ids[0], entity_ids[3]),
"Node 1 should NOT post-dominate exit 4"
);
}
#[test]
fn test_post_dominators_diamond() {
let graph = create_diamond_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let exit = entity_ids[3];
let result = post_dominators(&graph, exit).expect("Failed to compute post-dominators");
for &node in &entity_ids {
assert!(
result.post_dominates(exit, node),
"Exit should post-dominate {}",
node
);
}
assert!(result.post_dominates(entity_ids[0], entity_ids[0]));
assert!(!result.post_dominates(entity_ids[0], entity_ids[1]));
assert!(!result.post_dominates(entity_ids[0], entity_ids[2]));
assert!(!result.post_dominates(entity_ids[0], entity_ids[3]));
assert!(result.post_dominates(entity_ids[1], entity_ids[1]));
assert!(!result.post_dominates(entity_ids[1], exit));
assert!(!result.post_dominates(entity_ids[1], entity_ids[0]));
assert!(!result.post_dominates(entity_ids[1], entity_ids[2]));
assert!(result.post_dominates(entity_ids[2], entity_ids[2]));
assert!(!result.post_dominates(entity_ids[2], exit));
assert!(!result.post_dominates(entity_ids[2], entity_ids[0]));
assert!(!result.post_dominates(entity_ids[2], entity_ids[1]));
}
#[test]
fn test_post_dominators_loop() {
let graph = create_loop_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let exit = entity_ids[2];
let result = post_dominators(&graph, exit).expect("Failed to compute post-dominators");
for &node in &entity_ids {
assert!(
result.post_dominates(exit, node),
"Exit should post-dominate {}",
node
);
}
}
#[test]
fn test_immediate_post_dominators_linear() {
let graph = create_linear_chain();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let exit = entity_ids[3];
let result = post_dominators(&graph, exit).expect("Failed to compute post-dominators");
eprintln!("Entity IDs: {:?}", entity_ids);
eprintln!("Exit: {}", exit);
eprintln!("Post-dominators: {:?}", result.post_dom);
eprintln!("IPDOM: {:?}", result.ipdom);
assert_eq!(
result.immediate_post_dominator(exit),
None,
"Exit should have no immediate post-dominator"
);
assert_eq!(
result.immediate_post_dominator(entity_ids[2]),
Some(exit),
"ipdom(2) should be 3"
);
assert_eq!(
result.immediate_post_dominator(entity_ids[1]),
Some(entity_ids[2]),
"ipdom(1) should be 2"
);
assert_eq!(
result.immediate_post_dominator(entity_ids[0]),
Some(entity_ids[1]),
"ipdom(0) should be 1"
);
}
#[test]
fn test_immediate_post_dominators_diamond() {
let graph = create_diamond_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let exit = entity_ids[3];
let result = post_dominators(&graph, exit).expect("Failed to compute post-dominators");
assert_eq!(
result.immediate_post_dominator(exit),
None,
"Exit should have no ipdom"
);
assert_eq!(
result.immediate_post_dominator(entity_ids[1]),
Some(exit),
"ipdom(1) should be exit"
);
assert_eq!(
result.immediate_post_dominator(entity_ids[2]),
Some(exit),
"ipdom(2) should be exit"
);
assert_eq!(
result.immediate_post_dominator(entity_ids[0]),
Some(exit),
"ipdom(0) should be exit (all paths from 0 go through exit)"
);
}
#[test]
fn test_immediate_post_dominators_loop() {
let graph = create_loop_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let exit = entity_ids[2];
let result = post_dominators(&graph, exit).expect("Failed to compute post-dominators");
assert_eq!(result.immediate_post_dominator(exit), None);
assert_eq!(
result.immediate_post_dominator(entity_ids[1]),
Some(exit),
"ipdom(1) should be exit"
);
}
#[test]
fn test_immediate_post_dominators_exit_is_none() {
let graph = create_single_exit_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let exit = entity_ids[3];
let result = post_dominators(&graph, exit).expect("Failed to compute post-dominators");
assert_eq!(result.immediate_post_dominator(exit), None);
}
#[test]
fn test_post_dominators_auto_single_exit() {
let graph = create_single_exit_cfg();
let result = post_dominators_auto_exit(&graph).expect("Failed to auto-detect exit");
let entity_ids = graph.list_entity_ids().expect("Failed to get IDs");
assert!(!entity_ids.is_empty());
let exit = entity_ids[entity_ids.len() - 1];
assert!(result.is_exit(exit));
}
#[test]
fn test_post_dominators_auto_multiple_exits() {
let graph = create_multiple_exits_cfg();
let result = post_dominators_auto_exit(&graph).expect("Failed to auto-detect exits");
let entity_ids = graph.list_entity_ids().expect("Failed to get IDs");
eprintln!("Entity IDs: {:?}", entity_ids);
eprintln!("Post-dominators: {:?}", result.post_dom);
eprintln!("IPDOM: {:?}", result.ipdom);
assert_eq!(result.post_dom.len(), entity_ids.len());
assert_eq!(
result.immediate_post_dominator(entity_ids[2]),
None,
"Exit 2 should have no ipdom"
);
assert_eq!(
result.immediate_post_dominator(entity_ids[4]),
None,
"Exit 4 should have no ipdom"
);
}
#[test]
fn test_post_dominators_virtual_exit_unification() {
let graph = create_multiple_exits_cfg();
let result = post_dominators_auto_exit(&graph).expect("Failed with virtual exit");
let entity_ids = graph.list_entity_ids().expect("Failed to get IDs");
assert_eq!(result.immediate_post_dominator(entity_ids[2]), None);
assert_eq!(result.immediate_post_dominator(entity_ids[4]), None);
assert!(!result.post_dom.contains_key(&-1));
assert!(!result.ipdom.contains_key(&-1));
}
#[test]
fn test_post_dominators_no_exits() {
let graph = SqliteGraph::open_in_memory().expect("Failed to create graph");
for i in 0..3 {
let entity = GraphEntity {
id: 0,
kind: "node".to_string(),
name: format!("node_{}", i),
file_path: Some(format!("node_{}.rs", i)),
data: serde_json::json!({"index": i}),
};
graph
.insert_entity(&entity)
.expect("Failed to insert entity");
}
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let edges = vec![(0, 1), (1, 2), (2, 0)];
for (from_idx, to_idx) in edges {
let edge = GraphEdge {
id: 0,
from_id: entity_ids[from_idx],
to_id: entity_ids[to_idx],
edge_type: "next".to_string(),
data: serde_json::json!({}),
};
graph.insert_edge(&edge).expect("Failed to insert edge");
}
let result = post_dominators_auto_exit(&graph);
assert!(result.is_err(), "Should fail on graph with no exits");
}
#[test]
fn test_post_dominators_empty_graph() {
let graph = SqliteGraph::open_in_memory().expect("Failed to create graph");
let result = post_dominators_auto_exit(&graph).expect("Failed on empty graph");
assert_eq!(result.post_dom.len(), 0);
assert_eq!(result.ipdom.len(), 0);
}
#[test]
fn test_post_dominators_nonexistent_exit() {
let graph = create_linear_chain();
let result = post_dominators(&graph, 999);
assert!(result.is_err(), "Should fail for nonexistent exit");
if let Err(SqliteGraphError::NotFound(msg)) = result {
assert!(msg.contains("999"), "Error should mention node 999");
} else {
panic!("Expected NotFound error");
}
}
#[test]
fn test_post_dominators_self_post_dominance() {
let graph = create_diamond_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let exit = entity_ids[3];
let result = post_dominators(&graph, exit).expect("Failed to compute post-dominators");
for &node in &entity_ids {
assert!(
result.post_dominates(node, node),
"Node {} should post-dominate itself",
node
);
}
}
#[test]
fn test_post_dominators_single_node() {
let graph = SqliteGraph::open_in_memory().expect("Failed to create graph");
let entity = GraphEntity {
id: 0,
kind: "node".to_string(),
name: "single".to_string(),
file_path: Some("single.rs".to_string()),
data: serde_json::json!({}),
};
graph
.insert_entity(&entity)
.expect("Failed to insert entity");
let entity_ids = graph.list_entity_ids().expect("Failed to get IDs");
let exit = entity_ids[0];
let result = post_dominators(&graph, exit).expect("Failed to compute post-dominators");
assert!(result.post_dominates(exit, exit));
assert_eq!(result.immediate_post_dominator(exit), None);
assert_eq!(
result.post_dom.len(),
1,
"Should have 1 node in post_dom sets"
);
}
#[test]
fn test_post_dominators_is_exit() {
let graph = create_diamond_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let exit = entity_ids[3];
let result = post_dominators(&graph, exit).expect("Failed to compute post-dominators");
assert!(result.is_exit(exit), "Exit should be identified as exit");
assert!(
!result.is_exit(entity_ids[0]),
"Non-exit should not be exit"
);
assert!(
!result.is_exit(entity_ids[1]),
"Non-exit should not be exit"
);
assert!(
!result.is_exit(entity_ids[2]),
"Non-exit should not be exit"
);
}
#[test]
fn test_post_dominators_with_progress() {
use crate::progress::NoProgress;
let graph = create_diamond_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let exit = entity_ids[3];
let progress = NoProgress;
let result_with = post_dominators_with_progress(&graph, exit, &progress).expect("Failed");
let result_without = post_dominators(&graph, exit).expect("Failed");
assert_eq!(
result_with.post_dom.len(),
result_without.post_dom.len(),
"Should have same number of nodes"
);
for (&node, post_dom_set) in &result_without.post_dom {
assert!(
result_with.post_dom.contains_key(&node),
"Progress result missing node {}",
node
);
assert_eq!(
result_with.post_dom.get(&node),
Some(post_dom_set),
"Post-dominance sets differ for node {}",
node
);
}
assert_eq!(result_with.ipdom, result_without.ipdom);
}
#[test]
fn test_post_dominators_symmetry_property() {
let graph = create_linear_chain();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let exit = entity_ids[3];
let result = post_dominators(&graph, exit).expect("Failed to compute post-dominators");
for &node in &entity_ids {
assert!(
result.post_dominates(node, node),
"Reflexivity failed for {}",
node
);
}
assert!(result.post_dominates(exit, entity_ids[2]));
assert!(result.post_dominates(entity_ids[2], entity_ids[1]));
assert!(
result.post_dominates(exit, entity_ids[1]),
"Transitivity failed"
);
for i in 0..entity_ids.len() {
for j in (i + 1)..entity_ids.len() {
let a = entity_ids[i];
let b = entity_ids[j];
let a_post_dom_b = result.post_dominates(a, b);
let b_post_dom_a = result.post_dominates(b, a);
assert!(
!(a_post_dom_b && b_post_dom_a),
"Antisymmetry failed: {} and {} mutually post-dominate",
a,
b
);
}
}
}
}