Skip to content

Commit

Permalink
refactor(cfg)!: move control flow to its own crate. (#3728)
Browse files Browse the repository at this point in the history
  • Loading branch information
rzvxa committed Jun 18, 2024
1 parent 527bfc8 commit 1ccbd3a
Show file tree
Hide file tree
Showing 22 changed files with 243 additions and 183 deletions.
15 changes: 13 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ oxc_module_lexer = { version = "0.14.0", path = "crates/oxc_module_lexe
oxc_isolated_declarations = { version = "0.14.0", path = "crates/oxc_isolated_declarations" }

# publish = false
oxc_cfg = { path = "crates/oxc_cfg" }
oxc_macros = { path = "crates/oxc_macros" }
oxc_linter = { path = "crates/oxc_linter" }
oxc_prettier = { path = "crates/oxc_prettier" }
Expand Down
27 changes: 27 additions & 0 deletions crates/oxc_cfg/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "oxc_cfg"
version = "0.0.0"
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
categories.workspace = true
include = ["/src"]

[lints]
workspace = true

[lib]
doctest = true

[dependencies]
oxc_syntax = { workspace = true }

itertools = { workspace = true }
bitflags = { workspace = true }
petgraph = { workspace = true }
rustc-hash = { workspace = true }
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ impl<'a> Ctx<'a> {
}

pub trait CtxCursor {
#![allow(clippy::return_self_not_must_use)]
/// Marks the break jump position in the current context.
fn mark_break(self, jmp_pos: BasicBlockId) -> Self;
/// Marks the continue jump position in the current context.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ use crate::ReturnInstructionKind;
use context::Ctx;

pub use context::{CtxCursor, CtxFlags};
use oxc_syntax::node::AstNodeId;
use petgraph::Direction;

use super::{
AstNodeId, BasicBlock, BasicBlockId, ControlFlowGraph, EdgeType, ErrorEdgeKind, Graph,
Instruction, InstructionKind, IterationInstructionKind, LabeledInstruction,
BasicBlock, BasicBlockId, ControlFlowGraph, EdgeType, ErrorEdgeKind, Graph, Instruction,
InstructionKind, IterationInstructionKind, LabeledInstruction,
};

#[derive(Debug, Default)]
Expand Down Expand Up @@ -70,7 +71,8 @@ impl<'a> ControlFlowGraphBuilder<'a> {
self.new_basic_block_normal()
}

/// # Panics if there is no error harness to attach to.
/// # Panics
/// if there is no error harness to attach to.
#[must_use]
pub fn new_basic_block_normal(&mut self) -> BasicBlockId {
let graph_ix = self.new_basic_block();
Expand Down Expand Up @@ -122,7 +124,8 @@ impl<'a> ControlFlowGraphBuilder<'a> {
graph_ix
}

/// # Panics if there is no error harness pushed onto the stack,
/// # Panics
/// if there is no error harness pushed onto the stack,
/// Or last harness doesn't match the expected `BasicBlockId`.
pub fn release_error_harness(&mut self, expect: BasicBlockId) {
let harness = self
Expand Down Expand Up @@ -152,7 +155,8 @@ impl<'a> ControlFlowGraphBuilder<'a> {
debug_assert!(result.as_ref().is_some_and(Option::is_none));
}

/// # Panics if last finalizer doesn't match the expected `BasicBlockId`.
/// # Panics
/// if last finalizer doesn't match the expected `BasicBlockId`.
pub fn release_finalizer(&mut self, expect: BasicBlockId) {
// return early if there is no finalizer.
let Some(finalizer) = self.finalizers.pop() else { return };
Expand Down Expand Up @@ -216,6 +220,7 @@ impl<'a> ControlFlowGraphBuilder<'a> {
);
}

/// # Panics
#[inline]
pub(self) fn push_instruction(&mut self, kind: InstructionKind, node_id: Option<AstNodeId>) {
self.push_instruction_to(self.current_node_ix, kind, node_id);
Expand Down
80 changes: 80 additions & 0 deletions crates/oxc_cfg/src/dot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// use oxc_ast::{
// ast::{BreakStatement, ContinueStatement},
// AstKind,
// };
use petgraph::{
dot::{Config, Dot},
visit::EdgeRef,
};

use crate::{
BasicBlock, ControlFlowGraph, EdgeType, Instruction, InstructionKind, LabeledInstruction,
ReturnInstructionKind,
};

use super::IterationInstructionKind;

pub trait DisplayDot {
fn display_dot(&self) -> String;
}

impl DisplayDot for ControlFlowGraph {
fn display_dot(&self) -> String {
format!(
"{:?}",
Dot::with_attr_getters(
&self.graph,
&[Config::EdgeNoLabel, Config::NodeNoLabel],
&|_graph, edge| {
let weight = edge.weight();
let label = format!("label = \"{weight:?}\" ");
if matches!(weight, EdgeType::Unreachable)
|| self.basic_block(edge.source()).unreachable
{
format!("{label}, style = \"dotted\" ")
} else {
label
}
},
&|_graph, node| format!(
"label = {:?} ",
self.basic_blocks[*node.1].display_dot().trim()
),
)
)
}
}

impl DisplayDot for BasicBlock {
fn display_dot(&self) -> String {
self.instructions().iter().fold(String::new(), |mut acc, it| {
acc.push_str(it.display_dot().as_str());
acc.push('\n');
acc
})
}
}

impl DisplayDot for Instruction {
fn display_dot(&self) -> String {
match self.kind {
InstructionKind::Statement => "statement",
InstructionKind::Unreachable => "unreachable",
InstructionKind::Throw => "throw",
InstructionKind::Condition => "condition",
InstructionKind::Iteration(IterationInstructionKind::Of) => "iteration <of>",
InstructionKind::Iteration(IterationInstructionKind::In) => "iteration <in>",
InstructionKind::Break(LabeledInstruction::Labeled) => "break <label>",
InstructionKind::Break(LabeledInstruction::Unlabeled) => "break",
InstructionKind::Continue(LabeledInstruction::Labeled) => "continue <label>",
InstructionKind::Continue(LabeledInstruction::Unlabeled) => "continue",
InstructionKind::Return(ReturnInstructionKind::ImplicitUndefined) => {
"return <implicit undefined>"
}
InstructionKind::Return(ReturnInstructionKind::NotImplicitUndefined) => {
"return <value>"
}
}
.to_string()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod dot;
pub mod visit;

use itertools::Itertools;
use oxc_ast::AstKind;
use oxc_syntax::node::AstNodeId;
use petgraph::{
stable_graph::NodeIndex,
visit::{depth_first_search, Control, DfsEvent, EdgeRef},
Expand All @@ -18,10 +18,8 @@ pub mod graph {
}
}

use crate::{AstNodeId, AstNodes};

pub(crate) use builder::{ControlFlowGraphBuilder, CtxCursor, CtxFlags};
pub use dot::{DebugDot, DebugDotContext, DisplayDot};
pub use builder::{ControlFlowGraphBuilder, CtxCursor, CtxFlags};
pub use dot::DisplayDot;

pub type BasicBlockId = NodeIndex;

Expand Down Expand Up @@ -113,6 +111,12 @@ pub enum ErrorEdgeKind {
Implicit,
}

pub enum EvalConstConditionResult {
NotFound,
Fail,
Eval(bool),
}

#[derive(Debug)]
pub struct ControlFlowGraph {
pub graph: Graph<usize, EdgeType>,
Expand Down Expand Up @@ -172,32 +176,14 @@ impl ControlFlowGraph {

/// Returns `None` the given node isn't the cyclic point of an infinite loop.
/// Otherwise returns `Some(loop_start, loop_end)`.
pub fn is_infinite_loop_start(
pub fn is_infinite_loop_start<F>(
&self,
node: BasicBlockId,
nodes: &AstNodes,
) -> Option<(BasicBlockId, BasicBlockId)> {
enum EvalConstConditionResult {
NotFound,
Fail,
Eval(bool),
}
fn try_eval_const_condition(
instruction: &Instruction,
nodes: &AstNodes,
) -> EvalConstConditionResult {
use EvalConstConditionResult::{Eval, Fail, NotFound};
match instruction {
Instruction { kind: InstructionKind::Condition, node_id: Some(id) } => {
match nodes.kind(*id) {
AstKind::BooleanLiteral(lit) => Eval(lit.value),
_ => Fail,
}
}
_ => NotFound,
}
}

try_eval_const_condition: F,
) -> Option<(BasicBlockId, BasicBlockId)>
where
F: Fn(&Instruction) -> EvalConstConditionResult,
{
fn get_jump_target(
graph: &Graph<usize, EdgeType>,
node: BasicBlockId,
Expand Down Expand Up @@ -239,15 +225,14 @@ impl ControlFlowGraph {

// if there is exactly one and it is a condition instruction we are in a loop so we
// check the condition to infer if it is always true.
if let EvalConstConditionResult::Eval(true) =
try_eval_const_condition(only_instruction, nodes)
{
if let EvalConstConditionResult::Eval(true) = try_eval_const_condition(only_instruction) {
get_jump_target(&self.graph, node).map(|it| (it, node))
} else if let EvalConstConditionResult::Eval(true) =
self.basic_block(backedge.source()).instructions().iter().exactly_one().map_or_else(
|_| EvalConstConditionResult::NotFound,
|it| try_eval_const_condition(it, nodes),
)
} else if let EvalConstConditionResult::Eval(true) = self
.basic_block(backedge.source())
.instructions()
.iter()
.exactly_one()
.map_or_else(|_| EvalConstConditionResult::NotFound, try_eval_const_condition)
{
get_jump_target(&self.graph, node).map(|it| (node, it))
} else {
Expand Down
File renamed without changes.
27 changes: 14 additions & 13 deletions crates/oxc_linter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ workspace = true
doctest = false

[dependencies]
oxc_allocator = { workspace = true }
oxc_parser = { workspace = true }
oxc_span = { workspace = true }
oxc_ast = { workspace = true }
oxc_diagnostics = { workspace = true }
oxc_macros = { workspace = true }
oxc_semantic = { workspace = true }
oxc_syntax = { workspace = true }
oxc_codegen = { workspace = true }
oxc_resolver = { workspace = true }
oxc_allocator = { workspace = true }
oxc_parser = { workspace = true }
oxc_span = { workspace = true }
oxc_ast = { workspace = true }
oxc_cfg = { workspace = true }
oxc_diagnostics = { workspace = true }
oxc_macros = { workspace = true }
oxc_semantic = { workspace = true }
oxc_syntax = { workspace = true }
oxc_codegen = { workspace = true }
oxc_resolver = { workspace = true }

rayon = { workspace = true }
lazy_static = { workspace = true }
Expand All @@ -51,6 +52,6 @@ json-strip-comments = { workspace = true }
schemars = { workspace = true, features = ["indexmap2"] }

[dev-dependencies]
static_assertions = { workspace = true }
insta = { workspace = true }
project-root = { workspace = true }
static_assertions = { workspace = true }
insta = { workspace = true }
project-root = { workspace = true }
6 changes: 3 additions & 3 deletions crates/oxc_linter/src/rules/eslint/getter_return.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ use oxc_ast::{
};
use oxc_diagnostics::OxcDiagnostic;

use oxc_macros::declare_oxc_lint;
use oxc_semantic::{
control_flow::graph::visit::neighbors_filtered_by_edge_weight, EdgeType, InstructionKind,
use oxc_cfg::{
graph::visit::neighbors_filtered_by_edge_weight, EdgeType, InstructionKind,
ReturnInstructionKind,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

use crate::{context::LintContext, rule::Rule, AstNode};
Expand Down
8 changes: 4 additions & 4 deletions crates/oxc_linter/src/rules/eslint/no_fallthrough.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ use oxc_ast::{
ast::{Statement, SwitchCase, SwitchStatement},
AstKind,
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::{
control_flow::graph::{
use oxc_cfg::{
graph::{
visit::{neighbors_filtered_by_edge_weight, EdgeRef},
Direction,
},
BasicBlockId, EdgeType, ErrorEdgeKind, InstructionKind,
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
Expand Down
Loading

0 comments on commit 1ccbd3a

Please sign in to comment.