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

Issue:
panic when evaluating a specific regex
Using the regex & text used in benches, but with the public `Regex::new` API instead of the private one, leads to a panic in the eval VM:

```rust
use fancy_regex::Regex;

fn main() {
    let regex = Regex::new("^.*?(([ab]+)\\1b)").unwrap();
    eprintln!("match: {:?}", regex.is_match("babab"));
}
```

gives this:

```
thread 'main' panicked at 'byte index 18446744073709551615 is out of bounds of `babab`', library/core/src/str/mod.rs:107:9
stack backtrace:
   0: rust_begin_unwind
             at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/std/src/panicking.rs:584:5
   1: core::panicking::panic_fmt
             at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/panicking.rs:142:14
   2: core::str::slice_error_fail_rt
   3: core::ops::function::FnOnce::call_once
             at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/ops/function.rs:248:5
   4: core::intrinsics::const_eval_select
             at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/intrinsics.rs:2696:5
   5: core::str::slice_error_fail
             at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/str/mod.rs:86:9
   6: fancy_regex::vm::run
   7: fancy_regex::Regex::is_match
   8: bug::main
```

This is both present on `0.10.0` and on current main (f594a9ba1f675fce87b1f24a26d95d448afacc75)


Discussion / hints:
Thanks for reporting. This is also reproducible as `cargo run --example toy run '^.*?(([ab]+)\1b)' babab`. It seems like it is somehow caused by the `.*` being lazy.

Fix patch:
diff --git a/benches/bench.rs b/benches/bench.rs
index 6e10e37..c6e7ea0 100644
--- a/benches/bench.rs
+++ b/benches/bench.rs
@@ -63,7 +63,11 @@ fn run_backtrack(c: &mut Criterion) {
     let a = analyze(&tree).unwrap();
     let p = compile(&a).unwrap();
     c.bench_function("run_backtrack", |b| {
-        b.iter(|| run_default(&p, "babab", 0).unwrap())
+        b.iter(|| {
+            let result = run_default(&p, "babab", 0).unwrap();
+            assert_eq!(result, Some(vec![0, 5, 0, 2]));
+            return result;
+        })
     });
 }
 
diff --git a/src/vm.rs b/src/vm.rs
index 87c185a..3eb1511 100644
--- a/src/vm.rs
+++ b/src/vm.rs
@@ -595,6 +595,10 @@ pub(crate) fn run(
                         break 'fail;
                     }
                     let hi = state.get(slot + 1);
+                    if hi == usize::MAX {
+                        // Referenced group hasn't matched, so the backref doesn't match either
+                        break 'fail;
+                    }
                     let ref_text = &s[lo..hi];
                     let ix_end = ix + ref_text.len();
                     if !matches_literal(s, ix, ix_end, ref_text) {


Test patch:
diff --git a/tests/matching.rs b/tests/matching.rs
index c8d1fed..fb5154d 100644
--- a/tests/matching.rs
+++ b/tests/matching.rs
@@ -112,6 +112,11 @@ fn end_of_hard_expression_cannot_be_delegated() {
     assert_match(r"((?!x)(?:a|ab))c", "abc");
 }
 
+#[test]
+fn issue103() {
+    assert_no_match(r"(([ab]+)\1b)", "babab");
+}
+
 #[cfg_attr(feature = "track_caller", track_caller)]
 fn assert_match(re: &str, text: &str) {
     let result = match_text(re, text);