1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! Reachability Analysis for detecting unreachable code.
//!
//! This module provides the `ReachabilityAnalyzer` for analyzing code paths
//! and detecting unreachable statements after return/throw/break/continue.
//!
//! The analysis is performed during `FlowGraph` construction, which marks nodes
//! as unreachable when they follow control flow statements that prevent execution.
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;
/// Analyzer for detecting unreachable code.
///
/// This provides a high-level API for querying reachability information
/// from a `FlowGraph`. The `FlowGraph` automatically tracks unreachable nodes
/// during construction via the `FlowGraphBuilder`.
pub struct ReachabilityAnalyzer<'a> {
/// Reference to the flow graph
graph: &'a FlowGraph,
/// Reference to the `NodeArena` for AST access
arena: &'a NodeArena,
}
impl<'a> ReachabilityAnalyzer<'a> {
/// Create a new reachability analyzer.
pub const fn new(graph: &'a FlowGraph, arena: &'a NodeArena) -> Self {
Self { graph, arena }
}
/// Check if a node is definitely unreachable.
pub fn is_unreachable(&self, node: NodeIndex) -> bool {
self.graph.is_unreachable(node)
}
/// Get all unreachable nodes in the graph.
pub const fn get_unreachable_nodes(&self) -> &FxHashSet<u32> {
&self.graph.unreachable_nodes
}
/// Check if a node is reachable (the opposite of unreachable).
pub fn is_reachable(&self, node: NodeIndex) -> bool {
!self.is_unreachable(node)
}
/// Get the number of unreachable nodes in the graph.
pub fn unreachable_count(&self) -> usize {
self.graph.unreachable_nodes.len()
}
/// Analyze reachability starting from a specific flow node.
///
/// This performs a forward traversal to identify all reachable nodes
/// from the given entry point. Nodes not reached during traversal are
/// considered unreachable.
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;
};
// Skip unreachable nodes during traversal
if flow_node.has_any_flags(flow_flags::UNREACHABLE) {
continue;
}
visited.insert(flow_id);
// Add antecedents to worklist (forward flow)
for &antecedent in &flow_node.antecedent {
if antecedent != FlowNodeId::NONE && !visited.contains(&antecedent) {
worklist.push(antecedent);
}
}
}
// Nodes not visited during forward traversal are unreachable
// (this is already tracked during graph construction, so we use that info)
}
/// Check if code execution can reach a specific node from an entry point.
pub fn can_reach(&self, _entry: FlowNodeId, target: NodeIndex) -> bool {
// For now, use the precomputed unreachable set
self.is_reachable(target)
}
/// Get a human-readable description of why a node is unreachable.
pub fn get_unreachability_reason(&self, node: NodeIndex) -> Option<&'static str> {
if !self.is_unreachable(node) {
return None;
}
let _node_data = self.arena.get(node)?;
// Check what kind of node precedes this one to determine the reason
// For now, return a generic message
Some("Unreachable code")
}
/// Find all unreachable statements in a block of statements.
pub fn find_unreachable_in_block(&self, statements: &[NodeIndex]) -> Vec<NodeIndex> {
statements
.iter()
.filter(|&&node| self.is_unreachable(node))
.copied()
.collect()
}
/// Check if there are any unreachable code paths in the graph.
pub fn has_unreachable_code(&self) -> bool {
!self.graph.unreachable_nodes.is_empty()
}
}
#[cfg(test)]
#[path = "../tests/reachability_analyzer.rs"]
mod tests;