use crate::cfg::analysis::find_entry;
use crate::cfg::{BlockId, Cfg};
use petgraph::algo::has_path_connecting;
use petgraph::algo::DfsSpace;
use petgraph::graph::NodeIndex;
use petgraph::visit::Dfs;
use std::collections::HashSet;
pub fn find_reachable(cfg: &Cfg) -> Vec<NodeIndex> {
let entry = match find_entry(cfg) {
Some(e) => e,
None => return vec![],
};
let mut dfs = Dfs::new(cfg, entry);
let mut reachable = Vec::new();
while let Some(node) = dfs.next(cfg) {
reachable.push(node);
}
reachable
}
pub fn find_unreachable(cfg: &Cfg) -> Vec<NodeIndex> {
if find_entry(cfg).is_none() {
return vec![];
}
let reachable: HashSet<_> = find_reachable(cfg).into_iter().collect();
cfg.node_indices()
.filter(|&n| !reachable.contains(&n))
.collect()
}
pub fn is_reachable_from_entry(cfg: &Cfg, block: NodeIndex) -> bool {
let entry = match find_entry(cfg) {
Some(e) => e,
None => return false,
};
has_path_connecting(cfg, entry, block, None)
}
pub fn unreachable_block_ids(cfg: &Cfg) -> Vec<BlockId> {
find_unreachable(cfg)
.iter()
.filter_map(|&idx| cfg.node_weight(idx))
.map(|block| block.id)
.collect()
}
pub fn can_reach(cfg: &Cfg, from: NodeIndex, to: NodeIndex) -> bool {
has_path_connecting(cfg, from, to, None)
}
pub fn can_reach_cached(
cfg: &Cfg,
from: NodeIndex,
to: NodeIndex,
space: &mut DfsSpace<NodeIndex, <Cfg as petgraph::visit::Visitable>::Map>,
) -> bool {
has_path_connecting(cfg, from, to, Some(space))
}
pub struct ReachabilityCache {
space: DfsSpace<NodeIndex, <Cfg as petgraph::visit::Visitable>::Map>,
}
impl ReachabilityCache {
pub fn new(cfg: &Cfg) -> Self {
Self {
space: DfsSpace::new(cfg),
}
}
pub fn can_reach(&mut self, cfg: &Cfg, from: NodeIndex, to: NodeIndex) -> bool {
can_reach_cached(cfg, from, to, &mut self.space)
}
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct BlockImpact {
pub source_block_id: BlockId,
pub reachable_blocks: Vec<BlockId>,
pub reachable_count: usize,
pub max_depth_reached: usize,
pub has_cycles: bool,
}
pub fn find_reachable_from_block(
cfg: &Cfg,
start_block_id: BlockId,
max_depth: Option<usize>,
) -> BlockImpact {
use std::collections::{HashSet, VecDeque};
let start_node = match cfg.node_indices().find(|&n| cfg[n].id == start_block_id) {
Some(n) => n,
None => {
return BlockImpact {
source_block_id: start_block_id,
reachable_blocks: vec![],
reachable_count: 0,
max_depth_reached: 0,
has_cycles: false,
};
}
};
let max_depth = max_depth.unwrap_or(usize::MAX);
let mut visited: HashSet<NodeIndex> = HashSet::new();
let mut queue: VecDeque<(NodeIndex, usize)> = VecDeque::new();
let mut reachable_blocks = Vec::new();
let mut max_depth_reached = 0;
let mut has_cycles = false;
queue.push_back((start_node, 0));
visited.insert(start_node);
while let Some((node, depth)) = queue.pop_front() {
max_depth_reached = max_depth_reached.max(depth);
let block_id = cfg[node].id;
reachable_blocks.push(block_id);
if depth >= max_depth {
continue;
}
for neighbor in cfg.neighbors(node) {
if visited.contains(&neighbor) {
has_cycles = true;
} else {
visited.insert(neighbor);
queue.push_back((neighbor, depth + 1));
}
}
}
reachable_blocks.retain(|&id| id != start_block_id);
let reachable_count = reachable_blocks.len();
BlockImpact {
source_block_id: start_block_id,
reachable_blocks,
reachable_count,
max_depth_reached,
has_cycles,
}
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct PathImpact {
pub path_id: String,
pub path_length: usize,
pub unique_blocks_affected: Vec<BlockId>,
pub impact_count: usize,
}
pub fn compute_path_impact(
cfg: &Cfg,
path_block_ids: &[BlockId],
max_depth: Option<usize>,
) -> PathImpact {
use std::collections::HashSet;
let mut all_affected: HashSet<BlockId> = HashSet::new();
for &block_id in path_block_ids {
let impact = find_reachable_from_block(cfg, block_id, max_depth);
all_affected.extend(impact.reachable_blocks);
}
for &block_id in path_block_ids {
all_affected.remove(&block_id);
}
let mut affected_vec: Vec<BlockId> = all_affected.into_iter().collect();
affected_vec.sort();
let impact_count = affected_vec.len();
PathImpact {
path_id: "[computed]".to_string(), path_length: path_block_ids.len(),
unique_blocks_affected: affected_vec,
impact_count,
}
}
#[cfg(test)]
mod tests;