Skip to content

Commit 60f89d1

Browse files
committed
improvement(semantic/cfg): better throw control flow. (#3473)
https://github.com/rzvxa/oxlint-ecosystem-ci/actions/runs/9306698136
1 parent 987a3f3 commit 60f89d1

File tree

5 files changed

+46
-78
lines changed

5 files changed

+46
-78
lines changed

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

+29-76
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
use itertools::{FoldWhile, Itertools};
21
use oxc_ast::{
32
ast::{ArrowFunctionExpression, Function},
43
AstKind,
54
};
65
use oxc_macros::declare_oxc_lint;
76
use oxc_semantic::{
8-
petgraph::{self},
9-
pg::neighbors_filtered_by_edge_weight,
10-
AstNodeId, AstNodes, BasicBlockId, EdgeType, Instruction, InstructionKind,
7+
algo, petgraph::visit::Control, AstNodeId, AstNodes, BasicBlockId, EdgeType, InstructionKind,
118
};
129
use oxc_span::{Atom, CompactStr};
1310
use oxc_syntax::operator::AssignmentOperator;
@@ -241,87 +238,43 @@ impl Rule for RulesOfHooks {
241238
return ctx.diagnostic(diagnostics::loop_hook(span, hook_name));
242239
}
243240

244-
if Self::is_conditional(ctx, func_cfg_id, node_cfg_id)
245-
|| Self::breaks_early(ctx, func_cfg_id, node_cfg_id)
246-
{
241+
if has_conditional_path_accept_throw(ctx, func_cfg_id, node_cfg_id) {
247242
#[allow(clippy::needless_return)]
248243
return ctx.diagnostic(diagnostics::conditional_hook(span, hook_name));
249244
}
250245
}
251246
}
252247

253-
// TODO: all `dijkstra` algorithms can be merged together for better performance.
254-
impl RulesOfHooks {
255-
#[inline]
256-
fn is_conditional(
257-
ctx: &LintContext,
258-
func_cfg_id: BasicBlockId,
259-
node_cfg_id: BasicBlockId,
260-
) -> bool {
261-
let cfg = ctx.semantic().cfg();
262-
let graph = &cfg.graph;
263-
// All nodes should be reachable from our hook, Otherwise we have a conditional/branching flow.
264-
petgraph::algo::dijkstra(graph, func_cfg_id, Some(node_cfg_id), |e| match e.weight() {
265-
EdgeType::NewFunction => 1,
266-
EdgeType::Jump | EdgeType::Unreachable | EdgeType::Backedge | EdgeType::Normal => 0,
248+
fn has_conditional_path_accept_throw(
249+
ctx: &LintContext<'_>,
250+
from: BasicBlockId,
251+
to: BasicBlockId,
252+
) -> bool {
253+
let cfg = ctx.semantic().cfg();
254+
let graph = &cfg.graph;
255+
// All nodes should be able to reach the hook node, Otherwise we have a conditional/branching flow.
256+
algo::dijkstra(graph, from, Some(to), |e| match e.weight() {
257+
EdgeType::NewFunction => 1,
258+
EdgeType::Jump | EdgeType::Unreachable | EdgeType::Backedge | EdgeType::Normal => 0,
259+
})
260+
.into_iter()
261+
.filter(|(_, val)| *val == 0)
262+
.any(|(f, _)| {
263+
!cfg.is_reachabale_filtered(f, to, |it| {
264+
if cfg
265+
.basic_block(it)
266+
.instructions()
267+
.iter()
268+
.any(|i| matches!(i.kind, InstructionKind::Throw))
269+
{
270+
Control::Break(true)
271+
} else {
272+
Control::Continue
273+
}
267274
})
268-
.into_iter()
269-
.filter(|(_, val)| *val == 0)
270-
.any(|(f, _)| !cfg.is_reachabale(f, node_cfg_id))
271-
}
272-
273-
#[inline]
274-
fn breaks_early(
275-
ctx: &LintContext,
276-
func_cfg_id: BasicBlockId,
277-
node_cfg_id: BasicBlockId,
278-
) -> bool {
279-
let cfg = ctx.semantic().cfg();
280-
neighbors_filtered_by_edge_weight(
281-
&cfg.graph,
282-
func_cfg_id,
283-
&|e| match e {
284-
EdgeType::Jump | EdgeType::Normal => None,
285-
EdgeType::Unreachable | EdgeType::Backedge | EdgeType::NewFunction => {
286-
Some(State::default())
287-
}
288-
},
289-
&mut |id: &BasicBlockId, mut state: State| {
290-
if node_cfg_id == *id {
291-
return (state, false);
292-
}
293-
294-
let (push, keep_walking) = cfg
295-
.basic_block(*id)
296-
.instructions
297-
.iter()
298-
.fold_while((false, true), |acc, Instruction { kind, .. }| match kind {
299-
InstructionKind::Break(_) => FoldWhile::Done((true, false)),
300-
InstructionKind::Unreachable
301-
| InstructionKind::Throw
302-
| InstructionKind::Return(_) => FoldWhile::Continue((acc.0, false)),
303-
InstructionKind::Statement | InstructionKind::Continue(_) => {
304-
FoldWhile::Continue(acc)
305-
}
306-
})
307-
.into_inner();
308-
309-
if push {
310-
state.0.push(*id);
311-
}
312-
(state, keep_walking)
313-
},
314-
)
315-
.iter()
316-
.flat_map(|it| it.0.iter())
317-
.next()
318-
.is_some()
319-
}
275+
})
320276
}
321277

322-
#[derive(Debug, Default, Clone)]
323-
struct State(Vec<BasicBlockId>);
324-
325278
fn parent_func<'a>(nodes: &'a AstNodes<'a>, node: &AstNode) -> Option<&'a AstNode<'a>> {
326279
nodes.ancestors(node.id()).map(|id| nodes.get_node(id)).find(|it| it.kind().is_function_like())
327280
}

crates/oxc_semantic/src/builder.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
11181118
// todo - append unreachable after throw statement
11191119

11201120
/* cfg */
1121-
self.cfg.push_throw(node_id);
1121+
self.cfg.append_throw(node_id);
11221122
/* cfg */
11231123

11241124
self.leave_node(kind);

crates/oxc_semantic/src/control_flow/builder/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ impl<'a> ControlFlowGraphBuilder<'a> {
9696
self.push_instruction(InstructionKind::Return(kind), Some(node));
9797
}
9898

99-
pub fn push_throw(&mut self, node: AstNodeId) {
99+
pub fn append_throw(&mut self, node: AstNodeId) {
100100
self.push_instruction(InstructionKind::Throw, Some(node));
101+
self.append_unreachable();
101102
}
102103

103104
pub fn append_break(&mut self, node: AstNodeId, label: Option<&'a str>) {

crates/oxc_semantic/src/control_flow/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,25 @@ impl ControlFlowGraph {
191191
}
192192

193193
pub fn is_reachabale(&self, from: BasicBlockId, to: BasicBlockId) -> bool {
194+
self.is_reachabale_filtered(from, to, |_| Control::Continue)
195+
}
196+
197+
pub fn is_reachabale_filtered<F: Fn(BasicBlockId) -> Control<bool>>(
198+
&self,
199+
from: BasicBlockId,
200+
to: BasicBlockId,
201+
filter: F,
202+
) -> bool {
194203
if from == to {
195204
return true;
196205
}
197206
let graph = &self.graph;
198207
depth_first_search(&self.graph, Some(from), |event| match event {
199208
DfsEvent::TreeEdge(a, b) => {
209+
let filter_result = filter(a);
210+
if !matches!(filter_result, Control::Continue) {
211+
return filter_result;
212+
}
200213
let unreachable = graph.edges_connecting(a, b).all(|edge| {
201214
matches!(edge.weight(), EdgeType::NewFunction | EdgeType::Unreachable)
202215
});

crates/oxc_semantic/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod symbol;
1616
use std::{rc::Rc, sync::Arc};
1717

1818
pub use petgraph;
19+
pub use petgraph::algo;
1920

2021
pub use builder::{SemanticBuilder, SemanticBuilderReturn};
2122
use class::ClassTable;

0 commit comments

Comments
 (0)