Skip to content

Commit

Permalink
fix #101: disallow lookahead within lookbehind in Ruby
Browse files Browse the repository at this point in the history
  • Loading branch information
Aloso committed Nov 5, 2023
1 parent ab5d21e commit cf4b92f
Show file tree
Hide file tree
Showing 15 changed files with 136 additions and 15 deletions.
2 changes: 1 addition & 1 deletion pomsky-lib/afl-fuzz/justfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
tmin input:
cargo afl tmin -i {{input}} -o out.txt -- ./target/debug/afl-fuzz
AFL_DEBUG=1 AFL_MAP_SIZE=100000 cargo afl tmin -i {{input}} -o out.txt -- ./target/debug/afl-fuzz
2 changes: 2 additions & 0 deletions pomsky-lib/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub(crate) struct CompileState<'c, 'i> {
pub(crate) used_names: HashMap<String, CapturingGroupIndex>,
pub(crate) groups_count: u32,
pub(crate) numbered_groups_count: u32,
pub(crate) in_lookbehind: bool,

pub(crate) default_quantifier: RegexQuantifier,
pub(crate) ascii_only: bool,
Expand Down Expand Up @@ -49,6 +50,7 @@ impl<'c, 'i> CompileState<'c, 'i> {
used_names,
groups_count,
numbered_groups_count: capt_groups.count_numbered,
in_lookbehind: false,

default_quantifier,
ascii_only: false,
Expand Down
56 changes: 56 additions & 0 deletions pomsky-lib/src/defer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::ops::{Deref, DerefMut};

pub(crate) struct Deferred<'a, S, F: FnMut(&mut S)> {
state: &'a mut S,
mutate: F,
}

impl<'a, S, F: FnMut(&mut S)> Deferred<'a, S, F> {
pub(crate) fn new(state: &'a mut S, mutate: F) -> Self {
Deferred { state, mutate }
}
}

impl<'a, S, F: FnMut(&mut S)> Drop for Deferred<'a, S, F> {
fn drop(&mut self) {
let mutator = &mut self.mutate;
mutator(self.state);
}
}

impl<'a, S, F: FnMut(&mut S)> Deref for Deferred<'a, S, F> {
type Target = S;

fn deref(&self) -> &Self::Target {
&self.state

Check warning on line 25 in pomsky-lib/src/defer.rs

View workflow job for this annotation

GitHub Actions / clippy

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> pomsky-lib/src/defer.rs:25:9 | 25 | &self.state | ^^^^^^^^^^^ help: change this to: `self.state` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow = note: `#[warn(clippy::needless_borrow)]` on by default
}
}

impl<'a, S, F: FnMut(&mut S)> DerefMut for Deferred<'a, S, F> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.state

Check warning on line 31 in pomsky-lib/src/defer.rs

View workflow job for this annotation

GitHub Actions / clippy

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> pomsky-lib/src/defer.rs:31:9 | 31 | &mut self.state | ^^^^^^^^^^^^^^^ help: change this to: `self.state` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
}
}

#[doc(hidden)]
pub(crate) trait Mutable {
fn mutable(&mut self) -> &mut Self;
}

impl<T> Mutable for T {
fn mutable(&mut self) -> &mut Self {
self
}
}

macro_rules! revert_on_drop {
($state:ident . $($id:tt).*) => {
let __prev = $state.$($id).*;
let mut $state = {
use $crate::defer::Mutable as _;
$crate::defer::Deferred::new($state.mutable(), move |$state| {
$state.$($id).* = __prev;
})
};
};
}
10 changes: 10 additions & 0 deletions pomsky-lib/src/diagnose/compile_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ pub(crate) enum CompileErrorKind {
UnicodeInAsciiMode,
JsWordBoundaryInUnicodeMode,
DotNetNumberedRefWithMixedGroups,
RubyLookaheadInLookbehind {
was_word_boundary: bool,
},
NestedTest,
}

Expand Down Expand Up @@ -173,6 +176,13 @@ impl core::fmt::Display for CompileErrorKind {
and unnamed capturing groups. This is because .NET counts named and unnamed \
capturing groups separately, which is inconsistent with other flavors."
),
CompileErrorKind::RubyLookaheadInLookbehind { was_word_boundary: false } => {
write!(f, "In the Ruby flavor, lookahead is not allowed within lookbehind")
}
CompileErrorKind::RubyLookaheadInLookbehind { was_word_boundary: true } => write!(
f,
"In the Ruby flavor, `<` and `>` word boundaries are not allowed within lookbehind"
),
CompileErrorKind::NestedTest => {
write!(f, "Unit tests may only appear at the top level of the expression")
}
Expand Down
4 changes: 3 additions & 1 deletion pomsky-lib/src/diagnose/diagnostic_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ diagnostic_code! {
NestedTest = 316,
IllegalNegation = 317,
DotNetNumberedRefWithMixedGroups = 318,
RubyLookaheadInLookbehind = 319,

// Warning indicating something might not be supported
PossiblyUnsupported = 400,
Expand Down Expand Up @@ -219,7 +220,8 @@ impl<'a> From<&'a CompileErrorKind> for DiagnosticCode {
C::NegativeShorthandInAsciiMode => Self::UnsupportedInAsciiMode,
C::UnicodeInAsciiMode => Self::UnsupportedInAsciiMode,
C::JsWordBoundaryInUnicodeMode => Self::UnsupportedInUnicodeMode,
C::DotNetNumberedRefWithMixedGroups => Self::UnsupportedInUnicodeMode,
C::DotNetNumberedRefWithMixedGroups => Self::DotNetNumberedRefWithMixedGroups,
C::RubyLookaheadInLookbehind { .. } => Self::RubyLookaheadInLookbehind,
C::NestedTest => Self::NestedTest,
}
}
Expand Down
9 changes: 5 additions & 4 deletions pomsky-lib/src/diagnose/diagnostic_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,18 @@ impl From<&CompileErrorKind> for DiagnosticKind {
| K::UnknownReferenceName { .. }
| K::NameUsedMultipleTimes(_)
| K::UnknownVariable { .. }
| K::NegatedHorizVertSpace
| K::RelativeRefZero
| K::DotNetNumberedRefWithMixedGroups => DiagnosticKind::Resolve,
| K::RelativeRefZero => DiagnosticKind::Resolve,
K::EmptyClassNegated { .. } | K::IllegalNegation { .. } => DiagnosticKind::Invalid,
K::CaptureInLet
| K::ReferenceInLet
| K::RecursiveVariable
| K::NegativeShorthandInAsciiMode
| K::UnicodeInAsciiMode
| K::JsWordBoundaryInUnicodeMode
| K::NestedTest => DiagnosticKind::Unsupported,
| K::NestedTest
| K::NegatedHorizVertSpace
| K::DotNetNumberedRefWithMixedGroups
| K::RubyLookaheadInLookbehind { .. } => DiagnosticKind::Unsupported,
K::RangeIsTooBig(_) => DiagnosticKind::Limits,
}
}
Expand Down
3 changes: 3 additions & 0 deletions pomsky-lib/src/exprs/boundary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ impl<'i> RuleExt<'i> for Boundary {
&& matches!(self.kind, Word | NotWord | WordStart | WordEnd)
{
Err(CompileErrorKind::JsWordBoundaryInUnicodeMode.at(self.span))
} else if options.flavor == RegexFlavor::Ruby && state.in_lookbehind {
Err(CompileErrorKind::RubyLookaheadInLookbehind { was_word_boundary: true }
.at(self.span))
} else {
Ok(Regex::Boundary(self.kind))
}
Expand Down
34 changes: 25 additions & 9 deletions pomsky-lib/src/exprs/lookaround.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use pomsky_syntax::exprs::{Lookaround, LookaroundKind};

use crate::{
compile::{CompileResult, CompileState},
diagnose::{CompatWarning, CompileWarningKind},
diagnose::{CompatWarning, CompileErrorKind, CompileWarningKind},
options::{CompileOptions, RegexFlavor},
regex::Regex,
};
Expand All @@ -15,18 +15,34 @@ impl<'i> RuleExt<'i> for Lookaround<'i> {
options: CompileOptions,
state: &mut CompileState<'c, 'i>,
) -> CompileResult<'i> {
if let RegexFlavor::JavaScript = options.flavor {
if let LookaroundKind::Behind | LookaroundKind::BehindNegative = self.kind {
state.diagnostics.push(
CompileWarningKind::Compat(CompatWarning::JsLookbehind)
.at(self.span)
.diagnostic(),
);
match options.flavor {
RegexFlavor::JavaScript => {
if let LookaroundKind::Behind | LookaroundKind::BehindNegative = self.kind {
state.diagnostics.push(
CompileWarningKind::Compat(CompatWarning::JsLookbehind)
.at(self.span)
.diagnostic(),
);
}
}
RegexFlavor::Ruby if state.in_lookbehind => {
if let LookaroundKind::Ahead | LookaroundKind::AheadNegative = self.kind {
return Err(CompileErrorKind::RubyLookaheadInLookbehind {
was_word_boundary: false,
}
.at(self.span));
}
}
_ => (),
}

revert_on_drop!(state.in_lookbehind);
if let LookaroundKind::Behind | LookaroundKind::BehindNegative = self.kind {
state.in_lookbehind = true;
}

Ok(Regex::Lookaround(Box::new(RegexLookaround {
content: self.rule.compile(options, state)?,
content: self.rule.compile(options, &mut state)?,
kind: self.kind,
})))
}
Expand Down
3 changes: 3 additions & 0 deletions pomsky-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@

#![warn(missing_docs)]

#[macro_use]
mod defer;

pub mod diagnose;
pub mod error;
pub mod features;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#! flavor=Ruby
>> %
-----
(?=\b)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#! flavor=Ruby
>> <
-----
(?=(?<!\w)(?=\w))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#! expect=error, flavor=Ruby
<< <
-----
ERROR: In the Ruby flavor, `<` and `>` word boundaries are not allowed within lookbehind
SPAN: 3..4
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#! flavor=Ruby
(<< 'foo') (>> 'bar')
-----
(?<=foo)(?=bar)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#! expect=error, flavor=Ruby
<< 'foo' >> 'bar'
-----
ERROR: In the Ruby flavor, lookahead is not allowed within lookbehind
SPAN: 9..17
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#! expect=error, flavor=Ruby
let la = >> 'bar';
<< 'foo' la
-----
ERROR: In the Ruby flavor, lookahead is not allowed within lookbehind
SPAN: 9..17

0 comments on commit cf4b92f

Please sign in to comment.