diff --git a/crates/oxc_cfg/src/lib.rs b/crates/oxc_cfg/src/lib.rs index eb637358c65b80..7cfbea969be981 100644 --- a/crates/oxc_cfg/src/lib.rs +++ b/crates/oxc_cfg/src/lib.rs @@ -124,6 +124,10 @@ pub struct ControlFlowGraph { } impl ControlFlowGraph { + pub fn graph(&self) -> &Graph { + &self.graph + } + /// # Panics pub fn basic_block(&self, id: BasicBlockId) -> &BasicBlock { let ix = *self.graph.node_weight(id).expect("expected a valid node id in self.graph"); diff --git a/crates/oxc_linter/src/rules/eslint/getter_return.rs b/crates/oxc_linter/src/rules/eslint/getter_return.rs index b02b0ef806d496..914ed286efd1c4 100644 --- a/crates/oxc_linter/src/rules/eslint/getter_return.rs +++ b/crates/oxc_linter/src/rules/eslint/getter_return.rs @@ -8,7 +8,7 @@ use oxc_ast::{ use oxc_diagnostics::OxcDiagnostic; use oxc_cfg::{ - graph::visit::neighbors_filtered_by_edge_weight, EdgeType, InstructionKind, + graph::visit::neighbors_filtered_by_edge_weight, ControlFlowGraph, EdgeType, InstructionKind, ReturnInstructionKind, }; use oxc_macros::declare_oxc_lint; @@ -55,16 +55,19 @@ declare_oxc_lint!( impl Rule for GetterReturn { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + // control flow dependant + let Some(cfg) = ctx.semantic().cfg() else { return }; + // https://eslint.org/docs/latest/rules/getter-return#handled_by_typescript if ctx.source_type().is_typescript() { return; } match node.kind() { AstKind::Function(func) if !func.is_typescript_syntax() => { - self.run_diagnostic(node, ctx, func.span); + self.run_diagnostic(node, ctx, cfg, func.span); } AstKind::ArrowFunctionExpression(expr) => { - self.run_diagnostic(node, ctx, expr.span); + self.run_diagnostic(node, ctx, cfg, expr.span); } _ => {} } @@ -178,15 +181,19 @@ impl GetterReturn { false } - fn run_diagnostic<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>, span: Span) { + fn run_diagnostic<'a>( + &self, + node: &AstNode<'a>, + ctx: &LintContext<'a>, + cfg: &ControlFlowGraph, + span: Span, + ) { if !Self::is_wanted_node(node, ctx) { return; } - let cfg = ctx.semantic().cfg(); - let output = neighbors_filtered_by_edge_weight( - &cfg.graph, + cfg.graph(), node.cfg_id(), &|edge| match edge { EdgeType::Jump | EdgeType::Normal => None, diff --git a/crates/oxc_linter/src/rules/eslint/no_fallthrough.rs b/crates/oxc_linter/src/rules/eslint/no_fallthrough.rs index 153dc4953c70a1..6c844a71ca9437 100644 --- a/crates/oxc_linter/src/rules/eslint/no_fallthrough.rs +++ b/crates/oxc_linter/src/rules/eslint/no_fallthrough.rs @@ -10,7 +10,7 @@ use oxc_cfg::{ visit::{neighbors_filtered_by_edge_weight, EdgeRef}, Direction, }, - BasicBlockId, EdgeType, ErrorEdgeKind, InstructionKind, + BasicBlockId, ControlFlowGraph, EdgeType, ErrorEdgeKind, InstructionKind, }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; @@ -89,13 +89,15 @@ impl Rule for NoFallthrough { } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + // control flow dependant + let Some(cfg) = ctx.semantic().cfg() else { return }; + let AstKind::SwitchStatement(switch) = node.kind() else { return }; let switch_id = node.cfg_id(); - let cfg = ctx.semantic().cfg(); - let graph = &cfg.graph; + let graph = cfg.graph(); - let (cfg_ids, tests, default, exit) = get_switch_semantic_cases(ctx, node, switch); + let (cfg_ids, tests, default, exit) = get_switch_semantic_cases(ctx, cfg, node, switch); let Some(default_or_exit) = default.or(exit) else { // TODO: our `get_switch_semantic_cases` can't evaluate cfg_ids for switch statements @@ -260,6 +262,7 @@ impl NoFallthrough { // Issue: fn get_switch_semantic_cases( ctx: &LintContext, + cfg: &ControlFlowGraph, node: &AstNode, switch: &SwitchStatement, ) -> ( @@ -268,8 +271,7 @@ fn get_switch_semantic_cases( /* default */ Option, /* exit */ Option, ) { - let cfg = &ctx.semantic().cfg(); - let graph = &cfg.graph; + let graph = cfg.graph(); let has_default = switch.cases.iter().any(SwitchCase::is_default_case); let (tests, exit) = graph .edges_directed(node.cfg_id(), Direction::Outgoing) diff --git a/crates/oxc_linter/src/rules/eslint/no_this_before_super.rs b/crates/oxc_linter/src/rules/eslint/no_this_before_super.rs index 8f3b8340bbd837..33e61537e6416b 100644 --- a/crates/oxc_linter/src/rules/eslint/no_this_before_super.rs +++ b/crates/oxc_linter/src/rules/eslint/no_this_before_super.rs @@ -58,7 +58,8 @@ enum DefinitelyCallsThisBeforeSuper { impl Rule for NoThisBeforeSuper { fn run_once(&self, ctx: &LintContext) { let semantic = ctx.semantic(); - let cfg = semantic.cfg(); + // control flow dependant + let Some(cfg) = ctx.semantic().cfg() else { return }; // first pass -> find super calls and local violations let mut wanted_nodes = Vec::new(); diff --git a/crates/oxc_linter/src/rules/eslint/no_unreachable.rs b/crates/oxc_linter/src/rules/eslint/no_unreachable.rs index 0588ce45909954..2c71338ac9c0ee 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unreachable.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unreachable.rs @@ -31,10 +31,12 @@ declare_oxc_lint!( impl Rule for NoUnreachable { fn run_once(&self, ctx: &LintContext) { + // control flow dependant + let Some(cfg) = ctx.semantic().cfg() else { return }; + let nodes = ctx.nodes(); let Some(root) = nodes.root_node() else { return }; - let cfg = ctx.semantic().cfg(); - let graph = &cfg.graph; + let graph = cfg.graph(); // A pre-allocated vector containing the reachability status of all the basic blocks. // We initialize this vector with all nodes set to `unreachable` since if we don't visit a 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 9201325f946b7b..0e851a400e7ef8 100644 --- a/crates/oxc_linter/src/rules/react/require_render_return.rs +++ b/crates/oxc_linter/src/rules/react/require_render_return.rs @@ -2,8 +2,8 @@ use oxc_ast::{ast::Expression, AstKind}; use oxc_diagnostics::OxcDiagnostic; use oxc_cfg::{ - graph::visit::neighbors_filtered_by_edge_weight, EdgeType, Instruction, InstructionKind, - ReturnInstructionKind, + graph::visit::neighbors_filtered_by_edge_weight, ControlFlowGraph, EdgeType, Instruction, + InstructionKind, ReturnInstructionKind, }; use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; @@ -51,6 +51,9 @@ declare_oxc_lint!( impl Rule for RequireRenderReturn { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + // control flow dependant + let Some(cfg) = ctx.semantic().cfg() else { return }; + if !matches!(node.kind(), AstKind::ArrowFunctionExpression(_) | AstKind::Function(_)) { return; } @@ -64,7 +67,7 @@ impl Rule for RequireRenderReturn { return; } - if !contains_return_statement(node, ctx) { + if !contains_return_statement(node, cfg) { match parent.kind() { AstKind::MethodDefinition(method) => { ctx.diagnostic(require_render_return_diagnostic(method.key.span())); @@ -91,10 +94,9 @@ enum FoundReturn { const KEEP_WALKING_ON_THIS_PATH: bool = true; const STOP_WALKING_ON_THIS_PATH: bool = false; -fn contains_return_statement<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool { - let cfg = ctx.semantic().cfg(); +fn contains_return_statement(node: &AstNode, cfg: &ControlFlowGraph) -> bool { let state = neighbors_filtered_by_edge_weight( - &cfg.graph, + cfg.graph(), node.cfg_id(), &|edge| match edge { // We only care about normal edges having a return statement. diff --git a/crates/oxc_linter/src/rules/react/rules_of_hooks.rs b/crates/oxc_linter/src/rules/react/rules_of_hooks.rs index c551178287ee0f..8ed55f68b08db6 100644 --- a/crates/oxc_linter/src/rules/react/rules_of_hooks.rs +++ b/crates/oxc_linter/src/rules/react/rules_of_hooks.rs @@ -4,7 +4,7 @@ use oxc_ast::{ }; use oxc_cfg::{ graph::{algo, visit::Control}, - EdgeType, ErrorEdgeKind, InstructionKind, + ControlFlowGraph, EdgeType, ErrorEdgeKind, InstructionKind, }; use oxc_macros::declare_oxc_lint; use oxc_semantic::{AstNodeId, AstNodes}; @@ -107,6 +107,9 @@ declare_oxc_lint!( impl Rule for RulesOfHooks { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + // control flow dependant + let Some(cfg) = ctx.semantic().cfg() else { return }; + let AstKind::CallExpression(call) = node.kind() else { return }; if !is_react_hook(&call.callee) { @@ -227,7 +230,7 @@ impl Rule for RulesOfHooks { return; } - if !ctx.semantic().cfg().is_reachable(func_cfg_id, node_cfg_id) { + if !cfg.is_reachable(func_cfg_id, node_cfg_id) { // There should always be a control flow path between a parent and child node. // If there is none it means we always do an early exit before reaching our hook call. // In some cases it might mean that we are operating on an invalid `cfg` but in either @@ -236,11 +239,11 @@ impl Rule for RulesOfHooks { } // Is this node cyclic? - if semantic.cfg().is_cyclic(node_cfg_id) { + if cfg.is_cyclic(node_cfg_id) { return ctx.diagnostic(diagnostics::loop_hook(span, hook_name)); } - if has_conditional_path_accept_throw(ctx, parent_func, node) { + if has_conditional_path_accept_throw(cfg, parent_func, node) { #[allow(clippy::needless_return)] return ctx.diagnostic(diagnostics::conditional_hook(span, hook_name)); } @@ -248,14 +251,13 @@ impl Rule for RulesOfHooks { } fn has_conditional_path_accept_throw( - ctx: &LintContext<'_>, + cfg: &ControlFlowGraph, from: &AstNode<'_>, to: &AstNode<'_>, ) -> bool { let from_graph_id = from.cfg_id(); let to_graph_id = to.cfg_id(); - let cfg = ctx.semantic().cfg(); - let graph = &cfg.graph; + let graph = cfg.graph(); if graph .edges(to_graph_id) .any(|it| matches!(it.weight(), EdgeType::Error(ErrorEdgeKind::Explicit))) diff --git a/crates/oxc_semantic/examples/cfg.rs b/crates/oxc_semantic/examples/cfg.rs index 1c1498a43e68fa..5e6ef73d97171f 100644 --- a/crates/oxc_semantic/examples/cfg.rs +++ b/crates/oxc_semantic/examples/cfg.rs @@ -46,6 +46,7 @@ fn main() -> std::io::Result<()> { let semantic = SemanticBuilder::new(&source_text, source_type) .with_check_syntax_error(true) .with_trivias(ret.trivias) + .with_cfg(true) .build(program); if !semantic.errors.is_empty() { @@ -59,16 +60,19 @@ fn main() -> std::io::Result<()> { return Ok(()); } + let cfg = semantic + .semantic + .cfg() + .expect("we set semantic to build the control flow (`with_cfg`) for us so it should always be `Some`"); + let mut ast_nodes_by_block = HashMap::<_, Vec<_>>::new(); for node in semantic.semantic.nodes().iter() { let block = node.cfg_id(); - let block_ix = semantic.semantic.cfg().graph.node_weight(block).unwrap(); + let block_ix = cfg.graph.node_weight(block).unwrap(); ast_nodes_by_block.entry(*block_ix).or_default().push(node); } - let basic_blocks_printed = semantic - .semantic - .cfg() + let basic_blocks_printed = cfg .basic_blocks .iter() .map(DisplayDot::display_dot) @@ -93,13 +97,13 @@ fn main() -> std::io::Result<()> { let cfg_dot_diagram = format!( "{:?}", Dot::with_attr_getters( - &semantic.semantic.cfg().graph, + cfg.graph(), &[Config::EdgeNoLabel, Config::NodeNoLabel], &|_graph, edge| { let weight = edge.weight(); let label = format!("label = \"{weight:?}\""); if matches!(weight, EdgeType::Unreachable) - || semantic.semantic.cfg().basic_block(edge.source()).unreachable + || cfg.basic_block(edge.source()).unreachable { format!("{label}, style = \"dotted\" ") } else { @@ -124,9 +128,7 @@ fn main() -> std::io::Result<()> { node.1, nodes, node.1, - semantic.semantic.cfg().basic_blocks[*node.1] - .debug_dot(semantic.semantic.nodes().into()) - .trim() + cfg.basic_blocks[*node.1].debug_dot(semantic.semantic.nodes().into()).trim() ) } ) diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index f7763ecbcb30cd..e65b3037e9b2f0 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -31,6 +31,16 @@ use crate::{ Semantic, }; +macro_rules! cfg { + (|$self:ident, $cfg:tt| $body:expr) => { + if let Some(ref mut $cfg) = $self.cfg { + $body + } else { + Default::default() + } + }; +} + pub struct SemanticBuilder<'a> { pub source_text: &'a str, @@ -68,7 +78,7 @@ pub struct SemanticBuilder<'a> { check_syntax_error: bool, - pub cfg: ControlFlowGraphBuilder<'a>, + pub cfg: Option>, pub class_table_builder: ClassTableBuilder, @@ -105,7 +115,7 @@ impl<'a> SemanticBuilder<'a> { label_builder: LabelBuilder::default(), jsdoc: JSDocBuilder::new(source_text, trivias), check_syntax_error: false, - cfg: ControlFlowGraphBuilder::default(), + cfg: Some(ControlFlowGraphBuilder::default()), class_table_builder: ClassTableBuilder::new(), ast_nodes_records: Vec::new(), } @@ -124,6 +134,12 @@ impl<'a> SemanticBuilder<'a> { self } + #[must_use] + pub fn with_cfg(mut self, cfg: bool) -> Self { + self.cfg = if cfg { Some(ControlFlowGraphBuilder::default()) } else { None }; + self + } + /// Get the built module record from `build_module_record` pub fn module_record(&self) -> Arc { Arc::clone(&self.module_record) @@ -166,7 +182,7 @@ impl<'a> SemanticBuilder<'a> { module_record: Arc::clone(&self.module_record), jsdoc: self.jsdoc.build(), unused_labels: self.label_builder.unused_node_ids, - cfg: self.cfg.build(), + cfg: self.cfg.map(ControlFlowGraphBuilder::build), }; SemanticBuilderReturn { semantic, errors: self.errors.into_inner() } } @@ -183,7 +199,7 @@ impl<'a> SemanticBuilder<'a> { module_record: Arc::new(ModuleRecord::default()), jsdoc: self.jsdoc.build(), unused_labels: self.label_builder.unused_node_ids, - cfg: self.cfg.build(), + cfg: self.cfg.map(ControlFlowGraphBuilder::build), } } @@ -198,7 +214,8 @@ impl<'a> SemanticBuilder<'a> { flags |= NodeFlags::JSDoc; } - let ast_node = AstNode::new(kind, self.current_scope_id, self.cfg.current_node_ix, flags); + let ast_node = + AstNode::new(kind, self.current_scope_id, cfg!(|self, cfg| cfg.current_node_ix), flags); self.current_node_id = if matches!(kind, AstKind::Program(_)) { let id = self.nodes.add_node(ast_node, None); #[allow(unsafe_code)] @@ -467,8 +484,11 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { program.scope_id.set(Some(self.current_scope_id)); /* cfg */ - let error_harness = self.cfg.attach_error_harness(ErrorEdgeKind::Implicit); - let _program_basic_block = self.cfg.new_basic_block_normal(); + let error_harness = cfg!(|self, cfg| { + let error_harness = cfg.attach_error_harness(ErrorEdgeKind::Implicit); + let _program_basic_block = cfg.new_basic_block_normal(); + error_harness + }); /* cfg - must be above directives as directives are in cfg */ self.enter_node(kind); @@ -480,7 +500,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.visit_statements(&program.body); /* cfg */ - self.cfg.release_error_harness(error_harness); + cfg!(|self, cfg| cfg.release_error_harness(error_harness)); /* cfg */ self.leave_node(kind); @@ -512,7 +532,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { } /* cfg */ - self.cfg.append_break(node_id, stmt.label.as_ref().map(|it| it.name.as_str())); + cfg!(|self, cfg| cfg.append_break(node_id, stmt.label.as_ref().map(|it| it.name.as_str()))); /* cfg */ self.leave_node(kind); @@ -531,7 +551,8 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { } /* cfg */ - self.cfg.append_continue(node_id, stmt.label.as_ref().map(|it| it.name.as_str())); + cfg!(|self, cfg| cfg + .append_continue(node_id, stmt.label.as_ref().map(|it| it.name.as_str()))); /* cfg */ self.leave_node(kind); @@ -548,43 +569,49 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.enter_node(kind); /* cfg */ - let before_do_while_stmt_graph_ix = self.cfg.current_node_ix; - let start_body_graph_ix = self.cfg.new_basic_block_normal(); - - self.cfg.ctx(None).default().allow_break().allow_continue(); + let (before_do_while_stmt_graph_ix, start_body_graph_ix) = cfg!(|self, cfg| { + let before_do_while_stmt_graph_ix = cfg.current_node_ix; + let start_body_graph_ix = cfg.new_basic_block_normal(); + cfg.ctx(None).default().allow_break().allow_continue(); + (before_do_while_stmt_graph_ix, start_body_graph_ix) + }); /* cfg */ self.visit_statement(&stmt.body); /* cfg - condition basic block */ - let after_body_graph_ix = self.cfg.current_node_ix; - let start_of_condition_graph_ix = self.cfg.new_basic_block_normal(); + let (after_body_graph_ix, start_of_condition_graph_ix) = cfg!(|self, cfg| { + let after_body_graph_ix = cfg.current_node_ix; + let start_of_condition_graph_ix = cfg.new_basic_block_normal(); + (after_body_graph_ix, start_of_condition_graph_ix) + }); /* cfg */ self.record_ast_nodes(); self.visit_expression(&stmt.test); let test_node = self.retrieve_recorded_ast_nodes().into_iter().next(); - self.cfg.append_condition_to(start_of_condition_graph_ix, test_node); /* cfg */ - let end_of_condition_graph_ix = self.cfg.current_node_ix; + cfg!(|self, cfg| { + cfg.append_condition_to(start_of_condition_graph_ix, test_node); + let end_of_condition_graph_ix = cfg.current_node_ix; - let end_do_while_graph_ix = self.cfg.new_basic_block_normal(); + let end_do_while_graph_ix = cfg.new_basic_block_normal(); - // before do while to start of body basic block - self.cfg.add_edge(before_do_while_stmt_graph_ix, start_body_graph_ix, EdgeType::Normal); - // body of do-while to start of condition - self.cfg.add_edge(after_body_graph_ix, start_of_condition_graph_ix, EdgeType::Normal); - // end of condition to after do while - self.cfg.add_edge(end_of_condition_graph_ix, end_do_while_graph_ix, EdgeType::Normal); - // end of condition to after start of body - self.cfg.add_edge(end_of_condition_graph_ix, start_body_graph_ix, EdgeType::Backedge); + // before do while to start of body basic block + cfg.add_edge(before_do_while_stmt_graph_ix, start_body_graph_ix, EdgeType::Normal); + // body of do-while to start of condition + cfg.add_edge(after_body_graph_ix, start_of_condition_graph_ix, EdgeType::Normal); + // end of condition to after do while + cfg.add_edge(end_of_condition_graph_ix, end_do_while_graph_ix, EdgeType::Normal); + // end of condition to after start of body + cfg.add_edge(end_of_condition_graph_ix, start_body_graph_ix, EdgeType::Backedge); - self.cfg - .ctx(None) - .mark_break(end_do_while_graph_ix) - .mark_continue(start_of_condition_graph_ix) - .resolve_with_upper_label(); + cfg.ctx(None) + .mark_break(end_do_while_graph_ix) + .mark_continue(start_of_condition_graph_ix) + .resolve_with_upper_label(); + }); /* cfg */ self.leave_node(kind); @@ -611,19 +638,24 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.visit_expression(&expr.left); /* cfg */ - let left_expr_end_ix = self.cfg.current_node_ix; - let right_expr_start_ix = self.cfg.new_basic_block_normal(); + let (left_expr_end_ix, right_expr_start_ix) = cfg!(|self, cfg| { + let left_expr_end_ix = cfg.current_node_ix; + let right_expr_start_ix = cfg.new_basic_block_normal(); + (left_expr_end_ix, right_expr_start_ix) + }); /* cfg */ self.visit_expression(&expr.right); /* cfg */ - let right_expr_end_ix = self.cfg.current_node_ix; - let after_logical_expr_ix = self.cfg.new_basic_block_normal(); + cfg!(|self, cfg| { + let right_expr_end_ix = cfg.current_node_ix; + let after_logical_expr_ix = cfg.new_basic_block_normal(); - self.cfg.add_edge(left_expr_end_ix, right_expr_start_ix, EdgeType::Normal); - self.cfg.add_edge(left_expr_end_ix, after_logical_expr_ix, EdgeType::Normal); - self.cfg.add_edge(right_expr_end_ix, after_logical_expr_ix, EdgeType::Normal); + cfg.add_edge(left_expr_end_ix, right_expr_start_ix, EdgeType::Normal); + cfg.add_edge(left_expr_end_ix, after_logical_expr_ix, EdgeType::Normal); + cfg.add_edge(right_expr_end_ix, after_logical_expr_ix, EdgeType::Normal); + }); /* cfg */ self.leave_node(kind); @@ -641,26 +673,30 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.visit_assignment_target(&expr.left); /* cfg */ - let cfg_ixs = if expr.operator.is_logical() { - let target_end_ix = self.cfg.current_node_ix; - let expr_start_ix = self.cfg.new_basic_block_normal(); - Some((target_end_ix, expr_start_ix)) - } else { - None - }; + let cfg_ixs = cfg!(|self, cfg| { + if expr.operator.is_logical() { + let target_end_ix = cfg.current_node_ix; + let expr_start_ix = cfg.new_basic_block_normal(); + Some((target_end_ix, expr_start_ix)) + } else { + None + } + }); /* cfg */ self.visit_expression(&expr.right); /* cfg */ - if let Some((target_end_ix, expr_start_ix)) = cfg_ixs { - let expr_end_ix = self.cfg.current_node_ix; - let after_assignment_ix = self.cfg.new_basic_block_normal(); + cfg!(|self, cfg| { + if let Some((target_end_ix, expr_start_ix)) = cfg_ixs { + let expr_end_ix = cfg.current_node_ix; + let after_assignment_ix = cfg.new_basic_block_normal(); - self.cfg.add_edge(target_end_ix, expr_start_ix, EdgeType::Normal); - self.cfg.add_edge(target_end_ix, after_assignment_ix, EdgeType::Normal); - self.cfg.add_edge(expr_end_ix, after_assignment_ix, EdgeType::Normal); - } + cfg.add_edge(target_end_ix, expr_start_ix, EdgeType::Normal); + cfg.add_edge(target_end_ix, after_assignment_ix, EdgeType::Normal); + cfg.add_edge(expr_end_ix, after_assignment_ix, EdgeType::Normal); + } + }); /* cfg */ self.leave_node(kind); @@ -671,54 +707,61 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.enter_node(kind); /* cfg - condition basic block */ - let before_conditional_graph_ix = self.cfg.current_node_ix; - let start_of_condition_graph_ix = self.cfg.new_basic_block_normal(); + let (before_conditional_graph_ix, start_of_condition_graph_ix) = cfg!(|self, cfg| { + let before_conditional_graph_ix = cfg.current_node_ix; + let start_of_condition_graph_ix = cfg.new_basic_block_normal(); + (before_conditional_graph_ix, start_of_condition_graph_ix) + }); /* cfg */ self.record_ast_nodes(); self.visit_expression(&expr.test); let test_node = self.retrieve_recorded_ast_nodes().into_iter().next(); - self.cfg.append_condition_to(start_of_condition_graph_ix, test_node); /* cfg */ - let after_condition_graph_ix = self.cfg.current_node_ix; - // conditional expression basic block - let before_consequent_expr_graph_ix = self.cfg.new_basic_block_normal(); + let (after_condition_graph_ix, before_consequent_expr_graph_ix) = cfg!(|self, cfg| { + cfg.append_condition_to(start_of_condition_graph_ix, test_node); + let after_condition_graph_ix = cfg.current_node_ix; + // conditional expression basic block + let before_consequent_expr_graph_ix = cfg.new_basic_block_normal(); + (after_condition_graph_ix, before_consequent_expr_graph_ix) + }); /* cfg */ self.visit_expression(&expr.consequent); /* cfg */ - let after_consequent_expr_graph_ix = self.cfg.current_node_ix; - let start_alternate_graph_ix = self.cfg.new_basic_block_normal(); + let (after_consequent_expr_graph_ix, start_alternate_graph_ix) = cfg!(|self, cfg| { + let after_consequent_expr_graph_ix = cfg.current_node_ix; + let start_alternate_graph_ix = cfg.new_basic_block_normal(); + (after_consequent_expr_graph_ix, start_alternate_graph_ix) + }); /* cfg */ self.visit_expression(&expr.alternate); /* cfg */ - let after_alternate_graph_ix = self.cfg.current_node_ix; - /* bb after conditional expression joins consequent and alternate */ - let after_conditional_graph_ix = self.cfg.new_basic_block_normal(); + cfg!(|self, cfg| { + let after_alternate_graph_ix = cfg.current_node_ix; + /* bb after conditional expression joins consequent and alternate */ + let after_conditional_graph_ix = cfg.new_basic_block_normal(); - self.cfg.add_edge( - before_conditional_graph_ix, - start_of_condition_graph_ix, - EdgeType::Normal, - ); + cfg.add_edge( + before_conditional_graph_ix, + start_of_condition_graph_ix, + EdgeType::Normal, + ); - self.cfg.add_edge( - after_consequent_expr_graph_ix, - after_conditional_graph_ix, - EdgeType::Normal, - ); - self.cfg.add_edge( - after_condition_graph_ix, - before_consequent_expr_graph_ix, - EdgeType::Jump, - ); + cfg.add_edge( + after_consequent_expr_graph_ix, + after_conditional_graph_ix, + EdgeType::Normal, + ); + cfg.add_edge(after_condition_graph_ix, before_consequent_expr_graph_ix, EdgeType::Jump); - self.cfg.add_edge(after_condition_graph_ix, start_alternate_graph_ix, EdgeType::Normal); - self.cfg.add_edge(after_alternate_graph_ix, after_conditional_graph_ix, EdgeType::Normal); + cfg.add_edge(after_condition_graph_ix, start_alternate_graph_ix, EdgeType::Normal); + cfg.add_edge(after_alternate_graph_ix, after_conditional_graph_ix, EdgeType::Normal); + }); /* cfg */ self.leave_node(kind); @@ -737,19 +780,26 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.visit_for_statement_init(init); } /* cfg */ - let before_for_graph_ix = self.cfg.current_node_ix; - let test_graph_ix = self.cfg.new_basic_block_normal(); + let (before_for_graph_ix, test_graph_ix) = cfg!(|self, cfg| { + let before_for_graph_ix = cfg.current_node_ix; + let test_graph_ix = cfg.new_basic_block_normal(); + (before_for_graph_ix, test_graph_ix) + }); /* cfg */ + if let Some(test) = &stmt.test { self.record_ast_nodes(); self.visit_expression(test); let test_node = self.retrieve_recorded_ast_nodes().into_iter().next(); - self.cfg.append_condition_to(test_graph_ix, test_node); + + /* cfg */ + cfg!(|self, cfg| cfg.append_condition_to(test_graph_ix, test_node)); + /* cfg */ } /* cfg */ - let after_test_graph_ix = self.cfg.current_node_ix; - let update_graph_ix = self.cfg.new_basic_block_normal(); + let (after_test_graph_ix, update_graph_ix) = + cfg!(|self, cfg| (cfg.current_node_ix, cfg.new_basic_block_normal())); /* cfg */ if let Some(update) = &stmt.update { @@ -757,27 +807,30 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { } /* cfg */ - let before_body_graph_ix = self.cfg.new_basic_block_normal(); - - self.cfg.ctx(None).default().allow_break().allow_continue(); + let before_body_graph_ix = cfg!(|self, cfg| { + let before_body_graph_ix = cfg.new_basic_block_normal(); + cfg.ctx(None).default().allow_break().allow_continue(); + before_body_graph_ix + }); /* cfg */ self.visit_statement(&stmt.body); /* cfg */ - 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::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); + cfg!(|self, cfg| { + let after_body_graph_ix = cfg.current_node_ix; + let after_for_stmt = cfg.new_basic_block_normal(); + cfg.add_edge(before_for_graph_ix, test_graph_ix, EdgeType::Normal); + cfg.add_edge(after_test_graph_ix, before_body_graph_ix, EdgeType::Jump); + cfg.add_edge(after_body_graph_ix, update_graph_ix, EdgeType::Backedge); + cfg.add_edge(update_graph_ix, test_graph_ix, EdgeType::Backedge); + cfg.add_edge(after_test_graph_ix, after_for_stmt, EdgeType::Normal); - self.cfg - .ctx(None) - .mark_break(after_for_stmt) - .mark_continue(update_graph_ix) - .resolve_with_upper_label(); + cfg.ctx(None) + .mark_break(after_for_stmt) + .mark_continue(update_graph_ix) + .resolve_with_upper_label(); + }); /* cfg */ self.leave_node(kind); @@ -813,8 +866,8 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { 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(); + let (before_for_stmt_graph_ix, start_prepare_cond_graph_ix) = + cfg!(|self, cfg| (cfg.current_node_ix, cfg.new_basic_block_normal(),)); /* cfg */ self.record_ast_nodes(); @@ -822,37 +875,42 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { let right_node = self.retrieve_recorded_ast_nodes().into_iter().next(); /* cfg */ - let end_of_prepare_cond_graph_ix = self.cfg.current_node_ix; - 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(); + let (end_of_prepare_cond_graph_ix, iteration_graph_ix, body_graph_ix) = + cfg!(|self, cfg| { + let end_of_prepare_cond_graph_ix = cfg.current_node_ix; + let iteration_graph_ix = cfg.new_basic_block_normal(); + cfg.append_iteration(right_node, IterationInstructionKind::In); + let body_graph_ix = cfg.new_basic_block_normal(); - self.cfg.ctx(None).default().allow_break().allow_continue(); + cfg.ctx(None).default().allow_break().allow_continue(); + (end_of_prepare_cond_graph_ix, iteration_graph_ix, body_graph_ix) + }); /* cfg */ self.visit_statement(&stmt.body); /* cfg */ - let end_of_body_graph_ix = self.cfg.current_node_ix; - let after_for_graph_ix = self.cfg.new_basic_block_normal(); - // 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, iteration_graph_ix, EdgeType::Normal); - // connect the basic block with back edge to the start of the body - 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, 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(iteration_graph_ix, after_for_graph_ix, EdgeType::Normal); - - self.cfg - .ctx(None) - .mark_break(after_for_graph_ix) - .mark_continue(iteration_graph_ix) - .resolve_with_upper_label(); + cfg!(|self, cfg| { + let end_of_body_graph_ix = cfg.current_node_ix; + let after_for_graph_ix = cfg.new_basic_block_normal(); + // connect before for statement to the iterable expression + 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 + 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 + 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 + 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 + cfg.add_edge(iteration_graph_ix, after_for_graph_ix, EdgeType::Normal); + + cfg.ctx(None) + .mark_break(after_for_graph_ix) + .mark_continue(iteration_graph_ix) + .resolve_with_upper_label(); + }); /* cfg */ self.leave_node(kind); @@ -873,8 +931,8 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { 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(); + let (before_for_stmt_graph_ix, start_prepare_cond_graph_ix) = + cfg!(|self, cfg| (cfg.current_node_ix, cfg.new_basic_block_normal())); /* cfg */ self.record_ast_nodes(); @@ -882,37 +940,41 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { let right_node = self.retrieve_recorded_ast_nodes().into_iter().next(); /* cfg */ - let end_of_prepare_cond_graph_ix = self.cfg.current_node_ix; - 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(); + let (end_of_prepare_cond_graph_ix, iteration_graph_ix, body_graph_ix) = + cfg!(|self, cfg| { + let end_of_prepare_cond_graph_ix = cfg.current_node_ix; + let iteration_graph_ix = cfg.new_basic_block_normal(); + cfg.append_iteration(right_node, IterationInstructionKind::Of); + let body_graph_ix = cfg.new_basic_block_normal(); + cfg.ctx(None).default().allow_break().allow_continue(); + (end_of_prepare_cond_graph_ix, iteration_graph_ix, body_graph_ix) + }); /* cfg */ self.visit_statement(&stmt.body); /* cfg */ - let end_of_body_graph_ix = self.cfg.current_node_ix; - let after_for_graph_ix = self.cfg.new_basic_block_normal(); - // 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, iteration_graph_ix, EdgeType::Normal); - // connect the basic block with back edge to the start of the body - 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, 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(iteration_graph_ix, after_for_graph_ix, EdgeType::Normal); - - self.cfg - .ctx(None) - .mark_break(after_for_graph_ix) - .mark_continue(iteration_graph_ix) - .resolve_with_upper_label(); + cfg!(|self, cfg| { + let end_of_body_graph_ix = cfg.current_node_ix; + let after_for_graph_ix = cfg.new_basic_block_normal(); + // connect before for statement to the iterable expression + 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 + 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 + 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 + 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 + cfg.add_edge(iteration_graph_ix, after_for_graph_ix, EdgeType::Normal); + + cfg.ctx(None) + .mark_break(after_for_graph_ix) + .mark_continue(iteration_graph_ix) + .resolve_with_upper_label(); + }); /* cfg */ self.leave_node(kind); @@ -926,59 +988,62 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.enter_node(kind); /* cfg - condition basic block */ - let before_if_stmt_graph_ix = self.cfg.current_node_ix; - let start_of_condition_graph_ix = self.cfg.new_basic_block_normal(); + let (before_if_stmt_graph_ix, start_of_condition_graph_ix) = + cfg!(|self, cfg| (cfg.current_node_ix, cfg.new_basic_block_normal(),)); /* cfg */ self.record_ast_nodes(); self.visit_expression(&stmt.test); let test_node = self.retrieve_recorded_ast_nodes().into_iter().next(); - self.cfg.append_condition_to(start_of_condition_graph_ix, test_node); /* cfg */ - let after_test_graph_ix = self.cfg.current_node_ix; - let before_consequent_stmt_graph_ix = self.cfg.new_basic_block_normal(); + let (after_test_graph_ix, before_consequent_stmt_graph_ix) = cfg!(|self, cfg| { + cfg.append_condition_to(start_of_condition_graph_ix, test_node); + (cfg.current_node_ix, cfg.new_basic_block_normal()) + }); /* cfg */ self.visit_statement(&stmt.consequent); /* cfg */ - let after_consequent_stmt_graph_ix = self.cfg.current_node_ix; + let after_consequent_stmt_graph_ix = cfg!(|self, cfg| cfg.current_node_ix); /* cfg */ let else_graph_ix = if let Some(alternate) = &stmt.alternate { /* cfg */ - let else_graph_ix = self.cfg.new_basic_block_normal(); + let else_graph_ix = cfg!(|self, cfg| cfg.new_basic_block_normal()); /* cfg */ self.visit_statement(alternate); - Some((else_graph_ix, self.cfg.current_node_ix)) + cfg!(|self, cfg| Some((else_graph_ix, cfg.current_node_ix))) } else { None }; /* cfg - bb after if statement joins consequent and alternate */ - let after_if_graph_ix = self.cfg.new_basic_block_normal(); + cfg!(|self, cfg| { + let after_if_graph_ix = cfg.new_basic_block_normal(); - self.cfg.add_edge(before_if_stmt_graph_ix, start_of_condition_graph_ix, EdgeType::Normal); + cfg.add_edge(before_if_stmt_graph_ix, start_of_condition_graph_ix, EdgeType::Normal); - self.cfg.add_edge(after_consequent_stmt_graph_ix, after_if_graph_ix, EdgeType::Normal); + cfg.add_edge(after_consequent_stmt_graph_ix, after_if_graph_ix, EdgeType::Normal); - self.cfg.add_edge(after_test_graph_ix, before_consequent_stmt_graph_ix, EdgeType::Jump); + cfg.add_edge(after_test_graph_ix, before_consequent_stmt_graph_ix, EdgeType::Jump); - if let Some((start_of_alternate_stmt_graph_ix, after_alternate_stmt_graph_ix)) = - else_graph_ix - { - self.cfg.add_edge( - before_if_stmt_graph_ix, - start_of_alternate_stmt_graph_ix, - EdgeType::Normal, - ); - self.cfg.add_edge(after_alternate_stmt_graph_ix, after_if_graph_ix, EdgeType::Normal); - } else { - self.cfg.add_edge(before_if_stmt_graph_ix, after_if_graph_ix, EdgeType::Normal); - } + if let Some((start_of_alternate_stmt_graph_ix, after_alternate_stmt_graph_ix)) = + else_graph_ix + { + cfg.add_edge( + before_if_stmt_graph_ix, + start_of_alternate_stmt_graph_ix, + EdgeType::Normal, + ); + cfg.add_edge(after_alternate_stmt_graph_ix, after_if_graph_ix, EdgeType::Normal); + } else { + cfg.add_edge(before_if_stmt_graph_ix, after_if_graph_ix, EdgeType::Normal); + } + }); /* cfg */ self.leave_node(kind); @@ -990,10 +1055,12 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { /* cfg */ let label = &stmt.label.name; - let ctx = self.cfg.ctx(Some(label.as_str())).default().allow_break(); - if stmt.body.is_iteration_statement() { - ctx.allow_continue(); - } + cfg!(|self, cfg| { + let ctx = cfg.ctx(Some(label.as_str())).default().allow_break(); + if stmt.body.is_iteration_statement() { + ctx.allow_continue(); + } + }); /* cfg */ self.visit_label_identifier(&stmt.label); @@ -1001,11 +1068,13 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.visit_statement(&stmt.body); /* cfg */ - let after_body_graph_ix = self.cfg.current_node_ix; - let after_labeled_stmt_graph_ix = self.cfg.new_basic_block_normal(); - self.cfg.add_edge(after_body_graph_ix, after_labeled_stmt_graph_ix, EdgeType::Normal); + cfg!(|self, cfg| { + let after_body_graph_ix = cfg.current_node_ix; + let after_labeled_stmt_graph_ix = cfg.new_basic_block_normal(); + cfg.add_edge(after_body_graph_ix, after_labeled_stmt_graph_ix, EdgeType::Normal); - self.cfg.ctx(Some(label.as_str())).mark_break(after_labeled_stmt_graph_ix).resolve(); + cfg.ctx(Some(label.as_str())).mark_break(after_labeled_stmt_graph_ix).resolve(); + }); /* cfg */ self.leave_node(kind); @@ -1027,11 +1096,10 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { }; /* cfg */ - self.cfg.push_return(ret_kind, node_id); - /* cfg */ - - /* cfg - append unreachable after return */ - self.cfg.append_unreachable(); + cfg!(|self, cfg| { + cfg.push_return(ret_kind, node_id); + cfg.append_unreachable(); + }); /* cfg */ self.leave_node(kind); @@ -1045,66 +1113,67 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { stmt.scope_id.set(Some(self.current_scope_id)); /* cfg */ - let discriminant_graph_ix = self.cfg.current_node_ix; - - self.cfg.ctx(None).default().allow_break(); + let discriminant_graph_ix = cfg!(|self, cfg| { + let discriminant_graph_ix = cfg.current_node_ix; + cfg.ctx(None).default().allow_break(); + discriminant_graph_ix + }); let mut switch_case_graph_spans = vec![]; let mut have_default_case = false; /* cfg */ for case in &stmt.cases { - let before_case_graph_ix = self.cfg.new_basic_block_normal(); + let before_case_graph_ix = cfg!(|self, cfg| cfg.new_basic_block_normal()); self.visit_switch_case(case); if case.is_default_case() { have_default_case = true; } - switch_case_graph_spans.push((before_case_graph_ix, self.cfg.current_node_ix)); + cfg!(|self, cfg| switch_case_graph_spans + .push((before_case_graph_ix, cfg.current_node_ix))); } /* cfg */ // for each switch case - for i in 0..switch_case_graph_spans.len() { - let case_graph_span = switch_case_graph_spans[i]; - - // every switch case condition can be skipped, - // so there's a possible jump from it to the next switch case condition - for y in switch_case_graph_spans.iter().skip(i + 1) { - self.cfg.add_edge(case_graph_span.0, y.0, EdgeType::Normal); - } + cfg!(|self, cfg| { + for i in 0..switch_case_graph_spans.len() { + let case_graph_span = switch_case_graph_spans[i]; + + // every switch case condition can be skipped, + // so there's a possible jump from it to the next switch case condition + for y in switch_case_graph_spans.iter().skip(i + 1) { + cfg.add_edge(case_graph_span.0, y.0, EdgeType::Normal); + } - // connect the end of each switch statement to - // the condition of the next switch statement - if switch_case_graph_spans.len() > i + 1 { - let (_, end_of_switch_case) = switch_case_graph_spans[i]; - let (next_switch_statement_condition, _) = switch_case_graph_spans[i + 1]; + // connect the end of each switch statement to + // the condition of the next switch statement + if switch_case_graph_spans.len() > i + 1 { + let (_, end_of_switch_case) = switch_case_graph_spans[i]; + let (next_switch_statement_condition, _) = switch_case_graph_spans[i + 1]; + + cfg.add_edge( + end_of_switch_case, + next_switch_statement_condition, + EdgeType::Normal, + ); + } - self.cfg.add_edge( - end_of_switch_case, - next_switch_statement_condition, - EdgeType::Normal, - ); + cfg.add_edge(discriminant_graph_ix, case_graph_span.0, EdgeType::Normal); } - self.cfg.add_edge(discriminant_graph_ix, case_graph_span.0, EdgeType::Normal); - } - - let end_of_switch_case_statement = self.cfg.new_basic_block_normal(); + let end_of_switch_case_statement = cfg.new_basic_block_normal(); - if let Some(last) = switch_case_graph_spans.last() { - self.cfg.add_edge(last.1, end_of_switch_case_statement, EdgeType::Normal); - } + if let Some(last) = switch_case_graph_spans.last() { + cfg.add_edge(last.1, end_of_switch_case_statement, EdgeType::Normal); + } - // if we don't have a default case there should be an edge from discriminant to the end of - // the statement. - if !have_default_case { - self.cfg.add_edge( - discriminant_graph_ix, - end_of_switch_case_statement, - EdgeType::Normal, - ); - } + // if we don't have a default case there should be an edge from discriminant to the end of + // the statement. + if !have_default_case { + cfg.add_edge(discriminant_graph_ix, end_of_switch_case_statement, EdgeType::Normal); + } - self.cfg.ctx(None).mark_break(end_of_switch_case_statement).resolve(); + cfg.ctx(None).mark_break(end_of_switch_case_statement).resolve(); + }); /* cfg */ self.leave_scope(); @@ -1119,13 +1188,15 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.record_ast_nodes(); self.visit_expression(expr); let test_node = self.retrieve_recorded_ast_nodes().into_iter().next(); - self.cfg.append_condition_to(self.cfg.current_node_ix, test_node); + cfg!(|self, cfg| cfg.append_condition_to(cfg.current_node_ix, test_node)); } /* cfg */ - let after_test_graph_ix = self.cfg.current_node_ix; - let statements_in_switch_graph_ix = self.cfg.new_basic_block_normal(); - self.cfg.add_edge(after_test_graph_ix, statements_in_switch_graph_ix, EdgeType::Jump); + cfg!(|self, cfg| { + let after_test_graph_ix = cfg.current_node_ix; + let statements_in_switch_graph_ix = cfg.new_basic_block_normal(); + cfg.add_edge(after_test_graph_ix, statements_in_switch_graph_ix, EdgeType::Jump); + }); /* cfg */ self.visit_statements(&case.consequent); @@ -1142,10 +1213,9 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { /* cfg */ self.visit_expression(&stmt.argument); - // todo - append unreachable after throw statement /* cfg */ - self.cfg.append_throw(node_id); + cfg!(|self, cfg| cfg.append_throw(node_id)); /* cfg */ self.leave_node(kind); @@ -1156,37 +1226,55 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.enter_node(kind); /* cfg */ - let before_try_statement_graph_ix = self.cfg.current_node_ix; - let error_harness = - stmt.handler.as_ref().map(|_| self.cfg.attach_error_harness(ErrorEdgeKind::Explicit)); - let before_finalizer_graph_ix = - stmt.finalizer.as_ref().map(|_| self.cfg.attach_finalizer()); - let before_try_block_graph_ix = self.cfg.new_basic_block_normal(); + + let ( + before_try_statement_graph_ix, + error_harness, + before_finalizer_graph_ix, + before_try_block_graph_ix, + ) = cfg!(|self, cfg| { + let before_try_statement_graph_ix = cfg.current_node_ix; + let error_harness = + stmt.handler.as_ref().map(|_| cfg.attach_error_harness(ErrorEdgeKind::Explicit)); + let before_finalizer_graph_ix = stmt.finalizer.as_ref().map(|_| cfg.attach_finalizer()); + let before_try_block_graph_ix = cfg.new_basic_block_normal(); + + ( + before_try_statement_graph_ix, + error_harness, + before_finalizer_graph_ix, + before_try_block_graph_ix, + ) + }); /* cfg */ self.visit_block_statement(&stmt.block); /* cfg */ - let after_try_block_graph_ix = self.cfg.current_node_ix; + let after_try_block_graph_ix = cfg!(|self, cfg| cfg.current_node_ix); /* cfg */ let catch_block_end_ix = if let Some(handler) = &stmt.handler { /* cfg */ - let Some(error_harness) = error_harness else { - unreachable!("we always create an error harness if we have a catch block."); - }; - self.cfg.release_error_harness(error_harness); - let catch_block_start_ix = self.cfg.new_basic_block_normal(); - self.cfg.add_edge(error_harness, catch_block_start_ix, EdgeType::Normal); + cfg!(|self, cfg| { + let Some(error_harness) = error_harness else { + unreachable!("we always create an error harness if we have a catch block."); + }; + cfg.release_error_harness(error_harness); + let catch_block_start_ix = cfg.new_basic_block_normal(); + cfg.add_edge(error_harness, catch_block_start_ix, EdgeType::Normal); + }); /* cfg */ self.visit_catch_clause(handler); /* cfg */ - let catch_block_end_ix = self.cfg.current_node_ix; - // TODO: we shouldn't directly change the current node index. - self.cfg.current_node_ix = after_try_block_graph_ix; - Some(catch_block_end_ix) + cfg!(|self, cfg| { + let catch_block_end_ix = cfg.current_node_ix; + // TODO: we shouldn't directly change the current node index. + cfg.current_node_ix = after_try_block_graph_ix; + Some(catch_block_end_ix) + }) /* cfg */ } else { None @@ -1194,67 +1282,73 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { let finally_block_end_ix = if let Some(finalizer) = &stmt.finalizer { /* cfg */ - let Some(before_finalizer_graph_ix) = before_finalizer_graph_ix else { - unreachable!("we always create a finalizer when there is a finally block."); - }; - self.cfg.release_finalizer(before_finalizer_graph_ix); - let start_finally_graph_ix = self.cfg.new_basic_block_normal(); - self.cfg.add_edge(before_finalizer_graph_ix, start_finally_graph_ix, EdgeType::Normal); + cfg!(|self, cfg| { + let Some(before_finalizer_graph_ix) = before_finalizer_graph_ix else { + unreachable!("we always create a finalizer when there is a finally block."); + }; + cfg.release_finalizer(before_finalizer_graph_ix); + let start_finally_graph_ix = cfg.new_basic_block_normal(); + cfg.add_edge(before_finalizer_graph_ix, start_finally_graph_ix, EdgeType::Normal); + }); /* cfg */ self.visit_finally_clause(finalizer); /* cfg */ - let finally_block_end_ix = self.cfg.current_node_ix; - // TODO: we shouldn't directly change the current node index. - self.cfg.current_node_ix = after_try_block_graph_ix; - Some(finally_block_end_ix) + cfg!(|self, cfg| { + let finally_block_end_ix = cfg.current_node_ix; + // TODO: we shouldn't directly change the current node index. + cfg.current_node_ix = after_try_block_graph_ix; + Some(finally_block_end_ix) + }) /* cfg */ } else { None }; /* cfg */ - let after_try_statement_block_ix = self.cfg.new_basic_block_normal(); - self.cfg.add_edge( - before_try_statement_graph_ix, - before_try_block_graph_ix, - EdgeType::Normal, - ); - if let Some(catch_block_end_ix) = catch_block_end_ix { - if finally_block_end_ix.is_none() { - self.cfg.add_edge( - after_try_block_graph_ix, - after_try_statement_block_ix, - EdgeType::Normal, - ); - - self.cfg.add_edge( - catch_block_end_ix, - after_try_statement_block_ix, - EdgeType::Normal, - ); + cfg!(|self, cfg| { + let after_try_statement_block_ix = cfg.new_basic_block_normal(); + cfg.add_edge( + before_try_statement_graph_ix, + before_try_block_graph_ix, + EdgeType::Normal, + ); + if let Some(catch_block_end_ix) = catch_block_end_ix { + if finally_block_end_ix.is_none() { + cfg.add_edge( + after_try_block_graph_ix, + after_try_statement_block_ix, + EdgeType::Normal, + ); + + cfg.add_edge( + catch_block_end_ix, + after_try_statement_block_ix, + EdgeType::Normal, + ); + } } - } - if let Some(finally_block_end_ix) = finally_block_end_ix { - if catch_block_end_ix.is_some() { - self.cfg.add_edge( - finally_block_end_ix, - after_try_statement_block_ix, - EdgeType::Normal, - ); - } else { - self.cfg.add_edge( - finally_block_end_ix, - after_try_statement_block_ix, - if self.cfg.basic_block(after_try_block_graph_ix).unreachable { - EdgeType::Unreachable - } else { - EdgeType::Join - }, - ); + if let Some(finally_block_end_ix) = finally_block_end_ix { + if catch_block_end_ix.is_some() { + cfg.add_edge( + finally_block_end_ix, + after_try_statement_block_ix, + EdgeType::Normal, + ); + } else { + cfg.add_edge( + finally_block_end_ix, + after_try_statement_block_ix, + if cfg.basic_block(after_try_block_graph_ix).unreachable { + EdgeType::Unreachable + } else { + EdgeType::Join + }, + ); + } } - } + }); /* cfg */ self.leave_node(kind); @@ -1288,37 +1382,41 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.enter_node(kind); /* cfg - condition basic block */ - let before_while_stmt_graph_ix = self.cfg.current_node_ix; - let condition_graph_ix = self.cfg.new_basic_block_normal(); + let (before_while_stmt_graph_ix, condition_graph_ix) = + cfg!(|self, cfg| (cfg.current_node_ix, cfg.new_basic_block_normal())); /* cfg */ self.record_ast_nodes(); self.visit_expression(&stmt.test); let test_node = self.retrieve_recorded_ast_nodes().into_iter().next(); - self.cfg.append_condition_to(condition_graph_ix, test_node); /* cfg - body basic block */ - let body_graph_ix = self.cfg.new_basic_block_normal(); + let body_graph_ix = cfg!(|self, cfg| { + cfg.append_condition_to(condition_graph_ix, test_node); + let body_graph_ix = cfg.new_basic_block_normal(); - self.cfg.ctx(None).default().allow_break().allow_continue(); + cfg.ctx(None).default().allow_break().allow_continue(); + body_graph_ix + }); /* cfg */ self.visit_statement(&stmt.body); /* cfg - after body basic block */ - let after_body_graph_ix = self.cfg.current_node_ix; - 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::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); - - self.cfg - .ctx(None) - .mark_break(after_while_graph_ix) - .mark_continue(condition_graph_ix) - .resolve_with_upper_label(); + cfg!(|self, cfg| { + let after_body_graph_ix = cfg.current_node_ix; + let after_while_graph_ix = cfg.new_basic_block_normal(); + + cfg.add_edge(before_while_stmt_graph_ix, condition_graph_ix, EdgeType::Normal); + cfg.add_edge(condition_graph_ix, body_graph_ix, EdgeType::Jump); + cfg.add_edge(after_body_graph_ix, condition_graph_ix, EdgeType::Backedge); + cfg.add_edge(condition_graph_ix, after_while_graph_ix, EdgeType::Normal); + + cfg.ctx(None) + .mark_break(after_while_graph_ix) + .mark_continue(condition_graph_ix) + .resolve_with_upper_label(); + }); /* cfg */ self.leave_node(kind); } @@ -1328,26 +1426,27 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.enter_node(kind); /* cfg - condition basic block */ - let before_with_stmt_graph_ix = self.cfg.current_node_ix; - - let condition_graph_ix = self.cfg.new_basic_block_normal(); + let (before_with_stmt_graph_ix, condition_graph_ix) = + cfg!(|self, cfg| (cfg.current_node_ix, cfg.new_basic_block_normal())); /* cfg */ self.visit_expression(&stmt.object); /* cfg - body basic block */ - let body_graph_ix = self.cfg.new_basic_block_normal(); + let body_graph_ix = cfg!(|self, cfg| cfg.new_basic_block_normal()); /* cfg */ self.visit_statement(&stmt.body); /* cfg - after body basic block */ - let after_body_graph_ix = self.cfg.new_basic_block_normal(); + cfg!(|self, cfg| { + let after_body_graph_ix = cfg.new_basic_block_normal(); - self.cfg.add_edge(before_with_stmt_graph_ix, condition_graph_ix, EdgeType::Normal); - self.cfg.add_edge(condition_graph_ix, body_graph_ix, EdgeType::Normal); - self.cfg.add_edge(body_graph_ix, after_body_graph_ix, EdgeType::Normal); - self.cfg.add_edge(condition_graph_ix, after_body_graph_ix, EdgeType::Normal); + cfg.add_edge(before_with_stmt_graph_ix, condition_graph_ix, EdgeType::Normal); + cfg.add_edge(condition_graph_ix, body_graph_ix, EdgeType::Normal); + cfg.add_edge(body_graph_ix, after_body_graph_ix, EdgeType::Normal); + cfg.add_edge(condition_graph_ix, after_body_graph_ix, EdgeType::Normal); + }); /* cfg */ self.leave_node(kind); @@ -1365,11 +1464,14 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { func.scope_id.set(Some(self.current_scope_id)); /* cfg */ - let before_function_graph_ix = self.cfg.current_node_ix; - self.cfg.push_finalization_stack(); - let error_harness = self.cfg.attach_error_harness(ErrorEdgeKind::Implicit); - let function_graph_ix = self.cfg.new_basic_block_function(); - self.cfg.ctx(None).new_function(); + let (before_function_graph_ix, error_harness, function_graph_ix) = cfg!(|self, cfg| { + let before_function_graph_ix = cfg.current_node_ix; + cfg.push_finalization_stack(); + let error_harness = cfg.attach_error_harness(ErrorEdgeKind::Implicit); + let function_graph_ix = cfg.new_basic_block_function(); + cfg.ctx(None).new_function(); + (before_function_graph_ix, error_harness, function_graph_ix) + }); /* cfg */ // We add a new basic block to the cfg before entering the node @@ -1377,7 +1479,11 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.enter_node(kind); /* cfg */ - self.cfg.add_edge(before_function_graph_ix, function_graph_ix, EdgeType::NewFunction); + cfg!(|self, cfg| cfg.add_edge( + before_function_graph_ix, + function_graph_ix, + EdgeType::NewFunction + )); /* cfg */ if let Some(ident) = &func.id { @@ -1389,11 +1495,13 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { } /* cfg */ - self.cfg.ctx(None).resolve_expect(CtxFlags::FUNCTION); - self.cfg.release_error_harness(error_harness); - self.cfg.pop_finalization_stack(); - let after_function_graph_ix = self.cfg.new_basic_block_normal(); - self.cfg.add_edge(before_function_graph_ix, after_function_graph_ix, EdgeType::Normal); + cfg!(|self, cfg| { + cfg.ctx(None).resolve_expect(CtxFlags::FUNCTION); + cfg.release_error_harness(error_harness); + cfg.pop_finalization_stack(); + let after_function_graph_ix = cfg.new_basic_block_normal(); + cfg.add_edge(before_function_graph_ix, after_function_graph_ix, EdgeType::Normal); + }); /* cfg */ if let Some(parameters) = &func.type_parameters { @@ -1463,11 +1571,14 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { expr.scope_id.set(Some(self.current_scope_id)); /* cfg */ - let current_node_ix = self.cfg.current_node_ix; - self.cfg.push_finalization_stack(); - let error_harness = self.cfg.attach_error_harness(ErrorEdgeKind::Implicit); - let function_graph_ix = self.cfg.new_basic_block_function(); - self.cfg.ctx(None).new_function(); + let (current_node_ix, error_harness, function_graph_ix) = cfg!(|self, cfg| { + let current_node_ix = cfg.current_node_ix; + cfg.push_finalization_stack(); + let error_harness = cfg.attach_error_harness(ErrorEdgeKind::Implicit); + let function_graph_ix = cfg.new_basic_block_function(); + cfg.ctx(None).new_function(); + (current_node_ix, error_harness, function_graph_ix) + }); /* cfg */ // We add a new basic block to the cfg before entering the node @@ -1477,17 +1588,20 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.visit_formal_parameters(&expr.params); /* cfg */ - self.cfg.add_edge(current_node_ix, function_graph_ix, EdgeType::NewFunction); + cfg!(|self, cfg| cfg.add_edge(current_node_ix, function_graph_ix, EdgeType::NewFunction)); /* cfg */ self.visit_function_body(&expr.body); /* cfg */ - self.cfg.ctx(None).resolve_expect(CtxFlags::FUNCTION); - self.cfg.release_error_harness(error_harness); - self.cfg.pop_finalization_stack(); - self.cfg.current_node_ix = current_node_ix; + cfg!(|self, cfg| { + cfg.ctx(None).resolve_expect(CtxFlags::FUNCTION); + cfg.release_error_harness(error_harness); + cfg.pop_finalization_stack(); + cfg.current_node_ix = current_node_ix; + }); /* cfg */ + if let Some(parameters) = &expr.type_parameters { self.visit_ts_type_parameter_declaration(parameters); } @@ -1550,16 +1664,19 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { impl<'a> SemanticBuilder<'a> { fn enter_kind(&mut self, kind: AstKind<'a>) { /* cfg */ - match kind { - AstKind::ReturnStatement(_) - | AstKind::BreakStatement(_) - | AstKind::ContinueStatement(_) - | AstKind::ThrowStatement(_) => { /* These types have their own `InstructionKind`. */ } - it if it.is_statement() => { - self.cfg.enter_statement(self.current_node_id); - } - _ => { /* ignore the rest */ } - } + cfg!(|self, cfg| { + match kind { + AstKind::ReturnStatement(_) + | AstKind::BreakStatement(_) + | AstKind::ContinueStatement(_) + | AstKind::ThrowStatement(_) => { /* These types have their own `InstructionKind`. */ + } + it if it.is_statement() => { + cfg.enter_statement(self.current_node_id); + } + _ => { /* ignore the rest */ } + } + }); /* cfg */ match kind { diff --git a/crates/oxc_semantic/src/lib.rs b/crates/oxc_semantic/src/lib.rs index b6e26c66160212..fcdd203fc68bce 100644 --- a/crates/oxc_semantic/src/lib.rs +++ b/crates/oxc_semantic/src/lib.rs @@ -56,7 +56,7 @@ pub struct Semantic<'a> { unused_labels: FxHashSet, - cfg: ControlFlowGraph, + cfg: Option, } impl<'a> Semantic<'a> { @@ -108,8 +108,8 @@ impl<'a> Semantic<'a> { &self.unused_labels } - pub fn cfg(&self) -> &ControlFlowGraph { - &self.cfg + pub fn cfg(&self) -> Option<&ControlFlowGraph> { + self.cfg.as_ref() } pub fn is_unresolved_reference(&self, node_id: AstNodeId) -> bool { diff --git a/crates/oxc_semantic/tests/integration/util/mod.rs b/crates/oxc_semantic/tests/integration/util/mod.rs index a8a707d2d781de..bd21d01a98d57b 100644 --- a/crates/oxc_semantic/tests/integration/util/mod.rs +++ b/crates/oxc_semantic/tests/integration/util/mod.rs @@ -102,29 +102,29 @@ impl<'a> SemanticTester<'a> { pub fn basic_blocks_count(&self) -> usize { let built = self.build(); - built.cfg().basic_blocks.len() + built.cfg().map_or(0, |cfg| cfg.basic_blocks.len()) } pub fn basic_blocks_printed(&self) -> String { let built = self.build(); - built - .cfg() - .basic_blocks - .iter() - .map(DisplayDot::display_dot) - .enumerate() - .map(|(i, it)| { - format!( - "bb{i}: {{\n{}\n}}", - it.lines().map(|x| format!("\t{}", x.trim())).join("\n") - ) - }) - .join("\n\n") + built.cfg().map_or_else(String::default, |cfg| { + cfg.basic_blocks + .iter() + .map(DisplayDot::display_dot) + .enumerate() + .map(|(i, it)| { + format!( + "bb{i}: {{\n{}\n}}", + it.lines().map(|x| format!("\t{}", x.trim())).join("\n") + ) + }) + .join("\n\n") + }) } pub fn cfg_dot_diagram(&self) -> String { let semantic = self.build(); - semantic.cfg().debug_dot(semantic.nodes().into()) + semantic.cfg().map_or_else(String::default, |cfg| cfg.debug_dot(semantic.nodes().into())) } /// Tests that a symbol with the given name exists at the top-level scope and provides a