Skip to content

Commit 848a784

Browse files
committed
automata: make the PikeVM optional in the meta engine
1 parent 2b52759 commit 848a784

4 files changed

Lines changed: 448 additions & 179 deletions

File tree

‎regex-automata/src/meta/error.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ impl RetryFailError {
205205
pub(crate) fn from_offset(offset: usize) -> RetryFailError {
206206
RetryFailError { offset }
207207
}
208+
209+
pub(crate) fn offset(&self) -> usize {
210+
self.offset
211+
}
208212
}
209213

210214
#[cfg(feature = "std")]

‎regex-automata/src/meta/regex.rs‎

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ use crate::{
2424
pool::{Pool, PoolGuard},
2525
prefilter::Prefilter,
2626
primitives::{NonMaxUsize, PatternID},
27-
search::{HalfMatch, Input, Match, MatchKind, PatternSet, Span},
27+
search::{
28+
HalfMatch, Input, Match, MatchError, MatchKind, PatternSet, Span,
29+
},
2830
},
2931
};
3032

@@ -540,6 +542,63 @@ impl Regex {
540542
result
541543
}
542544

545+
/// Executes a leftmost is-match search, returning an error instead of
546+
/// falling back to the PikeVM.
547+
///
548+
/// This is like [`Regex::is_match`], but for a `Regex` built with
549+
/// [`Config::pikevm`] disabled. The meta regex engine's fast engines (the
550+
/// DFAs) can give up on a search, either because a lazy DFA's cache is
551+
/// being used ineffectively or because a Unicode word boundary was seen
552+
/// alongside a non-ASCII byte. The engines with bounded runtime (the
553+
/// one-pass DFA and the bounded backtracker) still pick those up, and with
554+
/// the PikeVM disabled the backtracker is given every search it can serve
555+
/// rather than only the short haystacks it is normally reserved for. Only
556+
/// once nothing is left to run does this routine report a [`MatchError`],
557+
/// which is to say only when the haystack outgrows the backtracker's
558+
/// visited bitset (see [`Config::backtrack_visited_capacity`]).
559+
///
560+
/// A `Regex` built with the PikeVM enabled (the default) never returns an
561+
/// error here.
562+
///
563+
/// This is a `topk-io/regex` fork addition.
564+
///
565+
/// # Example
566+
///
567+
/// ```
568+
/// use regex_automata::meta::Regex;
569+
///
570+
/// let re = Regex::builder()
571+
/// .configure(Regex::config().pikevm(false))
572+
/// .build(r"\b\w+\b")?;
573+
/// let mut cache = re.create_cache();
574+
///
575+
/// assert_eq!(Ok(true), re.try_is_match_with(&mut cache, "quux"));
576+
/// // A Unicode word boundary against a non-ASCII haystack makes the lazy
577+
/// // DFA quit, but the backtracker still answers it.
578+
/// let haystack = "☃".repeat(100);
579+
/// assert_eq!(Ok(false), re.try_is_match_with(&mut cache, &haystack));
580+
/// // Past the backtracker's bound there is no engine left to run.
581+
/// let haystack = "☃".repeat(10_000);
582+
/// assert!(re.try_is_match_with(&mut cache, &haystack).is_err());
583+
///
584+
/// # Ok::<(), Box<dyn std::error::Error>>(())
585+
/// ```
586+
#[inline]
587+
pub fn try_is_match_with<'h, I: Into<Input<'h>>>(
588+
&self,
589+
cache: &mut Cache,
590+
input: I,
591+
) -> Result<bool, MatchError> {
592+
let input = input.into().earliest(true);
593+
if self.imp.info.is_impossible(&input) {
594+
return Ok(false);
595+
}
596+
self.imp
597+
.strat
598+
.try_is_match(cache, &input)
599+
.map_err(|err| MatchError::gave_up(err.offset()))
600+
}
601+
543602
/// Executes a leftmost search and returns the first match that is found,
544603
/// if one exists.
545604
///
@@ -2481,6 +2540,8 @@ pub struct Config {
24812540
dfa_state_limit: Option<Option<usize>>,
24822541
onepass: Option<bool>,
24832542
backtrack: Option<bool>,
2543+
backtrack_visited_capacity: Option<usize>,
2544+
pikevm: Option<bool>,
24842545
byte_classes: Option<bool>,
24852546
line_terminator: Option<u8>,
24862547
}
@@ -3097,6 +3158,41 @@ impl Config {
30973158
Config { backtrack: Some(yes), ..self }
30983159
}
30993160

3161+
/// Sets the visited capacity, in bytes, used by the bounded backtracker.
3162+
///
3163+
/// The backtracker refuses to run when the haystack is longer than its
3164+
/// visited bitset can track, which is roughly this capacity divided by the
3165+
/// number of NFA states. Raising it lets the backtracker serve longer
3166+
/// haystacks, which matters when [`Config::pikevm`] is disabled and the
3167+
/// backtracker is the last engine standing.
3168+
///
3169+
/// When unset, the backtracker's own default is used.
3170+
///
3171+
/// This is a `topk-io/regex` fork addition.
3172+
pub fn backtrack_visited_capacity(self, capacity: usize) -> Config {
3173+
Config { backtrack_visited_capacity: Some(capacity), ..self }
3174+
}
3175+
3176+
/// Whether to permit the use of the PikeVM.
3177+
///
3178+
/// The PikeVM is the meta regex engine's engine of last resort: it can
3179+
/// handle any regex against any haystack, but its NFA simulation can be
3180+
/// orders of magnitude slower than the DFAs. Disabling it means a search
3181+
/// that the faster engines give up on has nothing left to fall back to.
3182+
/// Only [`Regex::try_is_match_with`] can report that condition, by
3183+
/// returning a [`MatchError`]; the other search routines require the
3184+
/// PikeVM and will panic if it is disabled.
3185+
///
3186+
/// This is useful for rejecting a pathological regex outright rather than
3187+
/// paying for it.
3188+
///
3189+
/// This is enabled by default.
3190+
///
3191+
/// This is a `topk-io/regex` fork addition.
3192+
pub fn pikevm(self, yes: bool) -> Config {
3193+
Config { pikevm: Some(yes), ..self }
3194+
}
3195+
31003196
/// Returns the match kind on this configuration, as set by
31013197
/// [`Config::match_kind`].
31023198
///
@@ -3270,6 +3366,22 @@ impl Config {
32703366
}
32713367
}
32723368

3369+
/// Returns the backtracker's visited capacity, as set by
3370+
/// [`Config::backtrack_visited_capacity`]. `None` means the backtracker's
3371+
/// own default is used.
3372+
///
3373+
/// This is a `topk-io/regex` fork addition.
3374+
pub fn get_backtrack_visited_capacity(&self) -> Option<usize> {
3375+
self.backtrack_visited_capacity
3376+
}
3377+
3378+
/// Returns whether the PikeVM may be used, as set by [`Config::pikevm`].
3379+
///
3380+
/// This is a `topk-io/regex` fork addition.
3381+
pub fn get_pikevm(&self) -> bool {
3382+
self.pikevm.unwrap_or(true)
3383+
}
3384+
32733385
/// Returns a "baseline" Thompson configuration for constructing NFAs based
32743386
/// on this configuration.
32753387
///
@@ -3315,6 +3427,10 @@ impl Config {
33153427
dfa_state_limit: o.dfa_state_limit.or(self.dfa_state_limit),
33163428
onepass: o.onepass.or(self.onepass),
33173429
backtrack: o.backtrack.or(self.backtrack),
3430+
backtrack_visited_capacity: o
3431+
.backtrack_visited_capacity
3432+
.or(self.backtrack_visited_capacity),
3433+
pikevm: o.pikevm.or(self.pikevm),
33183434
byte_classes: o.byte_classes.or(self.byte_classes),
33193435
line_terminator: o.line_terminator.or(self.line_terminator),
33203436
}

0 commit comments

Comments
 (0)