Skip to main content

regex_automata/meta/
strategy.rs

1use core::{
2    fmt::Debug,
3    panic::{RefUnwindSafe, UnwindSafe},
4};
5
6use alloc::{borrow::Cow, format, sync::Arc};
7
8use regex_syntax::hir::{literal, Hir};
9
10use crate::{
11    meta::{
12        error::{BuildError, RetryError, RetryFailError, RetryQuadraticError},
13        regex::{Cache, RegexInfo},
14        reverse_inner, reverse_suffix, wrappers,
15    },
16    nfa::thompson::{self, WhichCaptures, NFA},
17    util::{
18        captures::{Captures, GroupInfo},
19        look::LookMatcher,
20        prefilter::{self, Prefilter, PrefilterI},
21        primitives::{NonMaxUsize, PatternID},
22        search::{Anchored, HalfMatch, Input, Match, MatchKind, PatternSet},
23    },
24};
25
26/// A trait that represents a single meta strategy. Its main utility is in
27/// providing a way to do dynamic dispatch over a few choices.
28///
29/// Why dynamic dispatch? I actually don't have a super compelling reason, and
30/// importantly, I have not benchmarked it with the main alternative: an enum.
31/// I went with dynamic dispatch initially because the regex engine search code
32/// really can't be inlined into caller code in most cases because it's just
33/// too big. In other words, it is already expected that every regex search
34/// will entail at least the cost of a function call.
35///
36/// I do wonder whether using enums would result in better codegen overall
37/// though. It's a worthwhile experiment to try. Probably the most interesting
38/// benchmark to run in such a case would be one with a high match count. That
39/// is, a benchmark to test the overall latency of a search call.
40pub(super) trait Strategy:
41    Debug + Send + Sync + RefUnwindSafe + UnwindSafe + 'static
42{
43    #[allow(dead_code)]
44    fn name(&self) -> Cow<'static, str>;
45
46    fn group_info(&self) -> &GroupInfo;
47
48    fn create_cache(&self) -> Cache;
49
50    fn reset_cache(&self, cache: &mut Cache);
51
52    fn is_accelerated(&self) -> bool;
53
54    fn memory_usage(&self) -> usize;
55
56    fn search(&self, cache: &mut Cache, input: &Input<'_>) -> Option<Match>;
57
58    fn search_half(
59        &self,
60        cache: &mut Cache,
61        input: &Input<'_>,
62    ) -> Option<HalfMatch>;
63
64    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool;
65
66    fn search_slots(
67        &self,
68        cache: &mut Cache,
69        input: &Input<'_>,
70        slots: &mut [Option<NonMaxUsize>],
71    ) -> Option<PatternID>;
72
73    fn which_overlapping_matches(
74        &self,
75        cache: &mut Cache,
76        input: &Input<'_>,
77        patset: &mut PatternSet,
78    );
79}
80
81pub(super) fn new(
82    info: &RegexInfo,
83    hirs: &[&Hir],
84) -> Result<Arc<dyn Strategy>, BuildError> {
85    // At this point, we're committed to a regex engine of some kind. So pull
86    // out a prefilter if we can, which will feed to each of the constituent
87    // regex engines.
88    let pre = if info.is_always_anchored_start() {
89        // PERF: I'm not sure we necessarily want to do this... We may want to
90        // run a prefilter for quickly rejecting in some cases. The problem
91        // is that anchored searches overlap quite a bit with the use case
92        // of "run a regex on every line to extract data." In that case, the
93        // regex always matches, so running a prefilter doesn't really help us
94        // there. The main place where a prefilter helps in an anchored search
95        // is if the anchored search is not expected to match frequently. That
96        // is, the prefilter gives us a way to possibly reject a haystack very
97        // quickly.
98        //
99        // Maybe we should do use a prefilter, but only for longer haystacks?
100        // Or maybe we should only use a prefilter when we think it's "fast"?
101        //
102        // Interestingly, I think we currently lack the infrastructure for
103        // disabling a prefilter based on haystack length. That would probably
104        // need to be a new 'Input' option. (Interestingly, an 'Input' used to
105        // carry a 'Prefilter' with it, but I moved away from that.)
106        debug!("skipping literal extraction since regex is anchored");
107        None
108    } else if let Some(pre) = info.config().get_prefilter() {
109        debug!(
110            "skipping literal extraction since the caller provided a prefilter"
111        );
112        Some(pre.clone())
113    } else if info.config().get_auto_prefilter() {
114        let kind = info.config().get_match_kind();
115        let prefixes = crate::util::prefilter::prefixes(kind, hirs);
116        // If we can build a full `Strategy` from just the extracted prefixes,
117        // then we can short-circuit and avoid building a regex engine at all.
118        if let Some(pre) = Pre::from_prefixes(info, &prefixes) {
119            debug!(
120                "found that the regex can be broken down to a literal \
121                 search, avoiding the regex engine entirely",
122            );
123            debug!("using {} strategy", pre.name());
124            return Ok(pre);
125        }
126        // This now attempts another short-circuit of the regex engine: if we
127        // have a huge alternation of just plain literals, then we can just use
128        // Aho-Corasick for that and avoid the regex engine entirely.
129        //
130        // You might think this case would just be handled by
131        // `Pre::from_prefixes`, but that technique relies on heuristic literal
132        // extraction from the corresponding `Hir`. That works, but part of
133        // heuristics limit the size and number of literals returned. This case
134        // will specifically handle patterns with very large alternations.
135        //
136        // One wonders if we should just roll this our heuristic literal
137        // extraction, and then I think this case could disappear entirely.
138        if let Some(pre) = Pre::from_alternation_literals(info, hirs) {
139            debug!(
140                "found plain alternation of literals, \
141                 avoiding regex engine entirely and using Aho-Corasick"
142            );
143            debug!("using {} strategy", pre.name());
144            return Ok(pre);
145        }
146        prefixes.literals().and_then(|strings| {
147            debug!(
148                "creating prefilter from {} literals: {:?}",
149                strings.len(),
150                strings,
151            );
152            Prefilter::new(kind, strings)
153        })
154    } else {
155        debug!("skipping literal extraction since prefilters were disabled");
156        None
157    };
158    let mut core = Core::new(info.clone(), pre.clone(), hirs)?;
159    // Now that we have our core regex engines built, there are a few cases
160    // where we can do a little bit better than just a normal "search forward
161    // and maybe use a prefilter when in a start state." However, these cases
162    // may not always work or otherwise build on top of the Core searcher.
163    // For example, the reverse anchored optimization seems like it might
164    // always work, but only the DFAs support reverse searching and the DFAs
165    // might give up or quit for reasons. If we had, e.g., a PikeVM that
166    // supported reverse searching, then we could avoid building a full Core
167    // engine for this case.
168    core = match ReverseAnchored::new(core) {
169        Err(core) => core,
170        Ok(ra) => {
171            debug!("using {} strategy", ra.name());
172            return Ok(Arc::new(ra));
173        }
174    };
175    core = match ReverseSuffix::new(core, hirs) {
176        Err(core) => core,
177        Ok(rs) => {
178            debug!("using {} strategy", rs.name());
179            return Ok(Arc::new(rs));
180        }
181    };
182    core = match ReverseInner::new(core, hirs) {
183        Err(core) => core,
184        Ok(ri) => {
185            debug!("using {} strategy", ri.name());
186            return Ok(Arc::new(ri));
187        }
188    };
189    debug!("using {} strategy", core.name());
190    Ok(Arc::new(core))
191}
192
193#[derive(Clone, Debug)]
194struct Pre<P> {
195    pre: P,
196    group_info: GroupInfo,
197}
198
199impl<P: PrefilterI> Pre<P> {
200    fn new(pre: P) -> Arc<dyn Strategy> {
201        // The only thing we support when we use prefilters directly as a
202        // strategy is the start and end of the overall match for a single
203        // pattern. In other words, exactly one implicit capturing group. Which
204        // is exactly what we use here for a GroupInfo.
205        let group_info = GroupInfo::new([[None::<&str>]]).unwrap();
206        Arc::new(Pre { pre, group_info })
207    }
208}
209
210// This is a little weird, but we don't actually care about the type parameter
211// here because we're selecting which underlying prefilter to use. So we just
212// define it on an arbitrary type.
213impl Pre<()> {
214    /// Given a sequence of prefixes, attempt to return a full `Strategy` using
215    /// just the prefixes.
216    ///
217    /// Basically, this occurs when the prefixes given not just prefixes,
218    /// but an enumeration of the entire language matched by the regular
219    /// expression.
220    ///
221    /// A number of other conditions need to be true too. For example, there
222    /// can be only one pattern, the number of explicit capture groups is 0, no
223    /// look-around assertions and so on.
224    ///
225    /// Note that this ignores `Config::get_auto_prefilter` because if this
226    /// returns something, then it isn't a prefilter but a matcher itself.
227    /// Therefore, it shouldn't suffer from the problems typical to prefilters
228    /// (such as a high false positive rate).
229    fn from_prefixes(
230        info: &RegexInfo,
231        prefixes: &literal::Seq,
232    ) -> Option<Arc<dyn Strategy>> {
233        let kind = info.config().get_match_kind();
234        // Check to see if our prefixes are exact, which means we might be
235        // able to bypass the regex engine entirely and just rely on literal
236        // searches.
237        if !prefixes.is_exact() {
238            return None;
239        }
240        // We also require that we have a single regex pattern. Namely,
241        // we reuse the prefilter infrastructure to implement search and
242        // prefilters only report spans. Prefilters don't know about pattern
243        // IDs. The multi-regex case isn't a lost cause, we might still use
244        // Aho-Corasick and we might still just use a regular prefilter, but
245        // that's done below.
246        if info.pattern_len() != 1 {
247            return None;
248        }
249        // We can't have any capture groups either. The literal engines don't
250        // know how to deal with things like '(foo)(bar)'. In that case, a
251        // prefilter will just be used and then the regex engine will resolve
252        // the capture groups.
253        if info.props()[0].explicit_captures_len() != 0 {
254            return None;
255        }
256        // We also require that it has zero look-around assertions. Namely,
257        // literal extraction treats look-around assertions as if they match
258        // *every* empty string. But of course, that isn't true. So for
259        // example, 'foo\bquux' never matches anything, but 'fooquux' is
260        // extracted from that as an exact literal. Such cases should just run
261        // the regex engine. 'fooquux' will be used as a normal prefilter, and
262        // then the regex engine will try to look for an actual match.
263        if !info.props()[0].look_set().is_empty() {
264            return None;
265        }
266        // Finally, currently, our prefilters are all oriented around
267        // leftmost-first match semantics, so don't try to use them if the
268        // caller asked for anything else.
269        if kind != MatchKind::LeftmostFirst {
270            return None;
271        }
272        // The above seems like a lot of requirements to meet, but it applies
273        // to a lot of cases. 'foo', '[abc][123]' and 'foo|bar|quux' all meet
274        // the above criteria, for example.
275        //
276        // Note that this is effectively a latency optimization. If we didn't
277        // do this, then the extracted literals would still get bundled into
278        // a prefilter, and every regex engine capable of running unanchored
279        // searches supports prefilters. So this optimization merely sidesteps
280        // having to run the regex engine at all to confirm the match. Thus, it
281        // decreases the latency of a match.
282
283        // OK because we know the set is exact and thus finite.
284        let prefixes = prefixes.literals().unwrap();
285        debug!(
286            "trying to bypass regex engine by creating \
287             prefilter from {} literals: {:?}",
288            prefixes.len(),
289            prefixes,
290        );
291        let choice = match prefilter::Choice::new(kind, prefixes) {
292            Some(choice) => choice,
293            None => {
294                debug!(
295                    "regex bypass failed because no prefilter could be built"
296                );
297                return None;
298            }
299        };
300        let strat: Arc<dyn Strategy> = match choice {
301            prefilter::Choice::Memchr(pre) => Pre::new(pre),
302            prefilter::Choice::Memchr2(pre) => Pre::new(pre),
303            prefilter::Choice::Memchr3(pre) => Pre::new(pre),
304            prefilter::Choice::Memmem(pre) => Pre::new(pre),
305            prefilter::Choice::Teddy(pre) => Pre::new(pre),
306            prefilter::Choice::ByteSet(pre) => Pre::new(pre),
307            prefilter::Choice::AhoCorasick(pre) => Pre::new(pre),
308        };
309        Some(strat)
310    }
311
312    /// Attempts to extract an alternation of literals, and if it's deemed
313    /// worth doing, returns an Aho-Corasick prefilter as a strategy.
314    ///
315    /// And currently, this only returns something when 'hirs.len() == 1'. This
316    /// could in theory do something if there are multiple HIRs where all of
317    /// them are alternation of literals, but I haven't had the time to go down
318    /// that path yet.
319    fn from_alternation_literals(
320        info: &RegexInfo,
321        hirs: &[&Hir],
322    ) -> Option<Arc<dyn Strategy>> {
323        use crate::util::prefilter::AhoCorasick;
324
325        let lits = crate::meta::literal::alternation_literals(info, hirs)?;
326        let ac = AhoCorasick::new(MatchKind::LeftmostFirst, &lits)?;
327        Some(Pre::new(ac))
328    }
329}
330
331// This implements Strategy for anything that implements PrefilterI.
332//
333// Note that this must only be used for regexes of length 1. Multi-regexes
334// don't work here. The prefilter interface only provides the span of a match
335// and not the pattern ID. (I did consider making it more expressive, but I
336// couldn't figure out how to tie everything together elegantly.) Thus, so long
337// as the regex only contains one pattern, we can simply assume that a match
338// corresponds to PatternID::ZERO. And indeed, that's what we do here.
339//
340// In practice, since this impl is used to report matches directly and thus
341// completely bypasses the regex engine, we only wind up using this under the
342// following restrictions:
343//
344// * There must be only one pattern. As explained above.
345// * The literal sequence must be finite and only contain exact literals.
346// * There must not be any look-around assertions. If there are, the literals
347// extracted might be exact, but a match doesn't necessarily imply an overall
348// match. As a trivial example, 'foo\bbar' does not match 'foobar'.
349// * The pattern must not have any explicit capturing groups. If it does, the
350// caller might expect them to be resolved. e.g., 'foo(bar)'.
351//
352// So when all of those things are true, we use a prefilter directly as a
353// strategy.
354//
355// In the case where the number of patterns is more than 1, we don't use this
356// but do use a special Aho-Corasick strategy if all of the regexes are just
357// simple literals or alternations of literals. (We also use the Aho-Corasick
358// strategy when len(patterns)==1 if the number of literals is large. In that
359// case, literal extraction gives up and will return an infinite set.)
360impl<P: PrefilterI> Strategy for Pre<P> {
361    fn name(&self) -> Cow<'static, str> {
362        Cow::Owned(format!("prefilter {}", self.pre.name()))
363    }
364
365    #[cfg_attr(feature = "perf-inline", inline(always))]
366    fn group_info(&self) -> &GroupInfo {
367        &self.group_info
368    }
369
370    fn create_cache(&self) -> Cache {
371        Cache {
372            capmatches: Captures::all(self.group_info().clone()),
373            pikevm: wrappers::PikeVMCache::none(),
374            backtrack: wrappers::BoundedBacktrackerCache::none(),
375            onepass: wrappers::OnePassCache::none(),
376            hybrid: wrappers::HybridCache::none(),
377            revhybrid: wrappers::ReverseHybridCache::none(),
378        }
379    }
380
381    fn reset_cache(&self, _cache: &mut Cache) {}
382
383    fn is_accelerated(&self) -> bool {
384        self.pre.is_fast()
385    }
386
387    fn memory_usage(&self) -> usize {
388        self.pre.memory_usage()
389    }
390
391    #[cfg_attr(feature = "perf-inline", inline(always))]
392    fn search(&self, _cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
393        if input.is_done() {
394            return None;
395        }
396        if input.get_anchored().is_anchored() {
397            return self
398                .pre
399                .prefix(input.haystack(), input.get_span())
400                .map(|sp| Match::new(PatternID::ZERO, sp));
401        }
402        self.pre
403            .find(input.haystack(), input.get_span())
404            .map(|sp| Match::new(PatternID::ZERO, sp))
405    }
406
407    #[cfg_attr(feature = "perf-inline", inline(always))]
408    fn search_half(
409        &self,
410        cache: &mut Cache,
411        input: &Input<'_>,
412    ) -> Option<HalfMatch> {
413        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
414    }
415
416    #[cfg_attr(feature = "perf-inline", inline(always))]
417    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
418        self.search(cache, input).is_some()
419    }
420
421    #[cfg_attr(feature = "perf-inline", inline(always))]
422    fn search_slots(
423        &self,
424        cache: &mut Cache,
425        input: &Input<'_>,
426        slots: &mut [Option<NonMaxUsize>],
427    ) -> Option<PatternID> {
428        let m = self.search(cache, input)?;
429        if let Some(slot) = slots.get_mut(0) {
430            *slot = NonMaxUsize::new(m.start());
431        }
432        if let Some(slot) = slots.get_mut(1) {
433            *slot = NonMaxUsize::new(m.end());
434        }
435        Some(m.pattern())
436    }
437
438    #[cfg_attr(feature = "perf-inline", inline(always))]
439    fn which_overlapping_matches(
440        &self,
441        cache: &mut Cache,
442        input: &Input<'_>,
443        patset: &mut PatternSet,
444    ) {
445        if self.search(cache, input).is_some() {
446            patset.insert(PatternID::ZERO);
447        }
448    }
449}
450
451#[derive(Debug)]
452struct Core {
453    info: RegexInfo,
454    pre: Option<Prefilter>,
455    nfa: NFA,
456    nfarev: Option<NFA>,
457    pikevm: wrappers::PikeVM,
458    backtrack: wrappers::BoundedBacktracker,
459    onepass: wrappers::OnePass,
460    hybrid: wrappers::Hybrid,
461    dfa: wrappers::DFA,
462}
463
464impl Core {
465    fn new(
466        info: RegexInfo,
467        pre: Option<Prefilter>,
468        hirs: &[&Hir],
469    ) -> Result<Core, BuildError> {
470        let mut lookm = LookMatcher::new();
471        lookm.set_line_terminator(info.config().get_line_terminator());
472        let thompson_config = info.config().to_thompson_config();
473        let nfa = thompson::Compiler::new()
474            .configure(thompson_config.clone())
475            .build_many_from_hir(hirs)
476            .map_err(BuildError::nfa)?;
477        // It's possible for the PikeVM or the BB to fail to build, even though
478        // at this point, we already have a full NFA in hand. They can fail
479        // when a Unicode word boundary is used but where Unicode word boundary
480        // support is disabled at compile time, thus making it impossible to
481        // match. (Construction can also fail if the NFA was compiled without
482        // captures, but we always enable that above.)
483        let pikevm = wrappers::PikeVM::new(&info, pre.clone(), &nfa)?;
484        let backtrack =
485            wrappers::BoundedBacktracker::new(&info, pre.clone(), &nfa)?;
486        // The onepass engine can of course fail to build, but we expect it to
487        // fail in many cases because it is an optimization that doesn't apply
488        // to all regexes. The 'OnePass' wrapper encapsulates this failure (and
489        // logs a message if it occurs).
490        let onepass = wrappers::OnePass::new(&info, &nfa);
491        // We try to encapsulate whether a particular regex engine should be
492        // used within each respective wrapper, but the DFAs need a reverse NFA
493        // to build itself, and we really do not want to build a reverse NFA if
494        // we know we aren't going to use the lazy DFA. So we do a config check
495        // up front, which is in practice the only way we won't try to use the
496        // DFA.
497        let (nfarev, hybrid, dfa) =
498            if !info.config().get_hybrid() && !info.config().get_dfa() {
499                (None, wrappers::Hybrid::none(), wrappers::DFA::none())
500            } else {
501                // FIXME: Technically, we don't quite yet KNOW that we need
502                // a reverse NFA. It's possible for the DFAs below to both
503                // fail to build just based on the forward NFA. In which case,
504                // building the reverse NFA was totally wasted work. But...
505                // fixing this requires breaking DFA construction apart into
506                // two pieces: one for the forward part and another for the
507                // reverse part. Quite annoying. Making it worse, when building
508                // both DFAs fails, it's quite likely that the NFA is large and
509                // that it will take quite some time to build the reverse NFA
510                // too. So... it's really probably worth it to do this!
511                let nfarev = thompson::Compiler::new()
512                    // Currently, reverse NFAs don't support capturing groups,
513                    // so we MUST disable them. But even if we didn't have to,
514                    // we would, because nothing in this crate does anything
515                    // useful with capturing groups in reverse. And of course,
516                    // the lazy DFA ignores capturing groups in all cases.
517                    .configure(
518                        thompson_config
519                            .which_captures(WhichCaptures::None)
520                            .reverse(true),
521                    )
522                    .build_many_from_hir(hirs)
523                    .map_err(BuildError::nfa)?;
524                let dfa = if !info.config().get_dfa() {
525                    wrappers::DFA::none()
526                } else {
527                    wrappers::DFA::new(&info, pre.clone(), &nfa, &nfarev)
528                };
529                let hybrid = if !info.config().get_hybrid() {
530                    wrappers::Hybrid::none()
531                } else if dfa.is_some() {
532                    debug!("skipping lazy DFA because we have a full DFA");
533                    wrappers::Hybrid::none()
534                } else {
535                    wrappers::Hybrid::new(&info, pre.clone(), &nfa, &nfarev)
536                };
537                (Some(nfarev), hybrid, dfa)
538            };
539        Ok(Core {
540            info,
541            pre,
542            nfa,
543            nfarev,
544            pikevm,
545            backtrack,
546            onepass,
547            hybrid,
548            dfa,
549        })
550    }
551
552    #[cfg_attr(feature = "perf-inline", inline(always))]
553    fn try_search_mayfail(
554        &self,
555        cache: &mut Cache,
556        input: &Input<'_>,
557    ) -> Option<Result<Option<Match>, RetryFailError>> {
558        if let Some(e) = self.dfa.get(input) {
559            trace!("using full DFA for search at {:?}", input.get_span());
560            Some(e.try_search(input))
561        } else if let Some(e) = self.hybrid.get(input) {
562            trace!("using lazy DFA for search at {:?}", input.get_span());
563            Some(e.try_search(&mut cache.hybrid, input))
564        } else {
565            None
566        }
567    }
568
569    fn search_nofail(
570        &self,
571        cache: &mut Cache,
572        input: &Input<'_>,
573    ) -> Option<Match> {
574        let caps = &mut cache.capmatches;
575        caps.set_pattern(None);
576        // We manually inline 'try_search_slots_nofail' here because we need to
577        // borrow from 'cache.capmatches' in this method, but if we do, then
578        // we can't pass 'cache' wholesale to to 'try_slots_no_hybrid'. It's a
579        // classic example of how the borrow checker inhibits decomposition.
580        // There are of course work-arounds (more types and/or interior
581        // mutability), but that's more annoying than this IMO.
582        let pid = if let Some(ref e) = self.onepass.get(input) {
583            trace!("using OnePass for search at {:?}", input.get_span());
584            e.search_slots(&mut cache.onepass, input, caps.slots_mut())
585        } else if let Some(ref e) = self.backtrack.get(input) {
586            trace!(
587                "using BoundedBacktracker for search at {:?}",
588                input.get_span()
589            );
590            e.search_slots(&mut cache.backtrack, input, caps.slots_mut())
591        } else {
592            trace!("using PikeVM for search at {:?}", input.get_span());
593            let e = self.pikevm.get();
594            e.search_slots(&mut cache.pikevm, input, caps.slots_mut())
595        };
596        caps.set_pattern(pid);
597        caps.get_match()
598    }
599
600    fn search_half_nofail(
601        &self,
602        cache: &mut Cache,
603        input: &Input<'_>,
604    ) -> Option<HalfMatch> {
605        // Only the lazy/full DFA returns half-matches, since the DFA requires
606        // a reverse scan to find the start position. These fallback regex
607        // engines can find the start and end in a single pass, so we just do
608        // that and throw away the start offset to conform to the API.
609        let m = self.search_nofail(cache, input)?;
610        Some(HalfMatch::new(m.pattern(), m.end()))
611    }
612
613    fn search_slots_nofail(
614        &self,
615        cache: &mut Cache,
616        input: &Input<'_>,
617        slots: &mut [Option<NonMaxUsize>],
618    ) -> Option<PatternID> {
619        if let Some(ref e) = self.onepass.get(input) {
620            trace!(
621                "using OnePass for capture search at {:?}",
622                input.get_span()
623            );
624            e.search_slots(&mut cache.onepass, input, slots)
625        } else if let Some(ref e) = self.backtrack.get(input) {
626            trace!(
627                "using BoundedBacktracker for capture search at {:?}",
628                input.get_span()
629            );
630            e.search_slots(&mut cache.backtrack, input, slots)
631        } else {
632            trace!(
633                "using PikeVM for capture search at {:?}",
634                input.get_span()
635            );
636            let e = self.pikevm.get();
637            e.search_slots(&mut cache.pikevm, input, slots)
638        }
639    }
640
641    fn is_match_nofail(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
642        if let Some(ref e) = self.onepass.get(input) {
643            trace!(
644                "using OnePass for is-match search at {:?}",
645                input.get_span()
646            );
647            e.search_slots(&mut cache.onepass, input, &mut []).is_some()
648        } else if let Some(ref e) = self.backtrack.get(input) {
649            trace!(
650                "using BoundedBacktracker for is-match search at {:?}",
651                input.get_span()
652            );
653            e.is_match(&mut cache.backtrack, input)
654        } else {
655            trace!(
656                "using PikeVM for is-match search at {:?}",
657                input.get_span()
658            );
659            let e = self.pikevm.get();
660            e.is_match(&mut cache.pikevm, input)
661        }
662    }
663
664    fn is_capture_search_needed(&self, slots_len: usize) -> bool {
665        slots_len > self.nfa.group_info().implicit_slot_len()
666    }
667}
668
669impl Strategy for Core {
670    fn name(&self) -> Cow<'static, str> {
671        Cow::Borrowed("core")
672    }
673
674    #[cfg_attr(feature = "perf-inline", inline(always))]
675    fn group_info(&self) -> &GroupInfo {
676        self.nfa.group_info()
677    }
678
679    #[cfg_attr(feature = "perf-inline", inline(always))]
680    fn create_cache(&self) -> Cache {
681        Cache {
682            capmatches: Captures::all(self.group_info().clone()),
683            pikevm: self.pikevm.create_cache(),
684            backtrack: self.backtrack.create_cache(),
685            onepass: self.onepass.create_cache(),
686            hybrid: self.hybrid.create_cache(),
687            revhybrid: wrappers::ReverseHybridCache::none(),
688        }
689    }
690
691    #[cfg_attr(feature = "perf-inline", inline(always))]
692    fn reset_cache(&self, cache: &mut Cache) {
693        cache.pikevm.reset(&self.pikevm);
694        cache.backtrack.reset(&self.backtrack);
695        cache.onepass.reset(&self.onepass);
696        cache.hybrid.reset(&self.hybrid);
697    }
698
699    fn is_accelerated(&self) -> bool {
700        self.pre.as_ref().map_or(false, |pre| pre.is_fast())
701    }
702
703    fn memory_usage(&self) -> usize {
704        self.info.memory_usage()
705            + self.pre.as_ref().map_or(0, |pre| pre.memory_usage())
706            + self.nfa.memory_usage()
707            + self.nfarev.as_ref().map_or(0, |nfa| nfa.memory_usage())
708            + self.onepass.memory_usage()
709            + self.dfa.memory_usage()
710    }
711
712    #[cfg_attr(feature = "perf-inline", inline(always))]
713    fn search(&self, cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
714        // We manually inline try_search_mayfail here because letting the
715        // compiler do it seems to produce pretty crappy codegen.
716        return if let Some(e) = self.dfa.get(input) {
717            trace!("using full DFA for full search at {:?}", input.get_span());
718            match e.try_search(input) {
719                Ok(x) => x,
720                Err(_err) => {
721                    trace!("full DFA search failed: {_err}");
722                    self.search_nofail(cache, input)
723                }
724            }
725        } else if let Some(e) = self.hybrid.get(input) {
726            trace!("using lazy DFA for full search at {:?}", input.get_span());
727            match e.try_search(&mut cache.hybrid, input) {
728                Ok(x) => x,
729                Err(_err) => {
730                    trace!("lazy DFA search failed: {_err}");
731                    self.search_nofail(cache, input)
732                }
733            }
734        } else {
735            self.search_nofail(cache, input)
736        };
737    }
738
739    #[cfg_attr(feature = "perf-inline", inline(always))]
740    fn search_half(
741        &self,
742        cache: &mut Cache,
743        input: &Input<'_>,
744    ) -> Option<HalfMatch> {
745        // The main difference with 'search' is that if we're using a DFA, we
746        // can use a single forward scan without needing to run the reverse
747        // DFA.
748        if let Some(e) = self.dfa.get(input) {
749            trace!("using full DFA for half search at {:?}", input.get_span());
750            match e.try_search_half_fwd(input) {
751                Ok(x) => x,
752                Err(_err) => {
753                    trace!("full DFA half search failed: {_err}");
754                    self.search_half_nofail(cache, input)
755                }
756            }
757        } else if let Some(e) = self.hybrid.get(input) {
758            trace!("using lazy DFA for half search at {:?}", input.get_span());
759            match e.try_search_half_fwd(&mut cache.hybrid, input) {
760                Ok(x) => x,
761                Err(_err) => {
762                    trace!("lazy DFA half search failed: {_err}");
763                    self.search_half_nofail(cache, input)
764                }
765            }
766        } else {
767            self.search_half_nofail(cache, input)
768        }
769    }
770
771    #[cfg_attr(feature = "perf-inline", inline(always))]
772    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
773        if let Some(e) = self.dfa.get(input) {
774            trace!(
775                "using full DFA for is-match search at {:?}",
776                input.get_span()
777            );
778            match e.try_search_half_fwd(input) {
779                Ok(x) => x.is_some(),
780                Err(_err) => {
781                    trace!("full DFA half search failed: {_err}");
782                    self.is_match_nofail(cache, input)
783                }
784            }
785        } else if let Some(e) = self.hybrid.get(input) {
786            trace!(
787                "using lazy DFA for is-match search at {:?}",
788                input.get_span()
789            );
790            match e.try_search_half_fwd(&mut cache.hybrid, input) {
791                Ok(x) => x.is_some(),
792                Err(_err) => {
793                    trace!("lazy DFA half search failed: {_err}");
794                    self.is_match_nofail(cache, input)
795                }
796            }
797        } else {
798            self.is_match_nofail(cache, input)
799        }
800    }
801
802    #[cfg_attr(feature = "perf-inline", inline(always))]
803    fn search_slots(
804        &self,
805        cache: &mut Cache,
806        input: &Input<'_>,
807        slots: &mut [Option<NonMaxUsize>],
808    ) -> Option<PatternID> {
809        // Even if the regex has explicit capture groups, if the caller didn't
810        // provide any explicit slots, then it doesn't make sense to try and do
811        // extra work to get offsets for those slots. Ideally the caller should
812        // realize this and not call this routine in the first place, but alas,
813        // we try to save the caller from themselves if they do.
814        if !self.is_capture_search_needed(slots.len()) {
815            trace!("asked for slots unnecessarily, trying fast path");
816            let m = self.search(cache, input)?;
817            copy_match_to_slots(m, slots);
818            return Some(m.pattern());
819        }
820        // If the onepass DFA is available for this search (which only happens
821        // when it's anchored), then skip running a fallible DFA. The onepass
822        // DFA isn't as fast as a full or lazy DFA, but it is typically quite
823        // a bit faster than the backtracker or the PikeVM. So it isn't as
824        // advantageous to try and do a full/lazy DFA scan first.
825        //
826        // We still theorize that it's better to do a full/lazy DFA scan, even
827        // when it's anchored, because it's usually much faster and permits us
828        // to say "no match" much more quickly. This does hurt the case of,
829        // say, parsing each line in a log file into capture groups, because
830        // in that case, the line always matches. So the lazy DFA scan is
831        // usually just wasted work. But, the lazy DFA is usually quite fast
832        // and doesn't cost too much here.
833        if self.onepass.get(&input).is_some() {
834            return self.search_slots_nofail(cache, &input, slots);
835        }
836        let m = match self.try_search_mayfail(cache, input) {
837            Some(Ok(Some(m))) => m,
838            Some(Ok(None)) => return None,
839            Some(Err(_err)) => {
840                trace!("fast capture search failed: {_err}");
841                return self.search_slots_nofail(cache, input, slots);
842            }
843            None => {
844                return self.search_slots_nofail(cache, input, slots);
845            }
846        };
847        // At this point, now that we've found the bounds of the
848        // match, we need to re-run something that can resolve
849        // capturing groups. But we only need to run on it on the
850        // match bounds and not the entire haystack.
851        trace!(
852            "match found at {}..{} in capture search, \
853             using another engine to find captures",
854            m.start(),
855            m.end(),
856        );
857        let input = input
858            .clone()
859            .span(m.start()..m.end())
860            .anchored(Anchored::Pattern(m.pattern()));
861        Some(
862            self.search_slots_nofail(cache, &input, slots)
863                .expect("should find a match"),
864        )
865    }
866
867    #[cfg_attr(feature = "perf-inline", inline(always))]
868    fn which_overlapping_matches(
869        &self,
870        cache: &mut Cache,
871        input: &Input<'_>,
872        patset: &mut PatternSet,
873    ) {
874        if let Some(e) = self.dfa.get(input) {
875            trace!(
876                "using full DFA for overlapping search at {:?}",
877                input.get_span()
878            );
879            let _err = match e.try_which_overlapping_matches(input, patset) {
880                Ok(()) => return,
881                Err(err) => err,
882            };
883            trace!("fast overlapping search failed: {_err}");
884        } else if let Some(e) = self.hybrid.get(input) {
885            trace!(
886                "using lazy DFA for overlapping search at {:?}",
887                input.get_span()
888            );
889            let _err = match e.try_which_overlapping_matches(
890                &mut cache.hybrid,
891                input,
892                patset,
893            ) {
894                Ok(()) => {
895                    return;
896                }
897                Err(err) => err,
898            };
899            trace!("fast overlapping search failed: {_err}");
900        }
901        trace!(
902            "using PikeVM for overlapping search at {:?}",
903            input.get_span()
904        );
905        let e = self.pikevm.get();
906        e.which_overlapping_matches(&mut cache.pikevm, input, patset)
907    }
908}
909
910#[derive(Debug)]
911struct ReverseAnchored {
912    core: Core,
913}
914
915impl ReverseAnchored {
916    fn new(core: Core) -> Result<ReverseAnchored, Core> {
917        if !core.info.is_always_anchored_end() {
918            debug!(
919                "skipping reverse anchored optimization because \
920                 the regex is not always anchored at the end"
921            );
922            return Err(core);
923        }
924        // Note that the caller can still request an anchored search even when
925        // the regex isn't anchored at the start. We detect that case in the
926        // search routines below and just fallback to the core engine. This
927        // is fine because both searches are anchored. It's just a matter of
928        // picking one. Falling back to the core engine is a little simpler,
929        // since if we used the reverse anchored approach, we'd have to add an
930        // extra check to ensure the match reported starts at the place where
931        // the caller requested the search to start.
932        if core.info.is_always_anchored_start() {
933            debug!(
934                "skipping reverse anchored optimization because \
935                 the regex is also anchored at the start"
936            );
937            return Err(core);
938        }
939        // Only DFAs can do reverse searches (currently), so we need one of
940        // them in order to do this optimization. It's possible (although
941        // pretty unlikely) that we have neither and need to give up.
942        if !core.hybrid.is_some() && !core.dfa.is_some() {
943            debug!(
944                "skipping reverse anchored optimization because \
945                 we don't have a lazy DFA or a full DFA"
946            );
947            return Err(core);
948        }
949        Ok(ReverseAnchored { core })
950    }
951
952    #[cfg_attr(feature = "perf-inline", inline(always))]
953    fn try_search_half_anchored_rev(
954        &self,
955        cache: &mut Cache,
956        input: &Input<'_>,
957    ) -> Result<Option<HalfMatch>, RetryFailError> {
958        // We of course always want an anchored search. In theory, the
959        // underlying regex engines should automatically enable anchored
960        // searches since the regex is itself anchored, but this more clearly
961        // expresses intent and is always correct.
962        let input = input.clone().anchored(Anchored::Yes);
963        if let Some(e) = self.core.dfa.get(&input) {
964            trace!(
965                "using full DFA for reverse anchored search at {:?}",
966                input.get_span()
967            );
968            e.try_search_half_rev(&input)
969        } else if let Some(e) = self.core.hybrid.get(&input) {
970            trace!(
971                "using lazy DFA for reverse anchored search at {:?}",
972                input.get_span()
973            );
974            e.try_search_half_rev(&mut cache.hybrid, &input)
975        } else {
976            unreachable!("ReverseAnchored always has a DFA")
977        }
978    }
979}
980
981// Note that in this impl, we don't check that 'input.end() ==
982// input.haystack().len()'. In particular, when that condition is false, a
983// match is always impossible because we know that the regex is always anchored
984// at the end (or else 'ReverseAnchored' won't be built). We don't check that
985// here because the 'Regex' wrapper actually does that for us in all cases.
986// Thus, in this impl, we can actually assume that the end position in 'input'
987// is equivalent to the length of the haystack.
988impl Strategy for ReverseAnchored {
989    fn name(&self) -> Cow<'static, str> {
990        Cow::Borrowed("reverse anchored")
991    }
992
993    #[cfg_attr(feature = "perf-inline", inline(always))]
994    fn group_info(&self) -> &GroupInfo {
995        self.core.group_info()
996    }
997
998    #[cfg_attr(feature = "perf-inline", inline(always))]
999    fn create_cache(&self) -> Cache {
1000        self.core.create_cache()
1001    }
1002
1003    #[cfg_attr(feature = "perf-inline", inline(always))]
1004    fn reset_cache(&self, cache: &mut Cache) {
1005        self.core.reset_cache(cache);
1006    }
1007
1008    fn is_accelerated(&self) -> bool {
1009        // Since this is anchored at the end, a reverse anchored search is
1010        // almost certainly guaranteed to result in a much faster search than
1011        // a standard forward search.
1012        true
1013    }
1014
1015    fn memory_usage(&self) -> usize {
1016        self.core.memory_usage()
1017    }
1018
1019    #[cfg_attr(feature = "perf-inline", inline(always))]
1020    fn search(&self, cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
1021        if input.get_anchored().is_anchored() {
1022            return self.core.search(cache, input);
1023        }
1024        match self.try_search_half_anchored_rev(cache, input) {
1025            Err(_err) => {
1026                trace!("fast reverse anchored search failed: {_err}");
1027                self.core.search_nofail(cache, input)
1028            }
1029            Ok(None) => None,
1030            Ok(Some(hm)) => {
1031                Some(Match::new(hm.pattern(), hm.offset()..input.end()))
1032            }
1033        }
1034    }
1035
1036    #[cfg_attr(feature = "perf-inline", inline(always))]
1037    fn search_half(
1038        &self,
1039        cache: &mut Cache,
1040        input: &Input<'_>,
1041    ) -> Option<HalfMatch> {
1042        if input.get_anchored().is_anchored() {
1043            return self.core.search_half(cache, input);
1044        }
1045        match self.try_search_half_anchored_rev(cache, input) {
1046            Err(_err) => {
1047                trace!("fast reverse anchored search failed: {_err}");
1048                self.core.search_half_nofail(cache, input)
1049            }
1050            Ok(None) => None,
1051            Ok(Some(hm)) => {
1052                // Careful here! 'try_search_half' is a *forward* search that
1053                // only cares about the *end* position of a match. But
1054                // 'hm.offset()' is actually the start of the match. So we
1055                // actually just throw that away here and, since we know we
1056                // have a match, return the only possible position at which a
1057                // match can occur: input.end().
1058                Some(HalfMatch::new(hm.pattern(), input.end()))
1059            }
1060        }
1061    }
1062
1063    #[cfg_attr(feature = "perf-inline", inline(always))]
1064    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
1065        if input.get_anchored().is_anchored() {
1066            return self.core.is_match(cache, input);
1067        }
1068        match self.try_search_half_anchored_rev(cache, input) {
1069            Err(_err) => {
1070                trace!("fast reverse anchored search failed: {_err}");
1071                self.core.is_match_nofail(cache, input)
1072            }
1073            Ok(None) => false,
1074            Ok(Some(_)) => true,
1075        }
1076    }
1077
1078    #[cfg_attr(feature = "perf-inline", inline(always))]
1079    fn search_slots(
1080        &self,
1081        cache: &mut Cache,
1082        input: &Input<'_>,
1083        slots: &mut [Option<NonMaxUsize>],
1084    ) -> Option<PatternID> {
1085        if input.get_anchored().is_anchored() {
1086            return self.core.search_slots(cache, input, slots);
1087        }
1088        match self.try_search_half_anchored_rev(cache, input) {
1089            Err(_err) => {
1090                trace!("fast reverse anchored search failed: {_err}");
1091                self.core.search_slots_nofail(cache, input, slots)
1092            }
1093            Ok(None) => None,
1094            Ok(Some(hm)) => {
1095                if !self.core.is_capture_search_needed(slots.len()) {
1096                    trace!("asked for slots unnecessarily, skipping captures");
1097                    let m = Match::new(hm.pattern(), hm.offset()..input.end());
1098                    copy_match_to_slots(m, slots);
1099                    return Some(m.pattern());
1100                }
1101                let start = hm.offset();
1102                let input = input
1103                    .clone()
1104                    .span(start..input.end())
1105                    .anchored(Anchored::Pattern(hm.pattern()));
1106                self.core.search_slots_nofail(cache, &input, slots)
1107            }
1108        }
1109    }
1110
1111    #[cfg_attr(feature = "perf-inline", inline(always))]
1112    fn which_overlapping_matches(
1113        &self,
1114        cache: &mut Cache,
1115        input: &Input<'_>,
1116        patset: &mut PatternSet,
1117    ) {
1118        // It seems like this could probably benefit from a reverse anchored
1119        // optimization, perhaps by doing an overlapping reverse search (which
1120        // the DFAs do support). I haven't given it much thought though, and
1121        // I'm currently focus more on the single pattern case.
1122        self.core.which_overlapping_matches(cache, input, patset)
1123    }
1124}
1125
1126#[derive(Debug)]
1127struct ReverseSuffix {
1128    core: Core,
1129    pre: Prefilter,
1130}
1131
1132impl ReverseSuffix {
1133    fn new(core: Core, hirs: &[&Hir]) -> Result<ReverseSuffix, Core> {
1134        if !core.info.config().get_auto_prefilter() {
1135            debug!(
1136                "skipping reverse suffix optimization because \
1137                 automatic prefilters are disabled"
1138            );
1139            return Err(core);
1140        }
1141        // Also like the reverse inner optimization, a reverse suffix encodes
1142        // leftmost-first match semantics.
1143        if core.info.config().get_match_kind() != MatchKind::LeftmostFirst {
1144            debug!(
1145                "skipping reverse suffix optimization because \
1146                 match kind is {:?} but this only supports leftmost-first",
1147                core.info.config().get_match_kind(),
1148            );
1149            return Err(core);
1150        }
1151        // Like the reverse inner optimization, we don't do this for regexes
1152        // that are always anchored. It could lead to scanning too much, but
1153        // could say "no match" much more quickly than running the regex
1154        // engine if the initial literal scan doesn't match. With that said,
1155        // the reverse suffix optimization has lower overhead, since it only
1156        // requires a reverse scan after a literal match to confirm or reject
1157        // the match. (Although, in the case of confirmation, it then needs to
1158        // do another forward scan to find the end position.)
1159        //
1160        // Note that the caller can still request an anchored search even
1161        // when the regex isn't anchored. We detect that case in the search
1162        // routines below and just fallback to the core engine. Currently this
1163        // optimization assumes all searches are unanchored, so if we do want
1164        // to enable this optimization for anchored searches, it will need a
1165        // little work to support it.
1166        if core.info.is_always_anchored_start() {
1167            debug!(
1168                "skipping reverse suffix optimization because \
1169                 the regex is always anchored at the start",
1170            );
1171            return Err(core);
1172        }
1173        // Only DFAs can do reverse searches (currently), so we need one of
1174        // them in order to do this optimization. It's possible (although
1175        // pretty unlikely) that we have neither and need to give up.
1176        if !core.hybrid.is_some() && !core.dfa.is_some() {
1177            debug!(
1178                "skipping reverse suffix optimization because \
1179                 we don't have a lazy DFA or a full DFA"
1180            );
1181            return Err(core);
1182        }
1183        if core.pre.as_ref().map_or(false, |p| p.is_fast()) {
1184            debug!(
1185                "skipping reverse suffix optimization because \
1186                 we already have a prefilter that we think is fast"
1187            );
1188            return Err(core);
1189        }
1190        let kind = core.info.config().get_match_kind();
1191        let suffixes = crate::util::prefilter::suffixes(kind, hirs);
1192        let lcs = match suffixes.longest_common_suffix() {
1193            None => {
1194                debug!(
1195                    "skipping reverse suffix optimization because \
1196                     a longest common suffix could not be found",
1197                );
1198                return Err(core);
1199            }
1200            Some(lcs) if lcs.is_empty() => {
1201                debug!(
1202                    "skipping reverse suffix optimization because \
1203                     the longest common suffix is the empty string",
1204                );
1205                return Err(core);
1206            }
1207            Some(lcs) => lcs,
1208        };
1209        let pre = match Prefilter::new(kind, &[lcs]) {
1210            Some(pre) => pre,
1211            None => {
1212                debug!(
1213                    "skipping reverse suffix optimization because \
1214                     a prefilter could not be constructed from the \
1215                     longest common suffix",
1216                );
1217                return Err(core);
1218            }
1219        };
1220        if !pre.is_fast() {
1221            debug!(
1222                "skipping reverse suffix optimization because \
1223                 while we have a suffix prefilter, it is not \
1224                 believed to be 'fast'"
1225            );
1226            return Err(core);
1227        }
1228        if !reverse_suffix::has_no_earlier_match(hirs, &lcs) {
1229            debug!(
1230                "skipping reverse suffix optimization because \
1231                 an earlier suffix match could be a complete match \
1232                 inside of a larger match"
1233            );
1234            return Err(core);
1235        }
1236        Ok(ReverseSuffix { core, pre })
1237    }
1238
1239    #[cfg_attr(feature = "perf-inline", inline(always))]
1240    fn try_search_half_start(
1241        &self,
1242        cache: &mut Cache,
1243        input: &Input<'_>,
1244    ) -> Result<Option<HalfMatch>, RetryError> {
1245        let mut span = input.get_span();
1246        let mut min_start = 0;
1247        loop {
1248            let litmatch = match self.pre.find(input.haystack(), span) {
1249                None => break,
1250                Some(span) => span,
1251            };
1252            trace!("reverse suffix scan found suffix match at {litmatch:?}");
1253            let revinput = input
1254                .clone()
1255                .anchored(Anchored::Yes)
1256                .span(input.start()..litmatch.end);
1257            if let Some(hm) =
1258                self.try_search_half_rev_limited(cache, &revinput, min_start)?
1259            {
1260                return Ok(Some(hm));
1261            }
1262
1263            if span.start >= span.end {
1264                break;
1265            }
1266            span.start = litmatch.start.checked_add(1).unwrap();
1267            min_start = litmatch.end;
1268        }
1269        Ok(None)
1270    }
1271
1272    #[cfg_attr(feature = "perf-inline", inline(always))]
1273    fn try_search_half_fwd(
1274        &self,
1275        cache: &mut Cache,
1276        input: &Input<'_>,
1277    ) -> Result<Option<HalfMatch>, RetryFailError> {
1278        if let Some(e) = self.core.dfa.get(&input) {
1279            trace!(
1280                "using full DFA for forward reverse suffix search at {:?}",
1281                input.get_span()
1282            );
1283            e.try_search_half_fwd(&input)
1284        } else if let Some(e) = self.core.hybrid.get(&input) {
1285            trace!(
1286                "using lazy DFA for forward reverse suffix search at {:?}",
1287                input.get_span()
1288            );
1289            e.try_search_half_fwd(&mut cache.hybrid, &input)
1290        } else {
1291            unreachable!("ReverseSuffix always has a DFA")
1292        }
1293    }
1294
1295    #[cfg_attr(feature = "perf-inline", inline(always))]
1296    fn try_search_half_rev_limited(
1297        &self,
1298        cache: &mut Cache,
1299        input: &Input<'_>,
1300        min_start: usize,
1301    ) -> Result<Option<HalfMatch>, RetryError> {
1302        if let Some(e) = self.core.dfa.get(&input) {
1303            trace!(
1304                "using full DFA for reverse suffix search at {:?}, \
1305                 but will be stopped at {} to avoid quadratic behavior",
1306                input.get_span(),
1307                min_start,
1308            );
1309            e.try_search_half_rev_limited(&input, min_start)
1310        } else if let Some(e) = self.core.hybrid.get(&input) {
1311            trace!(
1312                "using lazy DFA for reverse suffix search at {:?}, \
1313                 but will be stopped at {} to avoid quadratic behavior",
1314                input.get_span(),
1315                min_start,
1316            );
1317            e.try_search_half_rev_limited(&mut cache.hybrid, &input, min_start)
1318        } else {
1319            unreachable!("ReverseSuffix always has a DFA")
1320        }
1321    }
1322}
1323
1324impl Strategy for ReverseSuffix {
1325    fn name(&self) -> Cow<'static, str> {
1326        Cow::Borrowed("reverse suffix")
1327    }
1328
1329    #[cfg_attr(feature = "perf-inline", inline(always))]
1330    fn group_info(&self) -> &GroupInfo {
1331        self.core.group_info()
1332    }
1333
1334    #[cfg_attr(feature = "perf-inline", inline(always))]
1335    fn create_cache(&self) -> Cache {
1336        self.core.create_cache()
1337    }
1338
1339    #[cfg_attr(feature = "perf-inline", inline(always))]
1340    fn reset_cache(&self, cache: &mut Cache) {
1341        self.core.reset_cache(cache);
1342    }
1343
1344    fn is_accelerated(&self) -> bool {
1345        self.pre.is_fast()
1346    }
1347
1348    fn memory_usage(&self) -> usize {
1349        self.core.memory_usage() + self.pre.memory_usage()
1350    }
1351
1352    #[cfg_attr(feature = "perf-inline", inline(always))]
1353    fn search(&self, cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
1354        if input.get_anchored().is_anchored() {
1355            return self.core.search(cache, input);
1356        }
1357        match self.try_search_half_start(cache, input) {
1358            Err(RetryError::Quadratic(_err)) => {
1359                trace!("reverse suffix optimization failed: {_err}");
1360                self.core.search(cache, input)
1361            }
1362            Err(RetryError::Fail(_err)) => {
1363                trace!("reverse suffix reverse fast search failed: {_err}");
1364                self.core.search_nofail(cache, input)
1365            }
1366            Ok(None) => None,
1367            Ok(Some(hm_start)) => {
1368                let fwdinput = input
1369                    .clone()
1370                    .anchored(Anchored::Pattern(hm_start.pattern()))
1371                    .span(hm_start.offset()..input.end());
1372                match self.try_search_half_fwd(cache, &fwdinput) {
1373                    Err(_err) => {
1374                        trace!(
1375                            "reverse suffix forward fast search failed: {_err}"
1376                        );
1377                        self.core.search_nofail(cache, input)
1378                    }
1379                    Ok(None) => {
1380                        unreachable!(
1381                            "suffix match plus reverse match implies \
1382                             there must be a match",
1383                        )
1384                    }
1385                    Ok(Some(hm_end)) => Some(Match::new(
1386                        hm_start.pattern(),
1387                        hm_start.offset()..hm_end.offset(),
1388                    )),
1389                }
1390            }
1391        }
1392    }
1393
1394    #[cfg_attr(feature = "perf-inline", inline(always))]
1395    fn search_half(
1396        &self,
1397        cache: &mut Cache,
1398        input: &Input<'_>,
1399    ) -> Option<HalfMatch> {
1400        if input.get_anchored().is_anchored() {
1401            return self.core.search_half(cache, input);
1402        }
1403        match self.try_search_half_start(cache, input) {
1404            Err(RetryError::Quadratic(_err)) => {
1405                trace!("reverse suffix half optimization failed: {_err}");
1406                self.core.search_half(cache, input)
1407            }
1408            Err(RetryError::Fail(_err)) => {
1409                trace!(
1410                    "reverse suffix reverse fast half search failed: {_err}"
1411                );
1412                self.core.search_half_nofail(cache, input)
1413            }
1414            Ok(None) => None,
1415            Ok(Some(hm_start)) => {
1416                // This is a bit subtle. It is tempting to just stop searching
1417                // at this point and return a half-match with an offset
1418                // corresponding to where the suffix was found. But the suffix
1419                // match does not necessarily correspond to the end of the
1420                // proper leftmost-first match. Consider /[a-z]+ing/ against
1421                // 'tingling'. The first suffix match is the first 'ing', and
1422                // the /[a-z]+/ matches the 't'. So if we stopped here, then
1423                // we'd report 'ting' as the match. But 'tingling' is the
1424                // correct match because of greediness.
1425                let fwdinput = input
1426                    .clone()
1427                    .anchored(Anchored::Pattern(hm_start.pattern()))
1428                    .span(hm_start.offset()..input.end());
1429                match self.try_search_half_fwd(cache, &fwdinput) {
1430                    Err(_err) => {
1431                        trace!(
1432                            "reverse suffix forward fast search failed: {_err}"
1433                        );
1434                        self.core.search_half_nofail(cache, input)
1435                    }
1436                    Ok(None) => {
1437                        unreachable!(
1438                            "suffix match plus reverse match implies \
1439                             there must be a match",
1440                        )
1441                    }
1442                    Ok(Some(hm_end)) => Some(hm_end),
1443                }
1444            }
1445        }
1446    }
1447
1448    #[cfg_attr(feature = "perf-inline", inline(always))]
1449    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
1450        if input.get_anchored().is_anchored() {
1451            return self.core.is_match(cache, input);
1452        }
1453        match self.try_search_half_start(cache, input) {
1454            Err(RetryError::Quadratic(_err)) => {
1455                trace!("reverse suffix half optimization failed: {_err}");
1456                self.core.is_match_nofail(cache, input)
1457            }
1458            Err(RetryError::Fail(_err)) => {
1459                trace!(
1460                    "reverse suffix reverse fast half search failed: {_err}"
1461                );
1462                self.core.is_match_nofail(cache, input)
1463            }
1464            Ok(None) => false,
1465            Ok(Some(_)) => true,
1466        }
1467    }
1468
1469    #[cfg_attr(feature = "perf-inline", inline(always))]
1470    fn search_slots(
1471        &self,
1472        cache: &mut Cache,
1473        input: &Input<'_>,
1474        slots: &mut [Option<NonMaxUsize>],
1475    ) -> Option<PatternID> {
1476        if input.get_anchored().is_anchored() {
1477            return self.core.search_slots(cache, input, slots);
1478        }
1479        if !self.core.is_capture_search_needed(slots.len()) {
1480            trace!("asked for slots unnecessarily, trying fast path");
1481            let m = self.search(cache, input)?;
1482            copy_match_to_slots(m, slots);
1483            return Some(m.pattern());
1484        }
1485        let hm_start = match self.try_search_half_start(cache, input) {
1486            Err(RetryError::Quadratic(_err)) => {
1487                trace!("reverse suffix captures optimization failed: {_err}");
1488                return self.core.search_slots(cache, input, slots);
1489            }
1490            Err(RetryError::Fail(_err)) => {
1491                trace!(
1492                    "reverse suffix reverse fast captures search failed: \
1493                     {_err}"
1494                );
1495                return self.core.search_slots_nofail(cache, input, slots);
1496            }
1497            Ok(None) => return None,
1498            Ok(Some(hm_start)) => hm_start,
1499        };
1500        trace!(
1501            "match found at {}..{} in capture search, \
1502             using another engine to find captures",
1503            hm_start.offset(),
1504            input.end(),
1505        );
1506        let start = hm_start.offset();
1507        let input = input
1508            .clone()
1509            .span(start..input.end())
1510            .anchored(Anchored::Pattern(hm_start.pattern()));
1511        self.core.search_slots_nofail(cache, &input, slots)
1512    }
1513
1514    #[cfg_attr(feature = "perf-inline", inline(always))]
1515    fn which_overlapping_matches(
1516        &self,
1517        cache: &mut Cache,
1518        input: &Input<'_>,
1519        patset: &mut PatternSet,
1520    ) {
1521        self.core.which_overlapping_matches(cache, input, patset)
1522    }
1523}
1524
1525#[derive(Debug)]
1526struct ReverseInner {
1527    core: Core,
1528    preinner: Prefilter,
1529    nfarev: NFA,
1530    hybrid: wrappers::ReverseHybrid,
1531    dfa: wrappers::ReverseDFA,
1532}
1533
1534impl ReverseInner {
1535    fn new(core: Core, hirs: &[&Hir]) -> Result<ReverseInner, Core> {
1536        if !core.info.config().get_auto_prefilter() {
1537            debug!(
1538                "skipping reverse inner optimization because \
1539                 automatic prefilters are disabled"
1540            );
1541            return Err(core);
1542        }
1543        // Currently we hard-code the assumption of leftmost-first match
1544        // semantics. This isn't a huge deal because 'all' semantics tend to
1545        // only be used for forward overlapping searches with multiple regexes,
1546        // and this optimization only supports a single pattern at the moment.
1547        if core.info.config().get_match_kind() != MatchKind::LeftmostFirst {
1548            debug!(
1549                "skipping reverse inner optimization because \
1550                 match kind is {:?} but this only supports leftmost-first",
1551                core.info.config().get_match_kind(),
1552            );
1553            return Err(core);
1554        }
1555        // It's likely that a reverse inner scan has too much overhead for it
1556        // to be worth it when the regex is anchored at the start. It is
1557        // possible for it to be quite a bit faster if the initial literal
1558        // scan fails to detect a match, in which case, we can say "no match"
1559        // very quickly. But this could be undesirable, e.g., scanning too far
1560        // or when the literal scan matches. If it matches, then confirming the
1561        // match requires a reverse scan followed by a forward scan to confirm
1562        // or reject, which is a fair bit of work.
1563        //
1564        // Note that the caller can still request an anchored search even
1565        // when the regex isn't anchored. We detect that case in the search
1566        // routines below and just fallback to the core engine. Currently this
1567        // optimization assumes all searches are unanchored, so if we do want
1568        // to enable this optimization for anchored searches, it will need a
1569        // little work to support it.
1570        if core.info.is_always_anchored_start() {
1571            debug!(
1572                "skipping reverse inner optimization because \
1573                 the regex is always anchored at the start",
1574            );
1575            return Err(core);
1576        }
1577        // Only DFAs can do reverse searches (currently), so we need one of
1578        // them in order to do this optimization. It's possible (although
1579        // pretty unlikely) that we have neither and need to give up.
1580        if !core.hybrid.is_some() && !core.dfa.is_some() {
1581            debug!(
1582                "skipping reverse inner optimization because \
1583                 we don't have a lazy DFA or a full DFA"
1584            );
1585            return Err(core);
1586        }
1587        if core.pre.as_ref().map_or(false, |p| p.is_fast()) {
1588            debug!(
1589                "skipping reverse inner optimization because \
1590                 we already have a prefilter that we think is fast"
1591            );
1592            return Err(core);
1593        } else if core.pre.is_some() {
1594            debug!(
1595                "core engine has a prefix prefilter, but it is \
1596                 probably not fast, so continuing with attempt to \
1597                 use reverse inner prefilter"
1598            );
1599        }
1600        let prefilter = match reverse_inner::InnerPrefilter::new(hirs) {
1601            Some(prefilter) => prefilter,
1602            // N.B. the 'new' function emits debug messages explaining
1603            // why we bailed out here.
1604            None => return Err(core),
1605        };
1606        if !reverse_inner::has_no_earlier_match(
1607            &prefilter.prefix,
1608            &prefilter.literals,
1609        ) {
1610            debug!(
1611                "skipping reverse inner optimization because an inner \
1612                 literal match could be confirmed before an earlier match"
1613            );
1614            return Err(core);
1615        }
1616        debug!("building reverse NFA for prefix before inner literal");
1617        let thompson_config = core
1618            .info
1619            .config()
1620            .to_thompson_config()
1621            .reverse(true)
1622            .which_captures(WhichCaptures::None);
1623        let result = thompson::Compiler::new()
1624            .configure(thompson_config)
1625            .build_from_hir(&prefilter.prefix);
1626        let nfarev = match result {
1627            Ok(nfarev) => nfarev,
1628            Err(_err) => {
1629                debug!(
1630                    "skipping reverse inner optimization because the \
1631                     reverse NFA failed to build: {}",
1632                    _err,
1633                );
1634                return Err(core);
1635            }
1636        };
1637        debug!("building reverse DFA for prefix before inner literal");
1638        let dfa = if !core.info.config().get_dfa() {
1639            wrappers::ReverseDFA::none()
1640        } else {
1641            wrappers::ReverseDFA::new(&core.info, &nfarev)
1642        };
1643        let hybrid = if !core.info.config().get_hybrid() {
1644            wrappers::ReverseHybrid::none()
1645        } else if dfa.is_some() {
1646            debug!(
1647                "skipping lazy DFA for reverse inner optimization \
1648                 because we have a full DFA"
1649            );
1650            wrappers::ReverseHybrid::none()
1651        } else {
1652            wrappers::ReverseHybrid::new(&core.info, &nfarev)
1653        };
1654        Ok(ReverseInner { core, preinner: prefilter.pre, nfarev, hybrid, dfa })
1655    }
1656
1657    #[cfg_attr(feature = "perf-inline", inline(always))]
1658    fn try_search_full(
1659        &self,
1660        cache: &mut Cache,
1661        input: &Input<'_>,
1662    ) -> Result<Option<Match>, RetryError> {
1663        let mut span = input.get_span();
1664        let mut min_match_start = 0;
1665        let mut min_pre_start = 0;
1666        loop {
1667            let litmatch = match self.preinner.find(input.haystack(), span) {
1668                None => break,
1669                Some(span) => span,
1670            };
1671            if litmatch.start < min_pre_start {
1672                trace!(
1673                    "found inner prefilter match at {litmatch:?}, which starts \
1674                     before the end of the last forward scan at {min_pre_start}, \
1675                     quitting to avoid quadratic behavior",
1676                );
1677                return Err(RetryError::Quadratic(RetryQuadraticError::new()));
1678            }
1679            trace!("reverse inner scan found inner match at {litmatch:?}");
1680            let revinput = input
1681                .clone()
1682                .anchored(Anchored::Yes)
1683                .span(input.start()..litmatch.start);
1684            // Note that in addition to the literal search above scanning past
1685            // our minimum start point, this routine can also return an error
1686            // as a result of detecting possible quadratic behavior if the
1687            // reverse scan goes past the minimum start point. That is, the
1688            // literal search might not, but the reverse regex search for the
1689            // prefix might!
1690            if let Some(hm_start) = self.try_search_half_rev_limited(
1691                cache,
1692                &revinput,
1693                min_match_start,
1694            )? {
1695                let fwdinput = input
1696                    .clone()
1697                    .anchored(Anchored::Pattern(hm_start.pattern()))
1698                    .span(hm_start.offset()..input.end());
1699                match self.try_search_half_fwd_stopat(cache, &fwdinput)? {
1700                    Err(stopat) => {
1701                        min_pre_start = stopat;
1702                        span.start = litmatch.start.checked_add(1).unwrap();
1703                    }
1704                    Ok(hm_end) => {
1705                        return Ok(Some(Match::new(
1706                            hm_start.pattern(),
1707                            hm_start.offset()..hm_end.offset(),
1708                        )));
1709                    }
1710                }
1711            }
1712
1713            if span.start >= span.end {
1714                break;
1715            }
1716            span.start = litmatch.start.checked_add(1).unwrap();
1717            min_match_start = litmatch.end;
1718        }
1719        Ok(None)
1720    }
1721
1722    #[cfg_attr(feature = "perf-inline", inline(always))]
1723    fn try_search_half_fwd_stopat(
1724        &self,
1725        cache: &mut Cache,
1726        input: &Input<'_>,
1727    ) -> Result<Result<HalfMatch, usize>, RetryFailError> {
1728        if let Some(e) = self.core.dfa.get(&input) {
1729            trace!(
1730                "using full DFA for forward reverse inner search at {:?}",
1731                input.get_span()
1732            );
1733            e.try_search_half_fwd_stopat(&input)
1734        } else if let Some(e) = self.core.hybrid.get(&input) {
1735            trace!(
1736                "using lazy DFA for forward reverse inner search at {:?}",
1737                input.get_span()
1738            );
1739            e.try_search_half_fwd_stopat(&mut cache.hybrid, &input)
1740        } else {
1741            unreachable!("ReverseInner always has a DFA")
1742        }
1743    }
1744
1745    #[cfg_attr(feature = "perf-inline", inline(always))]
1746    fn try_search_half_rev_limited(
1747        &self,
1748        cache: &mut Cache,
1749        input: &Input<'_>,
1750        min_start: usize,
1751    ) -> Result<Option<HalfMatch>, RetryError> {
1752        if let Some(e) = self.dfa.get(&input) {
1753            trace!(
1754                "using full DFA for reverse inner search at {:?}, \
1755                 but will be stopped at {} to avoid quadratic behavior",
1756                input.get_span(),
1757                min_start,
1758            );
1759            e.try_search_half_rev_limited(&input, min_start)
1760        } else if let Some(e) = self.hybrid.get(&input) {
1761            trace!(
1762                "using lazy DFA for reverse inner search at {:?}, \
1763                 but will be stopped at {} to avoid quadratic behavior",
1764                input.get_span(),
1765                min_start,
1766            );
1767            e.try_search_half_rev_limited(
1768                &mut cache.revhybrid,
1769                &input,
1770                min_start,
1771            )
1772        } else {
1773            unreachable!("ReverseInner always has a DFA")
1774        }
1775    }
1776}
1777
1778impl Strategy for ReverseInner {
1779    fn name(&self) -> Cow<'static, str> {
1780        Cow::Borrowed("reverse inner")
1781    }
1782
1783    #[cfg_attr(feature = "perf-inline", inline(always))]
1784    fn group_info(&self) -> &GroupInfo {
1785        self.core.group_info()
1786    }
1787
1788    #[cfg_attr(feature = "perf-inline", inline(always))]
1789    fn create_cache(&self) -> Cache {
1790        let mut cache = self.core.create_cache();
1791        cache.revhybrid = self.hybrid.create_cache();
1792        cache
1793    }
1794
1795    #[cfg_attr(feature = "perf-inline", inline(always))]
1796    fn reset_cache(&self, cache: &mut Cache) {
1797        self.core.reset_cache(cache);
1798        cache.revhybrid.reset(&self.hybrid);
1799    }
1800
1801    fn is_accelerated(&self) -> bool {
1802        self.preinner.is_fast()
1803    }
1804
1805    fn memory_usage(&self) -> usize {
1806        self.core.memory_usage()
1807            + self.preinner.memory_usage()
1808            + self.nfarev.memory_usage()
1809            + self.dfa.memory_usage()
1810    }
1811
1812    #[cfg_attr(feature = "perf-inline", inline(always))]
1813    fn search(&self, cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
1814        if input.get_anchored().is_anchored() {
1815            return self.core.search(cache, input);
1816        }
1817        match self.try_search_full(cache, input) {
1818            Err(RetryError::Quadratic(_err)) => {
1819                trace!("reverse inner optimization failed: {_err}");
1820                self.core.search(cache, input)
1821            }
1822            Err(RetryError::Fail(_err)) => {
1823                trace!("reverse inner fast search failed: {_err}");
1824                self.core.search_nofail(cache, input)
1825            }
1826            Ok(matornot) => matornot,
1827        }
1828    }
1829
1830    #[cfg_attr(feature = "perf-inline", inline(always))]
1831    fn search_half(
1832        &self,
1833        cache: &mut Cache,
1834        input: &Input<'_>,
1835    ) -> Option<HalfMatch> {
1836        if input.get_anchored().is_anchored() {
1837            return self.core.search_half(cache, input);
1838        }
1839        match self.try_search_full(cache, input) {
1840            Err(RetryError::Quadratic(_err)) => {
1841                trace!("reverse inner half optimization failed: {_err}");
1842                self.core.search_half(cache, input)
1843            }
1844            Err(RetryError::Fail(_err)) => {
1845                trace!("reverse inner fast half search failed: {_err}");
1846                self.core.search_half_nofail(cache, input)
1847            }
1848            Ok(None) => None,
1849            Ok(Some(m)) => Some(HalfMatch::new(m.pattern(), m.end())),
1850        }
1851    }
1852
1853    #[cfg_attr(feature = "perf-inline", inline(always))]
1854    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
1855        if input.get_anchored().is_anchored() {
1856            return self.core.is_match(cache, input);
1857        }
1858        match self.try_search_full(cache, input) {
1859            Err(RetryError::Quadratic(_err)) => {
1860                trace!("reverse inner half optimization failed: {_err}");
1861                self.core.is_match_nofail(cache, input)
1862            }
1863            Err(RetryError::Fail(_err)) => {
1864                trace!("reverse inner fast half search failed: {_err}");
1865                self.core.is_match_nofail(cache, input)
1866            }
1867            Ok(None) => false,
1868            Ok(Some(_)) => true,
1869        }
1870    }
1871
1872    #[cfg_attr(feature = "perf-inline", inline(always))]
1873    fn search_slots(
1874        &self,
1875        cache: &mut Cache,
1876        input: &Input<'_>,
1877        slots: &mut [Option<NonMaxUsize>],
1878    ) -> Option<PatternID> {
1879        if input.get_anchored().is_anchored() {
1880            return self.core.search_slots(cache, input, slots);
1881        }
1882        if !self.core.is_capture_search_needed(slots.len()) {
1883            trace!("asked for slots unnecessarily, trying fast path");
1884            let m = self.search(cache, input)?;
1885            copy_match_to_slots(m, slots);
1886            return Some(m.pattern());
1887        }
1888        let m = match self.try_search_full(cache, input) {
1889            Err(RetryError::Quadratic(_err)) => {
1890                trace!("reverse inner captures optimization failed: {_err}");
1891                return self.core.search_slots(cache, input, slots);
1892            }
1893            Err(RetryError::Fail(_err)) => {
1894                trace!("reverse inner fast captures search failed: {_err}");
1895                return self.core.search_slots_nofail(cache, input, slots);
1896            }
1897            Ok(None) => return None,
1898            Ok(Some(m)) => m,
1899        };
1900        trace!(
1901            "match found at {}..{} in capture search, \
1902             using another engine to find captures",
1903            m.start(),
1904            m.end(),
1905        );
1906        let input = input
1907            .clone()
1908            .span(m.start()..m.end())
1909            .anchored(Anchored::Pattern(m.pattern()));
1910        self.core.search_slots_nofail(cache, &input, slots)
1911    }
1912
1913    #[cfg_attr(feature = "perf-inline", inline(always))]
1914    fn which_overlapping_matches(
1915        &self,
1916        cache: &mut Cache,
1917        input: &Input<'_>,
1918        patset: &mut PatternSet,
1919    ) {
1920        self.core.which_overlapping_matches(cache, input, patset)
1921    }
1922}
1923
1924/// Copies the offsets in the given match to the corresponding positions in
1925/// `slots`.
1926///
1927/// In effect, this sets the slots corresponding to the implicit group for the
1928/// pattern in the given match. If the indices for the corresponding slots do
1929/// not exist, then no slots are set.
1930///
1931/// This is useful when the caller provides slots (or captures), but you use a
1932/// regex engine that doesn't operate on slots (like a lazy DFA). This function
1933/// lets you map the match you get back to the slots provided by the caller.
1934#[cfg_attr(feature = "perf-inline", inline(always))]
1935fn copy_match_to_slots(m: Match, slots: &mut [Option<NonMaxUsize>]) {
1936    let slot_start = m.pattern().as_usize() * 2;
1937    let slot_end = slot_start + 1;
1938    if let Some(slot) = slots.get_mut(slot_start) {
1939        *slot = NonMaxUsize::new(m.start());
1940    }
1941    if let Some(slot) = slots.get_mut(slot_end) {
1942        *slot = NonMaxUsize::new(m.end());
1943    }
1944}
1945
1946// We only test which strategy we get when all literal features are enabled.
1947// Other cases are less substantially less interesting.
1948//
1949// We also don't test this on miri since it takes forever.
1950//
1951// We also require `unicode-perl` since some regexes use `\w`, `\d` and `\s`.
1952#[cfg(all(
1953    feature = "perf-literal-substring",
1954    feature = "perf-literal-multisubstring",
1955    feature = "unicode-perl",
1956    not(miri),
1957))]
1958#[cfg(test)]
1959mod tests {
1960    use alloc::{format, string::String, vec::Vec};
1961
1962    use crate::{meta::regex::Config, util::syntax};
1963
1964    use super::*;
1965
1966    fn teddy_probably_available() -> bool {
1967        cfg!(any(target_arch = "x86_64", target_arch = "aarch64"))
1968    }
1969
1970    #[track_caller]
1971    fn strategy_with(patterns: &[&str], config: Config) -> Arc<dyn Strategy> {
1972        let hirs = syntax::parse_many(patterns).unwrap();
1973        let hirs: Vec<&Hir> = hirs.iter().collect();
1974        let info = RegexInfo::new(config, &hirs);
1975        new(&info, &hirs).unwrap()
1976    }
1977
1978    #[track_caller]
1979    fn assert_strategy(name: &'static str, patterns: &[&str]) {
1980        assert_strategy_with(name, patterns, Config::new());
1981    }
1982
1983    #[track_caller]
1984    fn assert_strategy_with(
1985        name: &'static str,
1986        patterns: &[&str],
1987        config: Config,
1988    ) {
1989        let strategy = strategy_with(patterns, config);
1990        assert_eq!(name, strategy.name().as_ref());
1991    }
1992
1993    fn literal_alternation(count: usize) -> String {
1994        let mut pattern = String::new();
1995        for i in 0..count {
1996            if i > 0 {
1997                pattern.push('|');
1998            }
1999            pattern.push_str(&format!("needle{i:05}"));
2000        }
2001        pattern
2002    }
2003
2004    #[test]
2005    fn pre_from_prefixes_accepts_exact_literal() {
2006        assert_strategy("prefilter memchr", &["a"]);
2007        assert_strategy("prefilter memchr2", &["a|b"]);
2008        assert_strategy("prefilter memchr3", &["a|b|c"]);
2009        assert_strategy("prefilter memmem", &["Sherlock"]);
2010        if teddy_probably_available() {
2011            assert_strategy(
2012                "prefilter teddy",
2013                &["Samwise|Gandalf|Holmes|Watson"],
2014            );
2015        }
2016    }
2017
2018    #[test]
2019    fn pre_from_prefixes_rejects_inexact_literal() {
2020        assert_strategy("core", &["a+"]);
2021    }
2022
2023    #[test]
2024    fn pre_from_prefixes_rejects_empty_literal() {
2025        assert_strategy("core", &[""]);
2026    }
2027
2028    #[test]
2029    fn pre_from_prefixes_rejects_multiple_patterns() {
2030        assert_strategy("core", &["a", "b"]);
2031        assert_strategy("core", &["a|b", "c|d"]);
2032    }
2033
2034    #[test]
2035    fn pre_from_prefixes_rejects_captures() {
2036        assert_strategy("core", &["(a)"]);
2037        // ... but not when it's a non-capture.
2038        assert_strategy("prefilter memchr", &["(?:a)"]);
2039        // ... or when there is a capture, but it's optimized out.
2040        assert_strategy("prefilter memchr", &["(){0}a"]);
2041    }
2042
2043    #[test]
2044    fn pre_from_prefixes_rejects_look_around() {
2045        assert_strategy("core", &[r"a\b"]);
2046    }
2047
2048    #[test]
2049    fn pre_from_prefixes_rejects_non_leftmost_first() {
2050        assert_strategy_with(
2051            "core",
2052            &["a"],
2053            Config::new().match_kind(MatchKind::All),
2054        );
2055    }
2056
2057    #[test]
2058    fn pre_from_alternation_literals_accepts_large_alternation() {
2059        let pattern = literal_alternation(3_000);
2060        assert_strategy("prefilter aho-corasick", &[&pattern]);
2061    }
2062
2063    #[test]
2064    fn pre_from_alternation_literals_rejects_small_alternation() {
2065        let pattern = literal_alternation(2_999);
2066        assert_strategy("core", &[&pattern]);
2067    }
2068
2069    #[test]
2070    fn pre_from_alternation_literals_rejects_captures() {
2071        let pattern = format!("({})", literal_alternation(3_000));
2072        assert_strategy("core", &[&pattern]);
2073    }
2074
2075    #[test]
2076    fn pre_from_alternation_literals_rejects_look_around() {
2077        let pattern = format!(r"(?:{})\b", literal_alternation(3_000));
2078        assert_strategy("core", &[&pattern]);
2079    }
2080
2081    #[test]
2082    fn pre_from_alternation_literals_rejects_non_leftmost_first() {
2083        let pattern = literal_alternation(3_000);
2084        assert_strategy_with(
2085            "core",
2086            &[&pattern],
2087            Config::new().match_kind(MatchKind::All),
2088        );
2089    }
2090
2091    #[test]
2092    fn pre_from_alternation_literals_rejects_multiple_patterns() {
2093        let pattern1 = literal_alternation(3_000);
2094        let pattern2 = literal_alternation(3_000);
2095        assert_strategy("core", &[&pattern1, &pattern2]);
2096    }
2097
2098    #[test]
2099    fn core_selected_when_no_special_strategy_applies() {
2100        assert_strategy("core", &["[a-z]+"]);
2101    }
2102
2103    #[test]
2104    fn core_selected_when_auto_prefilter_is_disabled() {
2105        assert_strategy_with(
2106            "core",
2107            &[r"\w+Holmes"],
2108            Config::new().auto_prefilter(false),
2109        );
2110    }
2111
2112    #[test]
2113    fn core_selected_when_reverse_engines_are_disabled() {
2114        assert_strategy_with(
2115            "core",
2116            &[r"\w+Holmes"],
2117            Config::new().dfa(false).hybrid(false),
2118        );
2119    }
2120
2121    #[test]
2122    fn reverse_anchored_accepts_end_anchored() {
2123        assert_strategy("reverse anchored", &[r"\w+Holmes$"]);
2124    }
2125
2126    #[test]
2127    fn reverse_anchored_accepts_multiple_end_anchored_patterns() {
2128        assert_strategy("reverse anchored", &[r"\w+Holmes$", r"\w+Watson$"]);
2129    }
2130
2131    #[test]
2132    fn reverse_anchored_accepts_hybrid_without_full_dfa() {
2133        assert_strategy_with(
2134            "reverse anchored",
2135            &[r"\w+Holmes$"],
2136            Config::new().dfa(false),
2137        );
2138    }
2139
2140    #[test]
2141    fn reverse_anchored_rejects_start_and_end_anchored() {
2142        assert_strategy("core", &[r"^\w+Holmes$"]);
2143    }
2144
2145    #[test]
2146    fn reverse_anchored_rejects_without_reverse_engine() {
2147        assert_strategy_with(
2148            "core",
2149            &[r"\w+Holmes$"],
2150            Config::new().dfa(false).hybrid(false),
2151        );
2152    }
2153
2154    #[test]
2155    fn reverse_suffix_accepts_prefix_without_internal_suffix() {
2156        assert_strategy("reverse suffix", &[r"\d+XYZ"]);
2157        assert_strategy("reverse suffix", &[r"[a-q][^u-z]{13}x"]);
2158    }
2159
2160    #[test]
2161    fn reverse_suffix_rejects_internal_suffix_with_word_boundary() {
2162        assert_strategy("core", &[r"\b\w+nn\b"]);
2163    }
2164
2165    #[test]
2166    fn reverse_suffix_accepts_disjoint_class_separator() {
2167        assert_strategy("reverse suffix", &[r"\w+\s+Holmes"]);
2168        assert_strategy("reverse suffix", &[r"\w+\d*\s+Holmes"]);
2169        assert_strategy("reverse suffix", &[r"(?:\w+|[.-]+)+\s+Holmes"]);
2170        assert_strategy("reverse suffix", &[r"\b\w+\s+Holmes"]);
2171        assert_strategy("reverse suffix", &[r"\w+\sHolmes"]);
2172        assert_strategy("reverse suffix", &[r"[a-z]+[0-9]+Holmes"]);
2173        assert_strategy("reverse suffix", &[r"(\w+)(\s+)Holmes"]);
2174        assert_strategy("reverse suffix", &[r"(?-u:[A-Za-z]+[0-9]+Holmes)"]);
2175    }
2176
2177    #[test]
2178    fn reverse_suffix_class_separator_is_conservative() {
2179        assert_strategy("core", &[r"\w+\w+Holmes"]);
2180        assert_strategy("core", &[r"\w+\s*Holmes"]);
2181        assert_strategy("core", &[r"[a-z]+[0-9a]+xyz"]);
2182        assert_strategy("core", &[r"[a-z]+[0-9]+a1"]);
2183        assert_strategy("core", &[r"(?:.abc)?[a]+[b]+c"]);
2184    }
2185
2186    #[test]
2187    fn reverse_suffix_rejects_safe_nfa_overlap() {
2188        assert_strategy("reverse inner", &[r"(a|aa)b"]);
2189    }
2190
2191    #[test]
2192    fn reverse_suffix_accepts_fixed_length_prefix() {
2193        assert_strategy("reverse suffix", &[r"[A-Z][0-9]XYZ"]);
2194    }
2195
2196    #[test]
2197    fn reverse_suffix_rejects_multiple_patterns_with_common_suffix() {
2198        assert_strategy("core", &[r"\d+XYZ", r"\w+XYZ"]);
2199    }
2200
2201    #[test]
2202    fn reverse_suffix_rejects_auto_prefilter_disabled() {
2203        assert_strategy_with(
2204            "core",
2205            &[r"\d+XYZ"],
2206            Config::new().auto_prefilter(false),
2207        );
2208    }
2209
2210    #[test]
2211    fn reverse_suffix_rejects_non_leftmost_first() {
2212        assert_strategy_with(
2213            "core",
2214            &[r"\d+XYZ"],
2215            Config::new().match_kind(MatchKind::All),
2216        );
2217    }
2218
2219    #[test]
2220    fn reverse_suffix_rejects_anchored_start() {
2221        assert_strategy("core", &[r"^\d+XYZ"]);
2222    }
2223
2224    #[test]
2225    fn reverse_suffix_rejects_variable_length_prefix() {
2226        assert_strategy("core", &[r"(?:[A-Za-z]ab)?b"]);
2227    }
2228
2229    #[test]
2230    fn reverse_suffix_rejects_without_reverse_engine() {
2231        assert_strategy_with(
2232            "core",
2233            &[r"\d+XYZ"],
2234            Config::new().dfa(false).hybrid(false),
2235        );
2236    }
2237
2238    #[test]
2239    fn reverse_suffix_rejects_existing_fast_prefilter() {
2240        assert_strategy("core", &[r"abc\w+XYZ"]);
2241    }
2242
2243    #[test]
2244    fn reverse_suffix_rejects_no_literal_suffix() {
2245        assert_strategy("core", &[r"[a-z]+"]);
2246    }
2247
2248    #[test]
2249    fn reverse_suffix_rejects_multiple_patterns_without_common_suffix() {
2250        assert_strategy("core", &[r"\d+XYZ", r"\w+ABC"]);
2251    }
2252
2253    #[test]
2254    fn reverse_suffix_rejects_multiple_patterns_when_first_looks_safe() {
2255        assert_strategy("core", &[r"\d+b", r".bb|b"]);
2256        assert_strategy("core", &[r"\d+b", r"ab"]);
2257    }
2258
2259    #[test]
2260    fn reverse_suffix_rejects_unsafe_overlap() {
2261        assert_strategy("core", &[r".abb|b"]);
2262        assert_strategy("core", &[r".bb|b"]);
2263    }
2264
2265    #[test]
2266    fn reverse_suffix_rejects_fully_absorbed_literal() {
2267        assert_strategy("core", &[r"(?:[a-wyz]{3}|[a-wyz]).b"]);
2268    }
2269
2270    #[test]
2271    fn reverse_suffix_rejects_disjoint_trailing_component() {
2272        assert_strategy("core", &[r"(?:[ac-z]{2}b[ac-z])?[ac-z]b"]);
2273    }
2274
2275    #[test]
2276    fn reverse_suffix_rejects_internal_candidate_before_absorber() {
2277        assert_strategy("core", &[r"(?:[a-z]cb|c)[a-z]*b"]);
2278    }
2279
2280    #[test]
2281    fn reverse_inner_accepts_disjoint_inner_literal() {
2282        assert_strategy("reverse inner", &[r"\w+@\w+"]);
2283        assert_strategy(
2284            "reverse inner",
2285            &[r"(?P<email>[.\pL]+@(?P<domain>[.\pL]+))"],
2286        );
2287    }
2288
2289    #[test]
2290    fn reverse_inner_accepts_fixed_length_prefix() {
2291        assert_strategy("reverse inner", &[r"[A-Z][0-9]@(foo|bar)"]);
2292    }
2293
2294    #[test]
2295    fn reverse_inner_accepts_disjoint_class_separator() {
2296        assert_strategy("reverse inner", &[r"\w+\s+Holmes\s+\w+"]);
2297        assert_strategy("reverse inner", &[r"\w+\d*\s+Holmes\s+\w+"]);
2298        assert_strategy("reverse inner", &[r"\b\w+\s+Holmes\s+\w+"]);
2299        if teddy_probably_available() {
2300            assert_strategy(
2301                "reverse inner",
2302                &[r"\w+\s+(?:Holmes|Watson)\s+\w+"],
2303            );
2304        }
2305    }
2306
2307    #[test]
2308    fn reverse_inner_accepts_leading_disjoint_class_separator() {
2309        assert_strategy("reverse inner", &[r"\s[A-Za-z]{0,12}ing\s"]);
2310        assert_strategy("reverse inner", &[r"\s[A-Za-z]*ing\s"]);
2311        assert_strategy("reverse inner", &[r"\b\s[A-Za-z]{0,12}ing\s"]);
2312        assert_strategy("reverse inner", &[r"(\s)([A-Za-z]{0,12})ing\s"]);
2313    }
2314
2315    #[test]
2316    fn reverse_inner_leading_class_separator_is_conservative() {
2317        assert_strategy("core", &[r"\s[\sA-Za-z]{0,12}ing\s"]);
2318        assert_strategy("core", &[r"\s*[A-Za-z]{0,12}ing\s"]);
2319    }
2320
2321    #[test]
2322    fn reverse_inner_rejects_auto_prefilter_disabled() {
2323        assert_strategy_with(
2324            "core",
2325            &[r"\w+@\w+"],
2326            Config::new().auto_prefilter(false),
2327        );
2328    }
2329
2330    #[test]
2331    fn reverse_inner_rejects_non_leftmost_first() {
2332        assert_strategy_with(
2333            "core",
2334            &[r"\w+@\w+"],
2335            Config::new().match_kind(MatchKind::All),
2336        );
2337    }
2338
2339    #[test]
2340    fn reverse_inner_rejects_anchored_start() {
2341        assert_strategy("core", &[r"^\w+@\w+"]);
2342    }
2343
2344    #[test]
2345    fn reverse_inner_rejects_without_reverse_engine() {
2346        assert_strategy_with(
2347            "core",
2348            &[r"\w+@\w+"],
2349            Config::new().dfa(false).hybrid(false),
2350        );
2351    }
2352
2353    #[test]
2354    fn reverse_inner_rejects_existing_fast_prefilter() {
2355        assert_strategy("core", &[r"abc\w+@\w+"]);
2356    }
2357
2358    #[test]
2359    fn reverse_inner_rejects_multiple_patterns() {
2360        assert_strategy("core", &[r"\w+@\w+", r"\w+#\w+"]);
2361    }
2362
2363    #[test]
2364    fn reverse_inner_rejects_no_top_level_concat() {
2365        assert_strategy("core", &[r"\w+"]);
2366    }
2367
2368    #[test]
2369    fn reverse_inner_rejects_unsafe_overlap() {
2370        assert_strategy("core", &[r"a.*@\w+"]);
2371    }
2372
2373    #[test]
2374    fn reverse_inner_rejects_literal_crossing_prefix_boundary() {
2375        assert_strategy("core", &[r"(?:[ac-z]{3}|[ac-z])(?:aba|baa)"]);
2376    }
2377
2378    #[test]
2379    fn reverse_inner_rejects_internal_separator_alignment() {
2380        assert_strategy("core", &[r"(?:.abc)?[a]+[b]+c.*"]);
2381    }
2382
2383    #[test]
2384    fn rebar_benchmarks() {
2385        assert_strategy("reverse suffix", &[r"[a-z]shing"]);
2386        assert_strategy("reverse suffix", &[r"[a-q][^u-z]{23}x"]);
2387    }
2388}