Skip to content

Commit 0086dec

Browse files
committed
lite: fix stack overflow test
It turns out that we missed another case where the stack could overflow: dropping a deeply nested Hir. Namely, since we permit deeply nested Hirs to be constructed and only reject them after determining they are too deeply nested, they still then need to be dropped. We fix this by implementing a custom a Drop impl that uses the heap to traverse the Hir and drop things without using unbounded stack space. An alternative way to fix this would be to adjust the parser somehow to avoid building deeply nested Hir values in the first place. But that seems trickier, so we just stick with this for now.
1 parent 4ae1472 commit 0086dec

File tree

3 files changed

+65
-3
lines changed

3 files changed

+65
-3
lines changed

regex-lite/src/hir/mod.rs

+60
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,24 @@ impl Hir {
366366
}
367367
}
368368

369+
impl HirKind {
370+
/// Returns a slice of this kind's sub-expressions, if any.
371+
fn subs(&self) -> &[Hir] {
372+
use core::slice::from_ref;
373+
374+
match *self {
375+
HirKind::Empty
376+
| HirKind::Char(_)
377+
| HirKind::Class(_)
378+
| HirKind::Look(_) => &[],
379+
HirKind::Repetition(Repetition { ref sub, .. }) => from_ref(sub),
380+
HirKind::Capture(Capture { ref sub, .. }) => from_ref(sub),
381+
HirKind::Concat(ref subs) => subs,
382+
HirKind::Alternation(ref subs) => subs,
383+
}
384+
}
385+
}
386+
369387
#[derive(Clone, Debug, Eq, PartialEq)]
370388
pub(crate) struct Class {
371389
pub(crate) ranges: Vec<ClassRange>,
@@ -747,3 +765,45 @@ fn prev_char(ch: char) -> Option<char> {
747765
// and U+E000 yields a valid scalar value.
748766
Some(char::from_u32(u32::from(ch).checked_sub(1)?).unwrap())
749767
}
768+
769+
impl Drop for Hir {
770+
fn drop(&mut self) {
771+
use core::mem;
772+
773+
match *self.kind() {
774+
HirKind::Empty
775+
| HirKind::Char(_)
776+
| HirKind::Class(_)
777+
| HirKind::Look(_) => return,
778+
HirKind::Capture(ref x) if x.sub.kind.subs().is_empty() => return,
779+
HirKind::Repetition(ref x) if x.sub.kind.subs().is_empty() => {
780+
return
781+
}
782+
HirKind::Concat(ref x) if x.is_empty() => return,
783+
HirKind::Alternation(ref x) if x.is_empty() => return,
784+
_ => {}
785+
}
786+
787+
let mut stack = vec![mem::replace(self, Hir::empty())];
788+
while let Some(mut expr) = stack.pop() {
789+
match expr.kind {
790+
HirKind::Empty
791+
| HirKind::Char(_)
792+
| HirKind::Class(_)
793+
| HirKind::Look(_) => {}
794+
HirKind::Capture(ref mut x) => {
795+
stack.push(mem::replace(&mut x.sub, Hir::empty()));
796+
}
797+
HirKind::Repetition(ref mut x) => {
798+
stack.push(mem::replace(&mut x.sub, Hir::empty()));
799+
}
800+
HirKind::Concat(ref mut x) => {
801+
stack.extend(x.drain(..));
802+
}
803+
HirKind::Alternation(ref mut x) => {
804+
stack.extend(x.drain(..));
805+
}
806+
}
807+
}
808+
}
809+
}

regex-lite/src/hir/parse.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1328,8 +1328,10 @@ fn into_class_item_range(hir: Hir) -> Result<char, Error> {
13281328
}
13291329
}
13301330

1331-
fn into_class_item_ranges(hir: Hir) -> Result<Vec<hir::ClassRange>, Error> {
1332-
match hir.kind {
1331+
fn into_class_item_ranges(
1332+
mut hir: Hir,
1333+
) -> Result<Vec<hir::ClassRange>, Error> {
1334+
match core::mem::replace(&mut hir.kind, HirKind::Empty) {
13331335
HirKind::Char(ch) => Ok(vec![hir::ClassRange { start: ch, end: ch }]),
13341336
HirKind::Class(hir::Class { ranges }) => Ok(ranges),
13351337
_ => Err(Error::new(ERR_CLASS_INVALID_ITEM)),

regex-lite/tests/fuzz/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ fn captures_wrong_order_min() {
2727
#[test]
2828
fn many_zero_to_many_reps() {
2929
let pat = format!(".{}", "*".repeat(1 << 15));
30-
let Ok(re) = regex_lite::RegexBuilder::new(&pat).build() else { return };
30+
let Ok(re) = regex_lite::Regex::new(&pat) else { return };
3131
re.is_match("");
3232
}
3333

0 commit comments

Comments
 (0)