diff --git a/crates/oxc_linter/src/rules/eslint/getter_return.rs b/crates/oxc_linter/src/rules/eslint/getter_return.rs index c6abd401eae6d..10582862c6668 100644 --- a/crates/oxc_linter/src/rules/eslint/getter_return.rs +++ b/crates/oxc_linter/src/rules/eslint/getter_return.rs @@ -275,6 +275,7 @@ impl GetterReturn { // Ignore irrelevant elements. | InstructionKind::Break(_) | InstructionKind::Continue(_) + | InstructionKind::Iteration(_) | InstructionKind::Statement => {} } } diff --git a/crates/oxc_linter/src/rules/react/require_render_return.rs b/crates/oxc_linter/src/rules/react/require_render_return.rs index 8ec9de1d656c6..49d67a1b17b33 100644 --- a/crates/oxc_linter/src/rules/react/require_render_return.rs +++ b/crates/oxc_linter/src/rules/react/require_render_return.rs @@ -126,6 +126,7 @@ fn contains_return_statement<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> b InstructionKind::Return(ReturnInstructionKind::ImplicitUndefined) | InstructionKind::Break(_) | InstructionKind::Continue(_) + | InstructionKind::Iteration(_) | InstructionKind::Statement => {} } } diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index bf73d903ec782..c56a760aec772 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -17,8 +17,8 @@ use crate::{ checker::{EarlyErrorJavaScript, EarlyErrorTypeScript}, class::ClassTableBuilder, control_flow::{ - ControlFlowGraphBuilder, CtxCursor, CtxFlags, EdgeType, ErrorEdgeKind, Register, - ReturnInstructionKind, + ControlFlowGraphBuilder, CtxCursor, CtxFlags, EdgeType, ErrorEdgeKind, + IterationInstructionKind, Register, ReturnInstructionKind, }, diagnostics::redeclaration, jsdoc::JSDocBuilder, @@ -71,6 +71,8 @@ pub struct SemanticBuilder<'a> { pub cfg: ControlFlowGraphBuilder<'a>, pub class_table_builder: ClassTableBuilder, + + ast_nodes_records: Vec>, } pub struct SemanticBuilderReturn<'a> { @@ -105,6 +107,7 @@ impl<'a> SemanticBuilder<'a> { check_syntax_error: false, cfg: ControlFlowGraphBuilder::default(), class_table_builder: ClassTableBuilder::new(), + ast_nodes_records: Vec::new(), } } @@ -207,6 +210,7 @@ impl<'a> SemanticBuilder<'a> { } else { self.nodes.add_node(ast_node, Some(self.current_node_id)) }; + self.record_ast_node(); } fn pop_ast_node(&mut self) { @@ -215,6 +219,20 @@ impl<'a> SemanticBuilder<'a> { } } + fn record_ast_nodes(&mut self) { + self.ast_nodes_records.push(Vec::new()); + } + + fn retrieve_recorded_ast_nodes(&mut self) -> Vec { + self.ast_nodes_records.pop().expect("there is no ast nodes record to stop.") + } + + fn record_ast_node(&mut self) { + if let Some(records) = self.ast_nodes_records.last_mut() { + records.push(self.current_node_id); + } + } + pub fn current_scope_flags(&self) -> ScopeFlags { self.scope.get_flags(self.current_scope_id) } @@ -741,7 +759,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { let after_body_graph_ix = self.cfg.current_node_ix; let after_for_stmt = self.cfg.new_basic_block_normal(); self.cfg.add_edge(before_for_graph_ix, test_graph_ix, EdgeType::Normal); - self.cfg.add_edge(after_test_graph_ix, before_body_graph_ix, EdgeType::Normal); + self.cfg.add_edge(after_test_graph_ix, before_body_graph_ix, EdgeType::Jump); self.cfg.add_edge(after_body_graph_ix, update_graph_ix, EdgeType::Backedge); self.cfg.add_edge(update_graph_ix, test_graph_ix, EdgeType::Backedge); self.cfg.add_edge(after_test_graph_ix, after_for_stmt, EdgeType::Normal); @@ -749,7 +767,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.cfg .ctx(None) .mark_break(after_for_stmt) - .mark_continue(test_graph_ix) + .mark_continue(update_graph_ix) .resolve_with_upper_label(); /* cfg */ @@ -782,6 +800,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { stmt.scope_id.set(Some(self.current_scope_id)); } self.enter_node(kind); + self.visit_for_statement_left(&stmt.left); /* cfg */ @@ -789,12 +808,14 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { let start_prepare_cond_graph_ix = self.cfg.new_basic_block_normal(); /* cfg */ + self.record_ast_nodes(); self.visit_expression(&stmt.right); + let right_node = self.retrieve_recorded_ast_nodes().into_iter().next(); /* cfg */ let end_of_prepare_cond_graph_ix = self.cfg.current_node_ix; - // this basic block is always empty since there's no update condition in a for-in loop. - let basic_block_with_backedge_graph_ix = self.cfg.new_basic_block_normal(); + let iteration_graph_ix = self.cfg.new_basic_block_normal(); + self.cfg.append_iteration(right_node, IterationInstructionKind::In); let body_graph_ix = self.cfg.new_basic_block_normal(); self.cfg.ctx(None).default().allow_break().allow_continue(); @@ -808,28 +829,20 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { // connect before for statement to the iterable expression self.cfg.add_edge(before_for_stmt_graph_ix, start_prepare_cond_graph_ix, EdgeType::Normal); // connect the end of the iterable expression to the basic block with back edge - self.cfg.add_edge( - end_of_prepare_cond_graph_ix, - basic_block_with_backedge_graph_ix, - EdgeType::Normal, - ); + self.cfg.add_edge(end_of_prepare_cond_graph_ix, iteration_graph_ix, EdgeType::Normal); // connect the basic block with back edge to the start of the body - self.cfg.add_edge(basic_block_with_backedge_graph_ix, body_graph_ix, EdgeType::Normal); + self.cfg.add_edge(iteration_graph_ix, body_graph_ix, EdgeType::Jump); // connect the end of the body back to the basic block // with back edge for the next iteration - self.cfg.add_edge( - end_of_body_graph_ix, - basic_block_with_backedge_graph_ix, - EdgeType::Backedge, - ); + self.cfg.add_edge(end_of_body_graph_ix, iteration_graph_ix, EdgeType::Backedge); // connect the basic block with back edge to the basic block after the for loop // for when there are no more iterations left in the iterable - self.cfg.add_edge(basic_block_with_backedge_graph_ix, after_for_graph_ix, EdgeType::Normal); + self.cfg.add_edge(iteration_graph_ix, after_for_graph_ix, EdgeType::Normal); self.cfg .ctx(None) .mark_break(after_for_graph_ix) - .mark_continue(basic_block_with_backedge_graph_ix) + .mark_continue(iteration_graph_ix) .resolve_with_upper_label(); /* cfg */ @@ -847,6 +860,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { stmt.scope_id.set(Some(self.current_scope_id)); } self.enter_node(kind); + self.visit_for_statement_left(&stmt.left); /* cfg */ @@ -854,12 +868,14 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { let start_prepare_cond_graph_ix = self.cfg.new_basic_block_normal(); /* cfg */ + self.record_ast_nodes(); self.visit_expression(&stmt.right); + let right_node = self.retrieve_recorded_ast_nodes().into_iter().next(); /* cfg */ let end_of_prepare_cond_graph_ix = self.cfg.current_node_ix; - // this basic block is always empty since there's no update condition in a for-of loop. - let basic_block_with_backedge_graph_ix = self.cfg.new_basic_block_normal(); + let iteration_graph_ix = self.cfg.new_basic_block_normal(); + self.cfg.append_iteration(right_node, IterationInstructionKind::Of); let body_graph_ix = self.cfg.new_basic_block_normal(); self.cfg.ctx(None).default().allow_break().allow_continue(); @@ -873,28 +889,20 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { // connect before for statement to the iterable expression self.cfg.add_edge(before_for_stmt_graph_ix, start_prepare_cond_graph_ix, EdgeType::Normal); // connect the end of the iterable expression to the basic block with back edge - self.cfg.add_edge( - end_of_prepare_cond_graph_ix, - basic_block_with_backedge_graph_ix, - EdgeType::Normal, - ); + self.cfg.add_edge(end_of_prepare_cond_graph_ix, iteration_graph_ix, EdgeType::Normal); // connect the basic block with back edge to the start of the body - self.cfg.add_edge(basic_block_with_backedge_graph_ix, body_graph_ix, EdgeType::Normal); + self.cfg.add_edge(iteration_graph_ix, body_graph_ix, EdgeType::Jump); // connect the end of the body back to the basic block // with back edge for the next iteration - self.cfg.add_edge( - end_of_body_graph_ix, - basic_block_with_backedge_graph_ix, - EdgeType::Backedge, - ); + self.cfg.add_edge(end_of_body_graph_ix, iteration_graph_ix, EdgeType::Backedge); // connect the basic block with back edge to the basic block after the for loop // for when there are no more iterations left in the iterable - self.cfg.add_edge(basic_block_with_backedge_graph_ix, after_for_graph_ix, EdgeType::Normal); + self.cfg.add_edge(iteration_graph_ix, after_for_graph_ix, EdgeType::Normal); self.cfg .ctx(None) .mark_break(after_for_graph_ix) - .mark_continue(basic_block_with_backedge_graph_ix) + .mark_continue(iteration_graph_ix) .resolve_with_upper_label(); /* cfg */ @@ -1281,7 +1289,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { let after_while_graph_ix = self.cfg.new_basic_block_normal(); self.cfg.add_edge(before_while_stmt_graph_ix, condition_graph_ix, EdgeType::Normal); - self.cfg.add_edge(condition_graph_ix, body_graph_ix, EdgeType::Normal); + self.cfg.add_edge(condition_graph_ix, body_graph_ix, EdgeType::Jump); self.cfg.add_edge(after_body_graph_ix, condition_graph_ix, EdgeType::Backedge); self.cfg.add_edge(condition_graph_ix, after_while_graph_ix, EdgeType::Normal); diff --git a/crates/oxc_semantic/src/control_flow/builder/mod.rs b/crates/oxc_semantic/src/control_flow/builder/mod.rs index a5e088a6df75f..35404c17521e2 100644 --- a/crates/oxc_semantic/src/control_flow/builder/mod.rs +++ b/crates/oxc_semantic/src/control_flow/builder/mod.rs @@ -7,7 +7,8 @@ pub use context::{CtxCursor, CtxFlags}; use super::{ AstNodeId, BasicBlock, BasicBlockId, CompactStr, ControlFlowGraph, EdgeType, ErrorEdgeKind, - Graph, Instruction, InstructionKind, LabeledInstruction, PreservedExpressionState, Register, + Graph, Instruction, InstructionKind, IterationInstructionKind, LabeledInstruction, + PreservedExpressionState, Register, }; #[derive(Debug, Default)] @@ -144,6 +145,10 @@ impl<'a> ControlFlowGraphBuilder<'a> { ); } + pub fn append_iteration(&mut self, node: Option, kind: IterationInstructionKind) { + self.push_instruction(InstructionKind::Iteration(kind), node); + } + pub fn append_throw(&mut self, node: AstNodeId) { self.push_instruction(InstructionKind::Throw, Some(node)); self.append_unreachable(); diff --git a/crates/oxc_semantic/src/control_flow/dot.rs b/crates/oxc_semantic/src/control_flow/dot.rs index f835879bc4d37..f3497380e0f0f 100644 --- a/crates/oxc_semantic/src/control_flow/dot.rs +++ b/crates/oxc_semantic/src/control_flow/dot.rs @@ -10,6 +10,8 @@ use crate::{ LabeledInstruction, ReturnInstructionKind, }; +use super::IterationInstructionKind; + pub trait DisplayDot { fn display_dot(&self) -> String; } @@ -78,6 +80,8 @@ impl DisplayDot for Instruction { InstructionKind::Break(LabeledInstruction::Unlabeled) => "break", InstructionKind::Continue(LabeledInstruction::Labeled) => "continue