Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/oxc_linter/src/rules/eslint/getter_return.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ impl GetterReturn {
// Ignore irrelevant elements.
| InstructionKind::Break(_)
| InstructionKind::Continue(_)
| InstructionKind::Iteration(_)
| InstructionKind::Statement => {}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {}
}
}
Expand Down
78 changes: 43 additions & 35 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -71,6 +71,8 @@ pub struct SemanticBuilder<'a> {
pub cfg: ControlFlowGraphBuilder<'a>,

pub class_table_builder: ClassTableBuilder,

ast_nodes_records: Vec<Vec<AstNodeId>>,
}

pub struct SemanticBuilderReturn<'a> {
Expand Down Expand Up @@ -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(),
}
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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<AstNodeId> {
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)
}
Expand Down Expand Up @@ -741,15 +759,15 @@ 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);

self.cfg
.ctx(None)
.mark_break(after_for_stmt)
.mark_continue(test_graph_ix)
.mark_continue(update_graph_ix)
.resolve_with_upper_label();
/* cfg */

Expand Down Expand Up @@ -782,19 +800,22 @@ 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 */
let before_for_stmt_graph_ix = self.cfg.current_node_ix;
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();
Expand All @@ -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 */

Expand All @@ -847,19 +860,22 @@ 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 */
let before_for_stmt_graph_ix = self.cfg.current_node_ix;
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();
Expand All @@ -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 */

Expand Down Expand Up @@ -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);

Expand Down
7 changes: 6 additions & 1 deletion crates/oxc_semantic/src/control_flow/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -144,6 +145,10 @@ impl<'a> ControlFlowGraphBuilder<'a> {
);
}

pub fn append_iteration(&mut self, node: Option<AstNodeId>, 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();
Expand Down
13 changes: 13 additions & 0 deletions crates/oxc_semantic/src/control_flow/dot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::{
LabeledInstruction, ReturnInstructionKind,
};

use super::IterationInstructionKind;

pub trait DisplayDot {
fn display_dot(&self) -> String;
}
Expand Down Expand Up @@ -78,6 +80,8 @@ impl DisplayDot for Instruction {
InstructionKind::Break(LabeledInstruction::Unlabeled) => "break",
InstructionKind::Continue(LabeledInstruction::Labeled) => "continue <label>",
InstructionKind::Continue(LabeledInstruction::Unlabeled) => "continue",
InstructionKind::Iteration(IterationInstructionKind::Of) => "iteration <of>",
InstructionKind::Iteration(IterationInstructionKind::In) => "iteration <in>",
InstructionKind::Return(ReturnInstructionKind::ImplicitUndefined) => {
"return <implicit undefined>"
}
Expand Down Expand Up @@ -132,6 +136,15 @@ impl DebugDot for Instruction {
}
InstructionKind::Unreachable => "unreachable".to_string(),
InstructionKind::Throw => "throw".to_string(),
InstructionKind::Iteration(ref kind) => {
format!(
"Iteration({} {} {})",
self.node_id.map_or("None".to_string(), |id| ctx.debug_ast_kind(id)),
if matches!(kind, IterationInstructionKind::Of) { "of" } else { "in" },
// TODO: at this point we can't evaluate this note. needs access to the graph information.
"expr"
)
}
InstructionKind::Break(LabeledInstruction::Labeled) => {
let Some(AstKind::BreakStatement(BreakStatement { label: Some(label), .. })) =
self.node_id.map(|id| ctx.0.get_node(id)).map(AstNode::kind)
Expand Down
8 changes: 7 additions & 1 deletion crates/oxc_semantic/src/control_flow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ pub enum InstructionKind {
Break(LabeledInstruction),
Continue(LabeledInstruction),
Throw,
Iteration(IterationInstructionKind),
}

#[derive(Debug, Clone)]
pub enum ReturnInstructionKind {
ImplicitUndefined,
Expand All @@ -162,6 +162,12 @@ pub enum LabeledInstruction {
Unlabeled,
}

#[derive(Debug, Clone)]
pub enum IterationInstructionKind {
Of,
In,
}

#[derive(Debug, Clone)]
pub enum EdgeType {
/// Conditional jumps
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ digraph {
6 -> 2 [ label = "Error(Implicit)" ]
7 -> 2 [ label = "Error(Implicit)" ]
3 -> 4 [ label = "Normal" ]
4 -> 6 [ label = "Normal" ]
4 -> 6 [ label = "Jump" ]
6 -> 5 [ label = "Backedge" ]
5 -> 4 [ label = "Backedge" ]
4 -> 7 [ label = "Normal" ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ digraph {
2 [ label = "" ]
3 [ label = "ForInStatement\nVariableDeclaration" ]
4 [ label = "" ]
5 [ label = "" ]
5 [ label = "Iteration(IdentifierReference(array) in expr)" ]
6 [ label = "BlockStatement\nIfStatement" ]
7 [ label = "BlockStatement\nExpressionStatement\nbreak" ]
8 [ label = "unreachable" ]
Expand Down Expand Up @@ -45,7 +45,7 @@ digraph {
14 -> 2 [ label = "Error(Implicit)" ]
3 -> 4 [ label = "Normal" ]
4 -> 5 [ label = "Normal" ]
5 -> 6 [ label = "Normal" ]
5 -> 6 [ label = "Jump" ]
13 -> 5 [ label = "Backedge" ]
5 -> 14 [ label = "Normal" ]
7 -> 14 [ label = "Jump" ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ bb4: {
}

bb5: {

iteration <in>
}

bb6: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ digraph {
3 -> 9 [ label = "Normal" ]
10 -> 0 [ label = "Error(Implicit)" ]
1 -> 2 [ label = "Normal" ]
2 -> 3 [ label = "Normal" ]
2 -> 3 [ label = "Jump" ]
9 -> 2 [ label = "Backedge" ]
2 -> 10 [ label = "Normal" ]
5 -> 2 [ label = "Jump" ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ digraph {
7 -> 8 [ label = "Unreachable" , style = "dotted" ]
9 -> 2 [ label = "Error(Implicit)" ]
3 -> 4 [ label = "Normal" ]
4 -> 7 [ label = "Normal" ]
4 -> 7 [ label = "Jump" ]
8 -> 4 [ label = "Backedge" ]
4 -> 9 [ label = "Normal" ]
10 -> 2 [ label = "Error(Implicit)" ]
Expand Down