feat(semantic): track cfg index per ast node (#2210)

This allows looking up a cfg index from an ast node in a semantics
return. This allows later passes to better make use of the cfg.
This commit is contained in:
Tzvi Melamed 2024-02-01 00:27:20 -05:00 committed by GitHub
parent 6b2150f3b3
commit e561457683
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 318 additions and 352 deletions

View file

@ -154,7 +154,7 @@ impl GetterReturn {
let output = neighbors_filtered_by_edge_weight(
&cfg.graph,
cfg.function_to_node_ix[&node.id()],
node.cfg_ix(),
&|edge| match edge {
EdgeType::Normal => None,
// We don't need to handle backedges because we would have already visited
@ -178,7 +178,7 @@ impl GetterReturn {
// previous such as [`no_this_before_super`] we would want to observe this value.
&mut |basic_block_id, _state_going_into_this_rule| {
// Scan through the values in this basic block.
for entry in &cfg.basic_blocks[*basic_block_id] {
for entry in cfg.basic_block_by_index(*basic_block_id) {
match entry {
// If the element is an assignment.
//

View file

@ -21,6 +21,8 @@ fn main() {
let ret = Parser::new(&allocator, &source_text, source_type).parse();
let program = allocator.alloc(ret.program);
println!("{:#?}", &program);
let semantic = SemanticBuilder::new(&source_text, source_type)
.with_check_syntax_error(true)
.with_trivias(ret.trivias)

View file

@ -209,7 +209,7 @@ impl<'a> SemanticBuilder<'a> {
if self.jsdoc.retrieve_jsdoc_comment(kind) {
flags |= NodeFlags::JSDoc;
}
let ast_node = AstNode::new(kind, self.current_scope_id, flags);
let ast_node = AstNode::new(kind, self.current_scope_id, self.cfg.current_node_ix, flags);
let parent_node_id =
if matches!(kind, AstKind::Program(_)) { None } else { Some(self.current_node_id) };
self.current_node_id = self.nodes.add_node(ast_node, parent_node_id);
@ -1012,6 +1012,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
/* cfg */
/* cfg - put unreachable after return */
let _ = self.cfg.new_basic_block();
self.cfg.put_unreachable();
self.cfg.after_statement(
@ -1164,7 +1165,6 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
let before_try_stmt_graph_ix = self.cfg.current_node_ix;
let catch_block_graph_ix = self.cfg.new_basic_block();
let catch_block_basic_block_id = self.cfg.current_basic_block;
// every statement created with this active adds an edge from that node to this
// catch block node
//
@ -1186,7 +1186,6 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
let optional_catch_block_graph_ix = if let Some(handler) = &stmt.handler {
/* cfg */
self.cfg.current_node_ix = catch_block_graph_ix;
self.cfg.current_basic_block = catch_block_basic_block_id;
/* cfg */
self.visit_catch_clause(handler);
@ -1417,14 +1416,19 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
}
flags
});
self.enter_node(kind);
/* cfg */
let preserved = self.cfg.preserve_expression_state();
let before_function_graph_ix = self.cfg.current_node_ix;
let function_graph_ix = self.cfg.new_basic_block_for_function();
self.cfg.function_to_node_ix.insert(self.current_node_id, function_graph_ix);
/* cfg */
// We add a new basic block to the cfg before entering the node
// so that the correct cfg_ix is associated with the ast node.
self.enter_node(kind);
/* cfg */
self.cfg.add_edge(before_function_graph_ix, function_graph_ix, EdgeType::NewFunction);
/* cfg */
@ -1510,17 +1514,20 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
fn visit_arrow_expression(&mut self, expr: &ArrowExpression<'a>) {
let kind = AstKind::ArrowExpression(self.alloc(expr));
self.enter_scope(ScopeFlags::Function | ScopeFlags::Arrow);
/* cfg */
let preserved = self.cfg.preserve_expression_state();
let current_node_ix = self.cfg.current_node_ix;
let function_graph_ix = self.cfg.new_basic_block_for_function();
/* cfg */
// We add a new basic block to the cfg before entering the node
// so that the correct cfg_ix is associated with the ast node.
self.enter_node(kind);
self.visit_formal_parameters(&expr.params);
/* cfg */
let preserved = self.cfg.preserve_expression_state();
let current_basic_block_ix = self.cfg.current_basic_block;
let current_node_ix = self.cfg.current_node_ix;
let function_graph_ix = self.cfg.new_basic_block_for_function();
self.cfg.function_to_node_ix.insert(self.current_node_id, function_graph_ix);
self.cfg.add_edge(current_node_ix, function_graph_ix, EdgeType::NewFunction);
if expr.expression {
self.cfg.store_assignments_into_this_array.push(vec![]);
@ -1531,7 +1538,6 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
/* cfg */
self.cfg.restore_expression_state(preserved);
self.cfg.current_basic_block = current_basic_block_ix;
self.cfg.current_node_ix = current_node_ix;
// self.cfg.put_x_in_register(AssignmentValue::Function(self.current_node_id));
/* cfg */

View file

@ -1,5 +1,3 @@
use std::collections::HashMap;
use oxc_span::Atom;
use oxc_syntax::operator::{
AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator, UpdateOperator,
@ -117,13 +115,13 @@ pub enum EdgeType {
NewFunction,
}
#[derive(Debug, Default)]
pub struct ControlFlowGraph {
pub basic_blocks: Vec<Vec<BasicBlockElement>>,
// note: this should only land in the big box for all things that take arguments
// ie: callexpression, arrayexpression, etc
// todo: add assert that it is used every time?
pub use_this_register: Option<Register>,
pub current_basic_block: usize,
pub next_free_register: u32,
pub store_assignments_into_this_array: Vec<Vec<Register>>,
pub store_final_assignments_into_this_array: Vec<Vec<Register>>,
@ -144,41 +142,31 @@ pub struct ControlFlowGraph {
pub next_label: Option<Atom>,
pub label_to_ast_node_ix: Vec<(Atom, AstNodeId)>,
pub ast_node_to_break_continue: Vec<(AstNodeId, usize, Option<usize>)>,
pub function_to_node_ix: HashMap<AstNodeId, NodeIndex>,
pub after_throw_block: Option<NodeIndex>,
}
impl Default for ControlFlowGraph {
fn default() -> Self {
Self::new()
}
}
impl ControlFlowGraph {
pub fn new() -> Self {
Self {
basic_blocks: Vec::new(),
use_this_register: None,
current_basic_block: 0,
next_free_register: 0,
store_assignments_into_this_array: Vec::new(),
store_final_assignments_into_this_array: Vec::new(),
spread_indices: Vec::new(),
should_save_stores_for_patching: false,
saved_stores: Vec::new(),
saved_store: None,
graph: Graph::default(),
// todo: invalid state
current_node_ix: NodeIndex::default(),
basic_blocks_with_breaks: Vec::new(),
basic_blocks_with_continues: Vec::new(),
switch_case_conditions: Vec::new(),
next_label: None,
label_to_ast_node_ix: Vec::new(),
ast_node_to_break_continue: Vec::new(),
function_to_node_ix: HashMap::new(),
after_throw_block: None,
}
Self::default()
}
/// # Panics
pub fn basic_block_by_index(&self, index: NodeIndex) -> &Vec<BasicBlockElement> {
let idx =
*self.graph.node_weight(index).expect("expected a valid node index in self.graph");
self.basic_blocks.get(idx).expect("expected a valid node index in self.basic_blocks")
}
/// # Panics
pub fn basic_block_by_index_mut(&mut self, index: NodeIndex) -> &mut Vec<BasicBlockElement> {
let idx =
*self.graph.node_weight(index).expect("expected a valid node index in self.graph");
self.basic_blocks.get_mut(idx).expect("expected a valid node index in self.basic_blocks")
}
/// # Panics
pub fn current_basic_block(&mut self) -> &mut Vec<BasicBlockElement> {
self.basic_block_by_index_mut(self.current_node_ix)
}
#[must_use]
@ -187,7 +175,6 @@ impl ControlFlowGraph {
let basic_block_id = self.basic_blocks.len() - 1;
let graph_index = self.graph.add_node(basic_block_id);
self.current_node_ix = graph_index;
self.current_basic_block = basic_block_id;
// todo: get smarter about what can throw, ie: return can't throw but it's expression can
if let Some(after_throw_block) = self.after_throw_block {
@ -200,10 +187,8 @@ impl ControlFlowGraph {
#[must_use]
pub fn new_basic_block(&mut self) -> NodeIndex {
self.basic_blocks.push(Vec::new());
let basic_block_id = self.basic_blocks.len() - 1;
let graph_index = self.graph.add_node(basic_block_id);
let graph_index = self.graph.add_node(self.basic_blocks.len() - 1);
self.current_node_ix = graph_index;
self.current_basic_block = basic_block_id;
// todo: get smarter about what can throw, ie: return can't throw but it's expression can
if let Some(after_throw_block) = self.after_throw_block {
@ -224,6 +209,7 @@ impl ControlFlowGraph {
register
}
/// # Panics
pub fn put_x_in_register(&mut self, asmt_value: AssignmentValue) {
let register = self.use_this_register.take().unwrap_or_else(|| self.new_register());
@ -237,7 +223,7 @@ impl ControlFlowGraph {
saved_store.0.push(basic_block_element);
saved_store.1 = Some(register);
} else {
self.basic_blocks[self.current_basic_block].push(basic_block_element);
self.current_basic_block().push(basic_block_element);
}
// used for storing the object base for MemberExpressions
if let Some(arr) = self.store_assignments_into_this_array.last_mut() {
@ -249,166 +235,21 @@ impl ControlFlowGraph {
}
}
// pub fn put_callee_with_arguments_in_register(
// &mut self,
// expression_ast_node_id: AstNodeId,
// callee_node_index: Register,
// call_type: CallType,
// arguments: Vec<Register>,
// ) {
// let call_expression = AssignmentValue::CalleeWithArguments(
// CalleeWithArgumentsAssignmentValue {
// id: expression_ast_node_id,
// callee: callee_node_index,
// arguments,
// spreads: self
// .spread_indices
// .pop()
// .expect("expected there to be atleast one vec in the spread_indices"),
// call_type,
// }
// .into(),
// );
// self.put_x_in_register(call_expression);
// }
// pub fn put_collection_in_register(
// &mut self,
// collection_ast_node_id: AstNodeId,
// collection_type: CollectionType,
// elements: Vec<Register>,
// ) {
// let array = AssignmentValue::Collection(
// CollectionAssignmentValue {
// id: collection_ast_node_id,
// elements,
// spreads: self
// .spread_indices
// .pop()
// .expect("expected there to be a corresponding spread_indices for an array"),
// collection_type,
// }
// .into(),
// );
// self.put_x_in_register(array);
// }
// pub fn put_constant_in_register(&mut self, constant_ast_node_id: AstNodeId) {
// self.put_x_in_register(AssignmentValue::Constant(constant_ast_node_id));
// }
// pub fn put_reference_in_register(&mut self, constant_ast_node_id: AstNodeId) {
// self.put_x_in_register(AssignmentValue::Reference(constant_ast_node_id));
// }
pub fn put_throw(&mut self, throw_expr: Register) {
self.basic_blocks[self.current_basic_block].push(BasicBlockElement::Throw(throw_expr));
self.current_basic_block().push(BasicBlockElement::Throw(throw_expr));
}
pub fn put_unreachable(&mut self) {
let current_node_ix = self.current_node_ix;
let basic_block_with_unreachable_graph_ix = self.new_basic_block();
self.add_edge(current_node_ix, basic_block_with_unreachable_graph_ix, EdgeType::Normal);
self.basic_blocks[self.current_basic_block].push(BasicBlockElement::Unreachable);
self.current_basic_block().push(BasicBlockElement::Unreachable);
}
pub fn put_undefined(&mut self) {
self.put_x_in_register(AssignmentValue::ImplicitUndefined);
}
// pub fn visit_computed_member_expression(
// &mut self,
// expr: &ComputedMemberExpression,
// ast_node_id: AstNodeId,
// ) {
// let basic_block = &mut self.basic_blocks[self.current_basic_block];
// let removed = self.saved_stores.pop();
// if let Some((saved_nodes, Some(last_register))) = removed {
// if self.should_save_stores_for_patching {
// let saved_store = self.saved_stores.last_mut().expect(
// "expected a saved_store if self.should_save_stores_for_patching is true",
// );
// saved_store.0.extend(saved_nodes);
// saved_store.1 = Some(last_register);
// } else {
// basic_block.extend(saved_nodes);
// }
// let accessed_on = {
// let asmts = self
// .store_assignments_into_this_array
// .pop()
// .expect("expected store_assignments_into_this_array to have an element");
// debug_assert!(!asmts.is_empty(), "there should be at least the object base here");
// asmts[asmts.len() - 1]
// };
// self.put_x_in_register(AssignmentValue::ObjectPropertyAccess(
// ObjectPropertyAccessAssignmentValue {
// id: ast_node_id,
// access_by: ObjectPropertyAccessBy::Expression(last_register),
// optional: expr.optional,
// access_on: accessed_on,
// }
// .into(),
// ));
// } else {
// panic!("No saved_node_indices in self.saved_stores")
// }
// }
// pub fn visit_static_member_expression(
// &mut self,
// expr: &StaticMemberExpression,
// ast_node_id: AstNodeId,
// ) {
// let accessed_on = {
// let asmts = self
// .store_assignments_into_this_array
// .pop()
// .expect("expected store_assignments_into_this_array to have an element");
// debug_assert!(!asmts.is_empty(), "there should be at least the object base here");
// asmts[asmts.len() - 1]
// };
// self.put_x_in_register(AssignmentValue::ObjectPropertyAccess(
// ObjectPropertyAccessAssignmentValue {
// id: ast_node_id,
// access_by: ObjectPropertyAccessBy::Property(expr.property.name.clone()), // todo: should we be cloning here?
// optional: expr.optional,
// access_on: accessed_on,
// }
// .into(),
// ));
// }
// pub fn visit_private_field_expression(
// &mut self,
// expr: &PrivateFieldExpression,
// ast_node_id: AstNodeId,
// ) {
// let accessed_on = {
// let asmts = self
// .store_assignments_into_this_array
// .pop()
// .expect("expected store_assignments_into_this_array to have an element");
// debug_assert!(!asmts.is_empty(), "there should be at least the object base here");
// asmts[asmts.len() - 1]
// };
// self.put_x_in_register(AssignmentValue::ObjectPropertyAccess(
// ObjectPropertyAccessAssignmentValue {
// id: ast_node_id,
// access_by: ObjectPropertyAccessBy::PrivateProperty(expr.field.name.clone()), // todo: should we be cloning here?
// optional: expr.optional,
// access_on: accessed_on,
// }
// .into(),
// ));
// }
#[must_use]
pub fn preserve_expression_state(&mut self) -> PreservedExpressionState {
let use_this_register = self.use_this_register.take();
@ -471,6 +312,7 @@ impl ControlFlowGraph {
pss
}
/// # Panics
pub fn after_statement(
&mut self,
preserved_state: &PreservedStatementState,

View file

@ -19,7 +19,6 @@ pub use petgraph;
pub use builder::{SemanticBuilder, SemanticBuilderReturn};
use class::ClassTable;
use control_flow::ControlFlowGraph;
pub use jsdoc::{JSDoc, JSDocComment, JSDocTag};
use oxc_ast::{ast::IdentifierReference, AstKind, TriviasMap};
use oxc_span::SourceType;
@ -34,8 +33,8 @@ pub use crate::{
builder::VariableInfo,
control_flow::{
print_basic_block, AssignmentValue, BasicBlockElement, BinaryAssignmentValue, BinaryOp,
CallType, CalleeWithArgumentsAssignmentValue, CollectionAssignmentValue, EdgeType,
ObjectPropertyAccessAssignmentValue, Register, UnaryExpressioneAssignmentValue,
CallType, CalleeWithArgumentsAssignmentValue, CollectionAssignmentValue, ControlFlowGraph,
EdgeType, ObjectPropertyAccessAssignmentValue, Register, UnaryExpressioneAssignmentValue,
UpdateAssignmentValue,
},
node::{AstNode, AstNodeId, AstNodes},

View file

@ -1,3 +1,5 @@
use petgraph::stable_graph::NodeIndex;
use oxc_ast::AstKind;
use oxc_index::IndexVec;
@ -15,18 +17,25 @@ pub struct AstNode<'a> {
/// Associated Scope (initialized by binding)
scope_id: ScopeId,
/// Associated NodeIndex in CFG (initialized by control_flow)
cfg_ix: NodeIndex,
flags: NodeFlags,
}
impl<'a> AstNode<'a> {
pub fn new(kind: AstKind<'a>, scope_id: ScopeId, flags: NodeFlags) -> Self {
Self { id: AstNodeId::new(0), kind, scope_id, flags }
pub fn new(kind: AstKind<'a>, scope_id: ScopeId, cfg_ix: NodeIndex, flags: NodeFlags) -> Self {
Self { id: AstNodeId::new(0), kind, cfg_ix, scope_id, flags }
}
pub fn id(&self) -> AstNodeId {
self.id
}
pub fn cfg_ix(&self) -> NodeIndex {
self.cfg_ix
}
pub fn kind(&self) -> AstKind<'a> {
self.kind
}

View file

@ -19,14 +19,13 @@ pub fn neighbors_filtered_by_edge_weight<
) -> Vec<State>
where
F: Fn(&EdgeWeight) -> Option<State>,
G: FnMut(&NodeWeight, State) -> (State, bool),
G: FnMut(&NodeIndex, State) -> (State, bool),
{
let mut q = vec![];
let mut final_states = vec![];
// for initial node
let (new_state, keep_walking_this_path) =
visitor(graph.node_weight(node).unwrap(), Default::default());
let (new_state, keep_walking_this_path) = visitor(&node, Default::default());
// if we will continue walking push this node
if keep_walking_this_path {
q.push((node, new_state));
@ -42,7 +41,7 @@ where
} else {
let opposite_dir_of_edge_graph_ix = edge.target();
let (new_state, keep_walking_this_path) =
visitor(graph.node_weight(opposite_dir_of_edge_graph_ix).unwrap(), state);
visitor(&opposite_dir_of_edge_graph_ix, state);
if keep_walking_this_path {
q.push((opposite_dir_of_edge_graph_ix, new_state));
} else {
@ -65,13 +64,13 @@ pub fn replicate_tree_to_leaves(
start_at: NodeIndex,
cfg: &mut ControlFlowGraph,
) -> HashMap<NodeIndex, NodeIndex> {
fn duplicate_graph_node(basic_block_id: usize, cfg: &mut ControlFlowGraph) -> NodeIndex {
fn duplicate_graph_node(basic_block_id: NodeIndex, cfg: &mut ControlFlowGraph) -> NodeIndex {
let new_basic_block_graph_ix = cfg.new_basic_block();
// todo: what's a better way to do this?
for i in 0..cfg.basic_blocks[basic_block_id].len() {
let item = cfg.basic_blocks[basic_block_id][i].clone();
cfg.basic_blocks[cfg.current_basic_block].push(item);
let items = cfg.basic_block_by_index(basic_block_id).clone();
for item in items {
cfg.current_basic_block().push(item);
}
// todo: should we add the functions copied here to the function_to_node_ix?
@ -80,12 +79,11 @@ pub fn replicate_tree_to_leaves(
}
let preserved_graph_ix = cfg.current_node_ix;
let preserved_bb = cfg.current_basic_block;
let mut old_to_new: HashMap<NodeIndex, NodeIndex> = HashMap::default();
let mut q = vec![];
let new_graph_start_ix = duplicate_graph_node(*cfg.graph.node_weight(start_at).unwrap(), cfg);
let new_graph_start_ix = duplicate_graph_node(start_at, cfg);
old_to_new.insert(start_at, new_graph_start_ix);
q.push((new_graph_start_ix, start_at));
let mut edges_finished_already = HashSet::new();
@ -108,7 +106,7 @@ pub fn replicate_tree_to_leaves(
let new_graph_ix = if old_to_new.contains_key(&to) {
old_to_new[&to]
} else {
let new_graph_ix = duplicate_graph_node(*cfg.graph.node_weight(to).unwrap(), cfg);
let new_graph_ix = duplicate_graph_node(to, cfg);
old_to_new.insert(node.1, new_graph_start_ix);
new_graph_ix
};
@ -119,7 +117,6 @@ pub fn replicate_tree_to_leaves(
}
}
cfg.current_basic_block = preserved_bb;
cfg.current_node_ix = preserved_graph_ix;
old_to_new
}

View file

@ -6,10 +6,11 @@ input_file: crates/oxc_semantic/tests/cfg_fixtures/class_extend_super.js
digraph {
0 [ label = ""]
1 [ label = "$return = <value>"]
2 [ label = "Unreachable()"]
3 [ label = ""]
2 [ label = ""]
3 [ label = "Unreachable()"]
4 [ label = ""]
0 -> 1 [ ]
1 -> 2 [ ]
0 -> 3 [ ]
2 -> 3 [ ]
0 -> 4 [ ]
}

View file

@ -12,9 +12,13 @@ bb1: {
}
bb2: {
Unreachable()
}
bb3: {
Unreachable()
}
bb4: {
}

View file

@ -6,10 +6,11 @@ input_file: crates/oxc_semantic/tests/cfg_fixtures/fn_return_obj_expr_with_compu
digraph {
0 [ label = ""]
1 [ label = "$return = <value>"]
2 [ label = "Unreachable()"]
3 [ label = ""]
2 [ label = ""]
3 [ label = "Unreachable()"]
4 [ label = ""]
0 -> 1 [ ]
1 -> 2 [ ]
0 -> 3 [ ]
2 -> 3 [ ]
0 -> 4 [ ]
}

View file

@ -12,9 +12,13 @@ bb1: {
}
bb2: {
Unreachable()
}
bb3: {
Unreachable()
}
bb4: {
}

View file

@ -7,22 +7,25 @@ digraph {
0 [ label = ""]
1 [ label = ""]
2 [ label = "$return = <value>"]
3 [ label = "Unreachable()"]
4 [ label = "$return = <value>"]
5 [ label = "Unreachable()"]
3 [ label = ""]
4 [ label = "Unreachable()"]
5 [ label = "$return = <value>"]
6 [ label = ""]
7 [ label = "Unreachable()\n$return = <value>"]
8 [ label = "Unreachable()"]
9 [ label = ""]
7 [ label = "Unreachable()"]
8 [ label = ""]
9 [ label = "Unreachable()\n$return = <value>"]
10 [ label = ""]
11 [ label = "Unreachable()"]
12 [ label = ""]
0 -> 1 [ ]
2 -> 3 [ ]
4 -> 5 [ ]
3 -> 4 [ ]
6 -> 7 [ ]
3 -> 6 [ ]
8 -> 9 [ ]
4 -> 8 [ ]
1 -> 2 [ ]
1 -> 4 [ ]
5 -> 6 [ ]
1 -> 5 [ ]
7 -> 8 [ ]
0 -> 9 [ ]
10 -> 11 [ ]
0 -> 12 [ ]
}

View file

@ -16,15 +16,15 @@ bb2: {
}
bb3: {
Unreachable()
}
bb4: {
$return = <value>
Unreachable()
}
bb5: {
Unreachable()
$return = <value>
}
bb6: {
@ -33,13 +33,25 @@ bb6: {
bb7: {
Unreachable()
$return = <value>
}
bb8: {
Unreachable()
}
bb9: {
Unreachable()
$return = <value>
}
bb10: {
}
bb11: {
Unreachable()
}
bb12: {
}

View file

@ -8,30 +8,33 @@ digraph {
1 [ label = ""]
2 [ label = ""]
3 [ label = "$return = <value>"]
4 [ label = "Unreachable()"]
4 [ label = ""]
5 [ label = "Unreachable()"]
6 [ label = ""]
7 [ label = "Unreachable()"]
8 [ label = "$return = <value>"]
9 [ label = ""]
10 [ label = "Unreachable()"]
6 [ label = "Unreachable()"]
7 [ label = ""]
8 [ label = "Unreachable()"]
9 [ label = "$return = <value>"]
10 [ label = ""]
11 [ label = "Unreachable()"]
12 [ label = ""]
13 [ label = "Unreachable()"]
14 [ label = ""]
0 -> 1 [ ]
3 -> 2 [ ]
4 -> 2 [ ]
3 -> 4 [ ]
2 -> 5 [ ]
6 -> 7 [ ]
1 -> 3 [ ]
9 -> 10 [ ]
4 -> 6 [ ]
5 -> 2 [ ]
4 -> 5 [ ]
2 -> 6 [ ]
7 -> 8 [ ]
4 -> 2 [ ]
2 -> 9 [ ]
1 -> 9 [ ]
6 -> 8 [ ]
8 -> 11 [ ]
0 -> 12 [ ]
1 -> 3 [ ]
10 -> 11 [ ]
5 -> 7 [ ]
8 -> 9 [ ]
5 -> 2 [ ]
2 -> 10 [ ]
1 -> 10 [ ]
7 -> 9 [ ]
12 -> 13 [ ]
0 -> 14 [ ]
}

View file

@ -20,7 +20,7 @@ bb3: {
}
bb4: {
Unreachable()
}
bb5: {
@ -28,23 +28,23 @@ bb5: {
}
bb6: {
Unreachable()
}
bb7: {
Unreachable()
}
bb8: {
$return = <value>
Unreachable()
}
bb9: {
$return = <value>
}
bb10: {
Unreachable()
}
bb11: {
@ -54,3 +54,11 @@ bb11: {
bb12: {
}
bb13: {
Unreachable()
}
bb14: {
}

View file

@ -6,12 +6,14 @@ input_file: crates/oxc_semantic/tests/cfg_fixtures/member_access_with_numbered_i
digraph {
0 [ label = ""]
1 [ label = "$return = <value>"]
2 [ label = "Unreachable()\n$return = <value>"]
3 [ label = "Unreachable()"]
2 [ label = ""]
3 [ label = "Unreachable()\n$return = <value>"]
4 [ label = ""]
5 [ label = "Unreachable()"]
6 [ label = ""]
0 -> 1 [ ]
1 -> 2 [ ]
2 -> 3 [ ]
0 -> 4 [ ]
4 -> 5 [ ]
0 -> 6 [ ]
}

View file

@ -12,14 +12,22 @@ bb1: {
}
bb2: {
Unreachable()
$return = <value>
}
bb3: {
Unreachable()
$return = <value>
}
bb4: {
}
bb5: {
Unreachable()
}
bb6: {
}

View file

@ -6,12 +6,14 @@ input_file: crates/oxc_semantic/tests/cfg_fixtures/member_access_with_unreachabl
digraph {
0 [ label = ""]
1 [ label = "$return = <value>"]
2 [ label = "Unreachable()\n$return = <value>"]
3 [ label = "Unreachable()"]
2 [ label = ""]
3 [ label = "Unreachable()\n$return = <value>"]
4 [ label = ""]
5 [ label = "Unreachable()"]
6 [ label = ""]
0 -> 1 [ ]
1 -> 2 [ ]
2 -> 3 [ ]
0 -> 4 [ ]
4 -> 5 [ ]
0 -> 6 [ ]
}

View file

@ -12,14 +12,22 @@ bb1: {
}
bb2: {
Unreachable()
$return = <value>
}
bb3: {
Unreachable()
$return = <value>
}
bb4: {
}
bb5: {
Unreachable()
}
bb6: {
}

View file

@ -6,10 +6,11 @@ input_file: crates/oxc_semantic/tests/cfg_fixtures/nested_computed_member_expres
digraph {
0 [ label = ""]
1 [ label = "$return = <value>"]
2 [ label = "Unreachable()"]
3 [ label = ""]
2 [ label = ""]
3 [ label = "Unreachable()"]
4 [ label = ""]
0 -> 1 [ ]
1 -> 2 [ ]
0 -> 3 [ ]
2 -> 3 [ ]
0 -> 4 [ ]
}

View file

@ -12,9 +12,13 @@ bb1: {
}
bb2: {
Unreachable()
}
bb3: {
Unreachable()
}
bb4: {
}

View file

@ -6,10 +6,11 @@ input_file: crates/oxc_semantic/tests/cfg_fixtures/private_in.js
digraph {
0 [ label = ""]
1 [ label = "$return = <value>"]
2 [ label = "Unreachable()"]
3 [ label = ""]
2 [ label = ""]
3 [ label = "Unreachable()"]
4 [ label = ""]
0 -> 1 [ ]
1 -> 2 [ ]
0 -> 3 [ ]
2 -> 3 [ ]
0 -> 4 [ ]
}

View file

@ -12,9 +12,13 @@ bb1: {
}
bb2: {
Unreachable()
}
bb3: {
Unreachable()
}
bb4: {
}

View file

@ -6,10 +6,11 @@ input_file: crates/oxc_semantic/tests/cfg_fixtures/simple_spread.js
digraph {
0 [ label = ""]
1 [ label = "$return = <value>"]
2 [ label = "Unreachable()"]
3 [ label = ""]
2 [ label = ""]
3 [ label = "Unreachable()"]
4 [ label = ""]
0 -> 1 [ ]
1 -> 2 [ ]
0 -> 3 [ ]
2 -> 3 [ ]
0 -> 4 [ ]
}

View file

@ -12,9 +12,13 @@ bb1: {
}
bb2: {
Unreachable()
}
bb3: {
Unreachable()
}
bb4: {
}

View file

@ -6,10 +6,11 @@ input_file: crates/oxc_semantic/tests/cfg_fixtures/spread_in_a_spread.js
digraph {
0 [ label = ""]
1 [ label = "$return = <value>"]
2 [ label = "Unreachable()"]
3 [ label = ""]
2 [ label = ""]
3 [ label = "Unreachable()"]
4 [ label = ""]
0 -> 1 [ ]
1 -> 2 [ ]
0 -> 3 [ ]
2 -> 3 [ ]
0 -> 4 [ ]
}

View file

@ -12,9 +12,13 @@ bb1: {
}
bb2: {
Unreachable()
}
bb3: {
Unreachable()
}
bb4: {
}

View file

@ -16,14 +16,17 @@ digraph {
9 [ label = "Unreachable()"]
10 [ label = ""]
11 [ label = "$return = <value>"]
12 [ label = "Unreachable()"]
13 [ label = ""]
12 [ label = ""]
13 [ label = "Unreachable()"]
14 [ label = ""]
15 [ label = ""]
16 [ label = "$return = <value>"]
17 [ label = "Unreachable()\n$return = <value>"]
18 [ label = "Unreachable()"]
19 [ label = ""]
16 [ label = ""]
17 [ label = "$return = <value>"]
18 [ label = ""]
19 [ label = "Unreachable()\n$return = <value>"]
20 [ label = ""]
21 [ label = "Unreachable()"]
22 [ label = ""]
0 -> 1 [ ]
2 -> 3 [ ]
3 -> 4 [ ]
@ -33,38 +36,38 @@ digraph {
8 -> 9 [ ]
8 -> 9 [ ]
10 -> 11 [ ]
11 -> 12 [ ]
13 -> 14 [ ]
15 -> 16 [ ]
12 -> 13 [ ]
14 -> 15 [ ]
16 -> 17 [ ]
18 -> 19 [ ]
2 -> 5 [ ]
2 -> 7 [ ]
2 -> 10 [ ]
2 -> 13 [ ]
2 -> 15 [ ]
2 -> 14 [ ]
2 -> 16 [ ]
4 -> 5 [ ]
1 -> 2 [ ]
5 -> 7 [ ]
5 -> 10 [ ]
5 -> 13 [ ]
5 -> 15 [ ]
5 -> 14 [ ]
5 -> 16 [ ]
6 -> 7 [ ]
1 -> 5 [ ]
7 -> 10 [ ]
7 -> 13 [ ]
7 -> 15 [ ]
7 -> 14 [ ]
7 -> 16 [ ]
9 -> 10 [ ]
1 -> 7 [ ]
10 -> 13 [ ]
10 -> 15 [ ]
12 -> 13 [ ]
10 -> 14 [ ]
10 -> 16 [ ]
13 -> 14 [ ]
1 -> 10 [ ]
13 -> 15 [ ]
14 -> 15 [ ]
1 -> 13 [ ]
1 -> 15 [ ]
15 -> 17 [ ]
17 -> 18 [ ]
0 -> 19 [ ]
14 -> 16 [ ]
15 -> 16 [ ]
1 -> 14 [ ]
1 -> 16 [ ]
16 -> 19 [ ]
20 -> 21 [ ]
0 -> 22 [ ]
}

View file

@ -52,11 +52,11 @@ bb11: {
}
bb12: {
Unreachable()
}
bb13: {
Unreachable()
}
bb14: {
@ -68,18 +68,30 @@ bb15: {
}
bb16: {
$return = <value>
}
bb17: {
Unreachable()
$return = <value>
}
bb18: {
Unreachable()
}
bb19: {
Unreachable()
$return = <value>
}
bb20: {
}
bb21: {
Unreachable()
}
bb22: {
}

View file

@ -6,16 +6,18 @@ input_file: crates/oxc_semantic/tests/cfg_fixtures/two_functions.js
digraph {
0 [ label = ""]
1 [ label = "$return = <value>"]
2 [ label = "Unreachable()"]
3 [ label = ""]
4 [ label = "$return = <value>"]
5 [ label = "Unreachable()"]
2 [ label = ""]
3 [ label = "Unreachable()"]
4 [ label = ""]
5 [ label = "$return = <value>"]
6 [ label = ""]
7 [ label = "Unreachable()"]
8 [ label = ""]
0 -> 1 [ ]
1 -> 2 [ ]
0 -> 3 [ ]
3 -> 4 [ ]
2 -> 3 [ ]
0 -> 4 [ ]
4 -> 5 [ ]
3 -> 6 [ ]
6 -> 7 [ ]
4 -> 8 [ ]
}

View file

@ -12,21 +12,29 @@ bb1: {
}
bb2: {
Unreachable()
}
bb3: {
Unreachable()
}
bb4: {
$return = <value>
}
bb5: {
Unreachable()
$return = <value>
}
bb6: {
}
bb7: {
Unreachable()
}
bb8: {
}

View file

@ -8,18 +8,20 @@ digraph {
1 [ label = ""]
2 [ label = ""]
3 [ label = "$return = <value>"]
4 [ label = "Unreachable()"]
5 [ label = "$return = <value>"]
6 [ label = "Unreachable()"]
4 [ label = ""]
5 [ label = "Unreachable()"]
6 [ label = "$return = <value>"]
7 [ label = ""]
8 [ label = "Unreachable()"]
9 [ label = ""]
0 -> 1 [ ]
3 -> 4 [ ]
4 -> 5 [ ]
1 -> 2 [ ]
2 -> 3 [ ]
3 -> 5 [ ]
3 -> 6 [ ]
3 -> 2 [ ]
2 -> 5 [ ]
5 -> 6 [ ]
0 -> 7 [ ]
2 -> 6 [ ]
7 -> 8 [ ]
0 -> 9 [ ]
}

View file

@ -20,17 +20,25 @@ bb3: {
}
bb4: {
Unreachable()
}
bb5: {
$return = <value>
Unreachable()
}
bb6: {
Unreachable()
$return = <value>
}
bb7: {
}
bb8: {
Unreachable()
}
bb9: {
}