From b3ee398bfbc365c99e6089e20bf78b1e9604033e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 27 Jan 2026 02:25:41 +0000
Subject: [PATCH 1/2] Initial plan
From 877a18df6d2e41e10fc5526e178e36a34d64e774 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 27 Jan 2026 02:31:04 +0000
Subject: [PATCH 2/2] Fix search_slots panic with zero-repetition capture
groups
Co-authored-by: keith-hall <11882719+keith-hall@users.noreply.github.com>
---
regex-automata/src/dfa/onepass.rs | 17 ++++---
regex-automata/tests/dfa/onepass/mod.rs | 1 +
.../tests/dfa/onepass/regression.rs | 51 +++++++++++++++++++
3 files changed, 63 insertions(+), 6 deletions(-)
create mode 100644 regex-automata/tests/dfa/onepass/regression.rs
diff --git a/regex-automata/src/dfa/onepass.rs b/regex-automata/src/dfa/onepass.rs
index 85f820ef54..a56a462511 100644
--- a/regex-automata/src/dfa/onepass.rs
+++ b/regex-automata/src/dfa/onepass.rs
@@ -2215,11 +2215,16 @@ impl DFA {
// We *also* need to set any explicit slots that are active as part of
// the path to the match state.
if self.explicit_slot_start < slots.len() {
- // NOTE: The 'cache.explicit_slots()' slice is setup at the
- // beginning of every search such that it is guaranteed to return a
- // slice of length equivalent to 'slots[explicit_slot_start..]'.
- slots[self.explicit_slot_start..]
- .copy_from_slice(cache.explicit_slots());
+ // Copy only the slots that exist in the cache. When a regex has
+ // capture groups with zero repetition (e.g., (abc){0}), the cache
+ // may have fewer explicit slots than what the caller provided.
+ let cache_slots = cache.explicit_slots();
+ let available = core::cmp::min(
+ cache_slots.len(),
+ slots.len().saturating_sub(self.explicit_slot_start),
+ );
+ slots[self.explicit_slot_start..self.explicit_slot_start + available]
+ .copy_from_slice(&cache_slots[..available]);
epsilons.slots().apply(at, &mut slots[self.explicit_slot_start..]);
}
*matched_pid = Some(pid);
@@ -2577,7 +2582,7 @@ impl Cache {
}
fn setup_search(&mut self, explicit_slot_len: usize) {
- self.explicit_slot_len = explicit_slot_len;
+ self.explicit_slot_len = core::cmp::min(explicit_slot_len, self.explicit_slots.len());
}
}
diff --git a/regex-automata/tests/dfa/onepass/mod.rs b/regex-automata/tests/dfa/onepass/mod.rs
index 9d6ab475ef..aa656435d4 100644
--- a/regex-automata/tests/dfa/onepass/mod.rs
+++ b/regex-automata/tests/dfa/onepass/mod.rs
@@ -1,2 +1,3 @@
#[cfg(not(miri))]
mod suite;
+mod regression;
diff --git a/regex-automata/tests/dfa/onepass/regression.rs b/regex-automata/tests/dfa/onepass/regression.rs
new file mode 100644
index 0000000000..1968e26ea8
--- /dev/null
+++ b/regex-automata/tests/dfa/onepass/regression.rs
@@ -0,0 +1,51 @@
+// Regression test for zero-repetition capture groups causing panic.
+// See: https://github.com/rust-lang/regex/issues/XXX
+#[test]
+fn zero_repetition_capture_group() {
+ use regex_automata::{
+ dfa::onepass::DFA,
+ util::primitives::NonMaxUsize,
+ Anchored, Input,
+ };
+
+ let expr = DFA::new(r"(abc)(ABC){0}").unwrap();
+ let s = "abcABC";
+ let input = Input::new(s).span(0..s.len()).anchored(Anchored::Yes);
+
+ // Test with slot array sized for the pattern
+ let mut cache = expr.create_cache();
+ let mut slots: Vec