Skip to content

Commit

Permalink
feat: detect infinite recursion
Browse files Browse the repository at this point in the history
  • Loading branch information
Aloso committed Dec 21, 2024
1 parent d00dbcd commit 5529450
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 8 deletions.
2 changes: 2 additions & 0 deletions pomsky-lib/src/diagnose/compile_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ pub(crate) enum CompileErrorKind {
flavor: RegexFlavor,
},
NestedTest,
InfiniteRecursion,
BadIntersection,
EmptyIntersection,
}
Expand Down Expand Up @@ -219,6 +220,7 @@ impl core::fmt::Display for CompileErrorKind {
),
_ => write!(f, "This kind of lookbehind is not supported in the {flavor:?} flavor"),
},
CompileErrorKind::InfiniteRecursion => write!(f, "This recursion never terminates"),
CompileErrorKind::BadIntersection => write!(
f,
"Intersecting these expressions is not supported. Only character sets \
Expand Down
6 changes: 4 additions & 2 deletions pomsky-lib/src/diagnose/diagnostic_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ diagnostic_code! {
RubyLookaheadInLookbehind = 319,
UnsupportedInLookbehind = 320,
LookbehindNotConstantLength = 321,
BadIntersection = 322,
EmptyIntersection = 323,
InfiniteRecursion = 322,
BadIntersection = 323,
EmptyIntersection = 324,

// Warning indicating something might not be supported
PossiblyUnsupported = 400,
Expand Down Expand Up @@ -234,6 +235,7 @@ impl<'a> From<&'a CompileErrorKind> for DiagnosticCode {
C::UnsupportedInLookbehind { .. } => Self::UnsupportedInLookbehind,
C::LookbehindNotConstantLength { .. } => Self::LookbehindNotConstantLength,
C::NestedTest => Self::NestedTest,
C::InfiniteRecursion => Self::InfiniteRecursion,
C::BadIntersection => Self::BadIntersection,
C::EmptyIntersection => Self::EmptyIntersection,
}
Expand Down
7 changes: 4 additions & 3 deletions pomsky-lib/src/diagnose/diagnostic_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ impl From<&CompileErrorKind> for DiagnosticKind {
| K::NameUsedMultipleTimes(_)
| K::UnknownVariable { .. }
| K::RelativeRefZero => DiagnosticKind::Resolve,
K::EmptyClassNegated { .. } | K::IllegalNegation { .. } | K::EmptyIntersection => {
DiagnosticKind::Invalid
}
K::EmptyClassNegated { .. }
| K::InfiniteRecursion
| K::IllegalNegation { .. }
| K::EmptyIntersection => DiagnosticKind::Invalid,
K::CaptureInLet
| K::ReferenceInLet
| K::RecursiveVariable
Expand Down
5 changes: 5 additions & 0 deletions pomsky-lib/src/diagnose/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ pub(super) fn get_compiler_help(kind: &CompileErrorKind, _span: Span) -> Option<
Parentheses may be required to clarify the parsing order."
.to_string(),
),
CompileErrorKind::InfiniteRecursion => Some(
"A recursive expression must have a branch that \
doesn't reach the `recursion`, or can repeat 0 times"
.to_string(),
),

_ => None,
}
Expand Down
11 changes: 9 additions & 2 deletions pomsky-lib/src/exprs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
capturing_groups::CapturingGroupsCollector,
compile::{CompileResult, CompileState},
diagnose::Diagnostic,
diagnose::{CompileErrorKind, Diagnostic},
options::CompileOptions,
regex::Count,
validation::Validator,
Expand Down Expand Up @@ -60,7 +60,8 @@ impl Expr {
input: &str,
options: CompileOptions,
) -> (Option<String>, Vec<Diagnostic>) {
if let Err(e) = Validator::new(options).visit_rule(&self.0) {
let mut validator = Validator::new(options);
if let Err(e) = validator.visit_rule(&self.0) {
return (None, vec![e.diagnostic(input)]);
}

Expand Down Expand Up @@ -90,6 +91,12 @@ impl Expr {
Ok(compiled) => compiled,
Err(e) => return (None, vec![e.diagnostic(input)]),
};
if let Some(rec_span) = validator.first_recursion {
if !compiled.terminates() {
let error = CompileErrorKind::InfiniteRecursion.at(rec_span);
return (None, vec![error.diagnostic(input)]);
}
}
let count = compiled.optimize();

let mut buf = String::new();
Expand Down
13 changes: 13 additions & 0 deletions pomsky-lib/src/regex/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,19 @@ impl Regex {
matches!(self, Regex::CharSet(_))
}
}

pub(super) fn terminates(&self) -> bool {
match self {
Regex::Recursion => false,
Regex::Repetition(repetition) => {
repetition.kind.lower_bound == 0 || repetition.content.terminates()
}
Regex::Group(group) => group.parts.iter().all(|part| part.terminates()),
Regex::Alternation(alternation) => alternation.parts.iter().any(|alt| alt.terminates()),
Regex::Lookaround(lookaround) => lookaround.content.terminates(),
_ => true,
}
}
}

impl Default for Regex {
Expand Down
7 changes: 6 additions & 1 deletion pomsky-lib/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ use crate::{
#[derive(Clone)]
pub(crate) struct Validator {
pub(crate) options: CompileOptions,
pub(crate) first_recursion: Option<Span>,
pub(crate) layer: u32,
}

impl Validator {
pub(crate) fn new(options: CompileOptions) -> Self {
Validator { options, layer: 0 }
Validator { options, first_recursion: None, layer: 0 }
}

fn require(&self, feature: u16, span: Span) -> Result<(), CompileError> {
Expand Down Expand Up @@ -132,6 +133,10 @@ impl RuleVisitor<CompileError> for Validator {
fn visit_recursion(&mut self, recursion: &exprs::Recursion) -> Result<(), CompileError> {
self.require(Feat::RECURSION, recursion.span)?;

if self.first_recursion.is_none() {
self.first_recursion = Some(recursion.span);
}

if let RegexFlavor::Pcre | RegexFlavor::Ruby = self.flavor() {
Ok(())
} else {
Expand Down
6 changes: 6 additions & 0 deletions pomsky-lib/tests/testcases/recursion/never_terminates.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#! expect=error, flavor=Pcre
("foo" recursion)+
-----
ERROR: This recursion never terminates
HELP: A recursive expression must have a branch that doesn't reach the `recursion`, or can repeat 0 times
SPAN: 7..16

0 comments on commit 5529450

Please sign in to comment.