Skip to content

Commit 61bae74

Browse files
committed
improvement(semantic/cfg): better CFG API (#3472)
1 parent 3c7ee85 commit 61bae74

File tree

3 files changed

+37
-21
lines changed

3 files changed

+37
-21
lines changed

crates/oxc_linter/src/rules/react/rules_of_hooks.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,7 @@ impl Rule for RulesOfHooks {
228228
return;
229229
}
230230

231-
if !petgraph::algo::has_path_connecting(
232-
&semantic.cfg().graph,
233-
func_cfg_id,
234-
node_cfg_id,
235-
None,
236-
) {
231+
if !ctx.semantic().cfg().is_reachabale(func_cfg_id, node_cfg_id) {
237232
// There should always be a control flow path between a parent and child node.
238233
// If there is none it means we always do an early exit before reaching our hook call.
239234
// In some cases it might mean that we are operating on an invalid `cfg` but in either

crates/oxc_semantic/examples/cfg.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ fn main() -> std::io::Result<()> {
111111
}
112112
});
113113
format!(
114-
"xlabel = \"nodes [{}]\\l\", label = \"bb{}\n{}\"",
114+
"xlabel = \"nodes{} [{}]\\l\", label = \"bb{}\n{}\"",
115+
node.1,
115116
nodes,
116117
node.1,
117118
semantic.semantic.cfg().basic_blocks[*node.1]

crates/oxc_semantic/src/control_flow/mod.rs

+34-14
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
mod builder;
22
mod dot;
33

4-
use itertools::Itertools;
54
use oxc_span::CompactStr;
65
use oxc_syntax::operator::{
76
AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator, UpdateOperator,
87
};
98
use petgraph::{
109
stable_graph::NodeIndex,
11-
visit::{depth_first_search, Dfs, DfsEvent, Walker},
10+
visit::{depth_first_search, Control, DfsEvent},
1211
Graph,
1312
};
1413

@@ -192,19 +191,28 @@ impl ControlFlowGraph {
192191
}
193192

194193
pub fn is_reachabale(&self, from: BasicBlockId, to: BasicBlockId) -> bool {
194+
if from == to {
195+
return true;
196+
}
195197
let graph = &self.graph;
196-
let mut dfs = Dfs::empty(graph);
197-
dfs.reset(graph);
198-
dfs.move_to(from);
199-
dfs.iter(graph)
200-
.take_while_inclusive(|it| {
201-
!self
202-
.basic_block(*it)
203-
.instructions()
204-
.iter()
205-
.any(|it| matches!(it, Instruction { kind: InstructionKind::Unreachable, .. }))
206-
})
207-
.any(|x| x == to)
198+
depth_first_search(&self.graph, Some(from), |event| match event {
199+
DfsEvent::TreeEdge(a, b) => {
200+
let unreachable = graph.edges_connecting(a, b).all(|edge| {
201+
matches!(edge.weight(), EdgeType::NewFunction | EdgeType::Unreachable)
202+
});
203+
204+
if unreachable {
205+
Control::Prune
206+
} else if b == to {
207+
return Control::Break(true);
208+
} else {
209+
Control::Continue
210+
}
211+
}
212+
_ => Control::Continue,
213+
})
214+
.break_value()
215+
.unwrap_or(false)
208216
}
209217

210218
pub fn is_cyclic(&self, node: BasicBlockId) -> bool {
@@ -214,6 +222,18 @@ impl ControlFlowGraph {
214222
})
215223
.is_err()
216224
}
225+
226+
pub fn has_conditional_path(&self, from: BasicBlockId, to: BasicBlockId) -> bool {
227+
let graph = &self.graph;
228+
// All nodes should be able to reach the `to` node, Otherwise we have a conditional/branching flow.
229+
petgraph::algo::dijkstra(graph, from, Some(to), |e| match e.weight() {
230+
EdgeType::NewFunction => 1,
231+
EdgeType::Jump | EdgeType::Unreachable | EdgeType::Backedge | EdgeType::Normal => 0,
232+
})
233+
.into_iter()
234+
.filter(|(_, val)| *val == 0)
235+
.any(|(f, _)| !self.is_reachabale(f, to))
236+
}
217237
}
218238

219239
pub struct PreservedExpressionState {

0 commit comments

Comments
 (0)