@@ -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,61 @@ 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+ /// # Example
564+ ///
565+ /// ```
566+ /// use regex_automata::meta::Regex;
567+ ///
568+ /// let re = Regex::builder()
569+ /// .configure(Regex::config().pikevm(false))
570+ /// .build(r"\b\w+\b")?;
571+ /// let mut cache = re.create_cache();
572+ ///
573+ /// assert_eq!(Ok(true), re.try_is_match_with(&mut cache, "quux"));
574+ /// // A Unicode word boundary against a non-ASCII haystack makes the lazy
575+ /// // DFA quit, but the backtracker still answers it.
576+ /// let haystack = "☃".repeat(100);
577+ /// assert_eq!(Ok(false), re.try_is_match_with(&mut cache, &haystack));
578+ /// // Past the backtracker's bound there is no engine left to run.
579+ /// let haystack = "☃".repeat(10_000);
580+ /// assert!(re.try_is_match_with(&mut cache, &haystack).is_err());
581+ ///
582+ /// # Ok::<(), Box<dyn std::error::Error>>(())
583+ /// ```
584+ #[ inline]
585+ pub fn try_is_match_with < ' h , I : Into < Input < ' h > > > (
586+ & self ,
587+ cache : & mut Cache ,
588+ input : I ,
589+ ) -> Result < bool , MatchError > {
590+ let input = input. into ( ) . earliest ( true ) ;
591+ if self . imp . info . is_impossible ( & input) {
592+ return Ok ( false ) ;
593+ }
594+ self . imp
595+ . strat
596+ . try_is_match ( cache, & input)
597+ . map_err ( |err| MatchError :: no_engine ( err. offset ( ) ) )
598+ }
599+
543600 /// Executes a leftmost search and returns the first match that is found,
544601 /// if one exists.
545602 ///
@@ -1257,6 +1314,30 @@ impl Regex {
12571314 self . imp . strat . search ( cache, input)
12581315 }
12591316
1317+ /// This is like [`Regex::search_with`], but returns an error instead of
1318+ /// falling back to the PikeVM.
1319+ ///
1320+ /// A `Regex` built with [`Config::pikevm`] disabled has no engine left
1321+ /// once the DFAs and the bounded engines have all given up on a search.
1322+ /// This routine reports that as a [`MatchError`], where the infallible
1323+ /// variants panic. A `Regex` built with the PikeVM enabled (the default)
1324+ /// never returns an error here.
1325+ pub fn try_search_with (
1326+ & self ,
1327+ cache : & mut Cache ,
1328+ input : & Input < ' _ > ,
1329+ ) -> Result < Option < Match > , MatchError > {
1330+ if self . imp . info . captures_disabled ( )
1331+ || self . imp . info . is_impossible ( input)
1332+ {
1333+ return Ok ( None ) ;
1334+ }
1335+ self . imp
1336+ . strat
1337+ . try_search ( cache, input)
1338+ . map_err ( |err| MatchError :: no_engine ( err. offset ( ) ) )
1339+ }
1340+
12601341 /// This is like [`Regex::search_half`], but requires the caller to
12611342 /// explicitly pass a [`Cache`].
12621343 ///
@@ -1301,6 +1382,30 @@ impl Regex {
13011382 self . imp . strat . search_half ( cache, input)
13021383 }
13031384
1385+ /// This is like [`Regex::search_half_with`], but returns an error instead of
1386+ /// falling back to the PikeVM.
1387+ ///
1388+ /// A `Regex` built with [`Config::pikevm`] disabled has no engine left
1389+ /// once the DFAs and the bounded engines have all given up on a search.
1390+ /// This routine reports that as a [`MatchError`], where the infallible
1391+ /// variants panic. A `Regex` built with the PikeVM enabled (the default)
1392+ /// never returns an error here.
1393+ pub fn try_search_half_with (
1394+ & self ,
1395+ cache : & mut Cache ,
1396+ input : & Input < ' _ > ,
1397+ ) -> Result < Option < HalfMatch > , MatchError > {
1398+ if self . imp . info . captures_disabled ( )
1399+ || self . imp . info . is_impossible ( input)
1400+ {
1401+ return Ok ( None ) ;
1402+ }
1403+ self . imp
1404+ . strat
1405+ . try_search_half ( cache, input)
1406+ . map_err ( |err| MatchError :: no_engine ( err. offset ( ) ) )
1407+ }
1408+
13041409 /// This is like [`Regex::search_captures`], but requires the caller to
13051410 /// explicitly pass a [`Cache`].
13061411 ///
@@ -1394,6 +1499,27 @@ impl Regex {
13941499 caps. set_pattern ( pid) ;
13951500 }
13961501
1502+ /// This is like [`Regex::search_captures_with`], but returns an error instead of
1503+ /// falling back to the PikeVM.
1504+ ///
1505+ /// A `Regex` built with [`Config::pikevm`] disabled has no engine left
1506+ /// once the DFAs and the bounded engines have all given up on a search.
1507+ /// This routine reports that as a [`MatchError`], where the infallible
1508+ /// variants panic. A `Regex` built with the PikeVM enabled (the default)
1509+ /// never returns an error here.
1510+ pub fn try_search_captures_with (
1511+ & self ,
1512+ cache : & mut Cache ,
1513+ input : & Input < ' _ > ,
1514+ caps : & mut Captures ,
1515+ ) -> Result < ( ) , MatchError > {
1516+ caps. set_pattern ( None ) ;
1517+ let pid =
1518+ self . try_search_slots_with ( cache, input, caps. slots_mut ( ) ) ?;
1519+ caps. set_pattern ( pid) ;
1520+ Ok ( ( ) )
1521+ }
1522+
13971523 /// This is like [`Regex::search_slots`], but requires the caller to
13981524 /// explicitly pass a [`Cache`].
13991525 ///
@@ -1456,6 +1582,31 @@ impl Regex {
14561582 self . imp . strat . search_slots ( cache, input, slots)
14571583 }
14581584
1585+ /// This is like [`Regex::search_slots_with`], but returns an error instead of
1586+ /// falling back to the PikeVM.
1587+ ///
1588+ /// A `Regex` built with [`Config::pikevm`] disabled has no engine left
1589+ /// once the DFAs and the bounded engines have all given up on a search.
1590+ /// This routine reports that as a [`MatchError`], where the infallible
1591+ /// variants panic. A `Regex` built with the PikeVM enabled (the default)
1592+ /// never returns an error here.
1593+ pub fn try_search_slots_with (
1594+ & self ,
1595+ cache : & mut Cache ,
1596+ input : & Input < ' _ > ,
1597+ slots : & mut [ Option < NonMaxUsize > ] ,
1598+ ) -> Result < Option < PatternID > , MatchError > {
1599+ if self . imp . info . captures_disabled ( )
1600+ || self . imp . info . is_impossible ( input)
1601+ {
1602+ return Ok ( None ) ;
1603+ }
1604+ self . imp
1605+ . strat
1606+ . try_search_slots ( cache, input, slots)
1607+ . map_err ( |err| MatchError :: no_engine ( err. offset ( ) ) )
1608+ }
1609+
14591610 /// This is like [`Regex::which_overlapping_matches`], but requires the
14601611 /// caller to explicitly pass a [`Cache`].
14611612 ///
@@ -1504,6 +1655,29 @@ impl Regex {
15041655 }
15051656 self . imp . strat . which_overlapping_matches ( cache, input, patset)
15061657 }
1658+
1659+ /// This is like [`Regex::which_overlapping_matches_with`], but returns an error instead of
1660+ /// falling back to the PikeVM.
1661+ ///
1662+ /// A `Regex` built with [`Config::pikevm`] disabled has no engine left
1663+ /// once the DFAs and the bounded engines have all given up on a search.
1664+ /// This routine reports that as a [`MatchError`], where the infallible
1665+ /// variants panic. A `Regex` built with the PikeVM enabled (the default)
1666+ /// never returns an error here.
1667+ pub fn try_which_overlapping_matches_with (
1668+ & self ,
1669+ cache : & mut Cache ,
1670+ input : & Input < ' _ > ,
1671+ patset : & mut PatternSet ,
1672+ ) -> Result < ( ) , MatchError > {
1673+ if self . imp . info . is_impossible ( input) {
1674+ return Ok ( ( ) ) ;
1675+ }
1676+ self . imp
1677+ . strat
1678+ . try_which_overlapping_matches ( cache, input, patset)
1679+ . map_err ( |err| MatchError :: no_engine ( err. offset ( ) ) )
1680+ }
15071681}
15081682
15091683/// Various non-search routines for querying properties of a `Regex` and
@@ -2481,6 +2655,8 @@ pub struct Config {
24812655 dfa_state_limit : Option < Option < usize > > ,
24822656 onepass : Option < bool > ,
24832657 backtrack : Option < bool > ,
2658+ backtrack_visited_capacity : Option < usize > ,
2659+ pikevm : Option < bool > ,
24842660 byte_classes : Option < bool > ,
24852661 line_terminator : Option < u8 > ,
24862662}
@@ -3097,6 +3273,39 @@ impl Config {
30973273 Config { backtrack : Some ( yes) , ..self }
30983274 }
30993275
3276+ /// Sets the visited capacity, in bytes, used by the bounded backtracker.
3277+ ///
3278+ /// The backtracker refuses to run when the haystack is longer than its
3279+ /// visited bitset can track, which is roughly this capacity divided by the
3280+ /// number of NFA states. Raising it lets the backtracker serve longer
3281+ /// haystacks, which matters when [`Config::pikevm`] is disabled and the
3282+ /// backtracker is the last engine standing.
3283+ ///
3284+ /// When unset, the backtracker's own default is used.
3285+ pub fn backtrack_visited_capacity ( self , capacity : usize ) -> Config {
3286+ Config { backtrack_visited_capacity : Some ( capacity) , ..self }
3287+ }
3288+
3289+ /// Whether to permit the use of the PikeVM.
3290+ ///
3291+ /// The PikeVM is the meta regex engine's engine of last resort: it can
3292+ /// handle any regex against any haystack, but its NFA simulation can be
3293+ /// orders of magnitude slower than the DFAs. It is what makes the
3294+ /// infallible search routines infallible.
3295+ ///
3296+ /// Disabling it turns this into a regex engine that can fail: a search
3297+ /// that the faster engines give up on has nothing left to run it. The
3298+ /// `try_` search routines report that condition as a [`MatchError`] with
3299+ /// a [`MatchErrorKind::NoEngine`](crate::MatchErrorKind::NoEngine) kind,
3300+ /// while the infallible routines panic. This is useful for rejecting a
3301+ /// pathological regex outright rather than paying for it.
3302+ ///
3303+ /// This is enabled by default, and when it is, no search routine can
3304+ /// fail or panic.
3305+ pub fn pikevm ( self , yes : bool ) -> Config {
3306+ Config { pikevm : Some ( yes) , ..self }
3307+ }
3308+
31003309 /// Returns the match kind on this configuration, as set by
31013310 /// [`Config::match_kind`].
31023311 ///
@@ -3270,6 +3479,18 @@ impl Config {
32703479 }
32713480 }
32723481
3482+ /// Returns the backtracker's visited capacity, as set by
3483+ /// [`Config::backtrack_visited_capacity`]. `None` means the backtracker's
3484+ /// own default is used.
3485+ pub fn get_backtrack_visited_capacity ( & self ) -> Option < usize > {
3486+ self . backtrack_visited_capacity
3487+ }
3488+
3489+ /// Returns whether the PikeVM may be used, as set by [`Config::pikevm`].
3490+ pub fn get_pikevm ( & self ) -> bool {
3491+ self . pikevm . unwrap_or ( true )
3492+ }
3493+
32733494 /// Returns a "baseline" Thompson configuration for constructing NFAs based
32743495 /// on this configuration.
32753496 ///
@@ -3315,6 +3536,10 @@ impl Config {
33153536 dfa_state_limit : o. dfa_state_limit . or ( self . dfa_state_limit ) ,
33163537 onepass : o. onepass . or ( self . onepass ) ,
33173538 backtrack : o. backtrack . or ( self . backtrack ) ,
3539+ backtrack_visited_capacity : o
3540+ . backtrack_visited_capacity
3541+ . or ( self . backtrack_visited_capacity ) ,
3542+ pikevm : o. pikevm . or ( self . pikevm ) ,
33183543 byte_classes : o. byte_classes . or ( self . byte_classes ) ,
33193544 line_terminator : o. line_terminator . or ( self . line_terminator ) ,
33203545 }
0 commit comments