use crate::flow_graph_builder::FlowGraph;
use rustc_hash::FxHashSet;
use tsz_binder::{FlowNodeId, flow_flags};
use tsz_parser::parser::NodeIndex;
use tsz_parser::parser::node::NodeArena;
pub struct ReachabilityAnalyzer<'a> {
graph: &'a FlowGraph,
arena: &'a NodeArena,
}
impl<'a> ReachabilityAnalyzer<'a> {
pub const fn new(graph: &'a FlowGraph, arena: &'a NodeArena) -> Self {
Self { graph, arena }
}
pub fn is_unreachable(&self, node: NodeIndex) -> bool {
self.graph.is_unreachable(node)
}
pub const fn get_unreachable_nodes(&self) -> &FxHashSet<u32> {
&self.graph.unreachable_nodes
}
pub fn is_reachable(&self, node: NodeIndex) -> bool {
!self.is_unreachable(node)
}
pub fn unreachable_count(&self) -> usize {
self.graph.unreachable_nodes.len()
}
pub fn analyze_from(&mut self, entry: FlowNodeId) {
let mut visited: FxHashSet<FlowNodeId> = FxHashSet::default();
let mut worklist: Vec<FlowNodeId> = vec![entry];
while let Some(flow_id) = worklist.pop() {
if visited.contains(&flow_id) {
continue;
}
let Some(flow_node) = self.graph.nodes.get(flow_id) else {
continue;
};
if flow_node.has_any_flags(flow_flags::UNREACHABLE) {
continue;
}
visited.insert(flow_id);
for &antecedent in &flow_node.antecedent {
if antecedent != FlowNodeId::NONE && !visited.contains(&antecedent) {
worklist.push(antecedent);
}
}
}
}
pub fn can_reach(&self, _entry: FlowNodeId, target: NodeIndex) -> bool {
self.is_reachable(target)
}
pub fn get_unreachability_reason(&self, node: NodeIndex) -> Option<&'static str> {
if !self.is_unreachable(node) {
return None;
}
self.arena.get(node)?;
Some("Unreachable code")
}
pub fn find_unreachable_in_block(&self, statements: &[NodeIndex]) -> Vec<NodeIndex> {
statements
.iter()
.filter(|&&node| self.is_unreachable(node))
.copied()
.collect()
}
pub fn has_unreachable_code(&self) -> bool {
!self.graph.unreachable_nodes.is_empty()
}
}
#[cfg(test)]
#[path = "../../tests/reachability_analyzer.rs"]
mod tests;