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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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