Repository: fancy-regex/fancy-regex (language: rust)

Issue:
Look-ahead/behind with repeat operator should not compile
From https://github.com/fancy-regex/fancy-regex/issues/66#issuecomment-779820378

The following types of regexes should not compile (but they currently do):

```
(?=a)*
(?!)?
(?=a){1,2}
(?<=a)*
(?<!a)*
```

Note that Oniguruma fails with this error: `target of repeat operator is invalid`


Discussion / hints:

Fix patch:
diff --git a/src/error.rs b/src/error.rs
index 8fed5e0..b87eb3b 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -44,6 +44,9 @@ pub enum Error {
     /// Once named groups are used you cannot refer to groups by number
     NamedBackrefOnly,
 
+    /// Quantifier on lookaround or other zero-width assertion
+    TargetNotRepeatable,
+
     // Run time errors
     /// Max stack size exceeded for backtracking while executing regex.
     StackOverflow,
@@ -89,6 +92,7 @@ impl fmt::Display for Error {
             Error::__Nonexhaustive => unreachable!(),
             Error::InvalidGroupName => write!(f, "Could not parse group name"),
             Error::InvalidGroupNameBackref(s) => write!(f, "Invalid group name in back reference: {}", s),
+            Error::TargetNotRepeatable => write!(f, "Target of repeat operator is invalid"),
             Error::NamedBackrefOnly => write!(f, "Numbered backref/call not allowed because named group was used, use a named backref instead"),
         }
     }
diff --git a/src/parse.rs b/src/parse.rs
index 298844d..40fcbdd 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -149,6 +149,9 @@ impl<'a> Parser<'a> {
                 }
                 _ => return Ok((ix, child)),
             };
+            if !self.is_repeatable(&child) {
+                return Err(Error::TargetNotRepeatable);
+            }
             ix += 1;
             ix = self.optional_whitespace(ix)?;
             let mut greedy = true;
@@ -172,6 +175,18 @@ impl<'a> Parser<'a> {
         Ok((ix, child))
     }
 
+    fn is_repeatable(&self, child: &Expr) -> bool {
+        match child {
+            Expr::LookAround(_, _) => false,
+            Expr::Empty => false,
+            Expr::StartText => false,
+            Expr::EndText => false,
+            Expr::StartLine => false,
+            Expr::EndLine => false,
+            _ => true,
+        }
+    }
+
     // ix, lo, hi
     fn parse_repeat(&self, ix: usize) -> Result<(usize, usize, usize)> {
         let ix = self.optional_whitespace(ix + 1)?; // skip opening '{'
@@ -1316,6 +1331,19 @@ mod tests {
         assert_error("(?\u{1F60A})", "Unknown group flag: (?\u{1F60A}");
     }
 
+    #[test]
+    fn no_quantifiers_on_lookarounds() {
+        assert_error("(?=hello)+", "Target of repeat operator is invalid");
+        assert_error("(?<!hello)*", "Target of repeat operator is invalid");
+        assert_error("(?<=hello){2,3}", "Target of repeat operator is invalid");
+        assert_error("(?!hello)?", "Target of repeat operator is invalid");
+        assert_error("^?", "Target of repeat operator is invalid");
+        assert_error("${2}", "Target of repeat operator is invalid");
+        assert_error("(?m)^?", "Target of repeat operator is invalid");
+        assert_error("(?m)${2}", "Target of repeat operator is invalid");
+        assert_error("(a|b|?)", "Target of repeat operator is invalid");
+    }
+
     // found by cargo fuzz, then minimized
     #[test]
     fn fuzz_1() {


Test patch:
diff --git a/tests/oniguruma/test_utf8_ignore.c b/tests/oniguruma/test_utf8_ignore.c
index e4133af..a129700 100644
--- a/tests/oniguruma/test_utf8_ignore.c
+++ b/tests/oniguruma/test_utf8_ignore.c
@@ -918,24 +918,10 @@
   // Compile failed: UnknownFlag("(?(")
   x2("((?()0+)+++(((0\\g<0>)0)|())++++((?(1)(0\\g<0>))++++++0*())++++((?(1)(0\\g<1>)+)++++++++++*())++++((?(1)((0)\\g<0>)+)++())+0++*+++(((0\\g<0>))*())++++((?(1)(0\\g<0>)+)++++++++++*|)++++*+++((?(1)((0)\\g<0>)+)+++++++++())++*|)++++((?()0))|", "abcde", 0, 0);
 
-  // Compile failed: InnerError(Syntax(
-  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-  // regex parse error:
-  //     (?:[ab]|(*MAX{2}).)*
-  //              ^
-  // error: repetition operator missing expression
-  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-  // ))
+  // Compile failed: TargetNotRepeatable
   x2("(?:[ab]|(*MAX{2}).)*", "abcbaaccaaa", 0, 7);
 
-  // Compile failed: InnerError(Syntax(
-  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-  // regex parse error:
-  //     (?:(*COUNT[AB]\{X\})[ab]|(*COUNT[CD]\{X\})[cd])*(*CMP\{AB,<,CD\})
-  //         ^
-  // error: repetition operator missing expression
-  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-  // ))
+  // Compile failed: TargetNotRepeatable
   x2("(?:(*COUNT[AB]{X})[ab]|(*COUNT[CD]{X})[cd])*(*CMP{AB,<,CD})",
      "abababcdab", 5, 8);