regex_automata/meta/
wrappers.rs

1/*!
2This module contains a boat load of wrappers around each of our internal regex
3engines. They encapsulate a few things:
4
51. The wrappers manage the conditional existence of the regex engine. Namely,
6the PikeVM is the only required regex engine. The rest are optional. These
7wrappers present a uniform API regardless of which engines are available. And
8availability might be determined by compile time features or by dynamic
9configuration via `meta::Config`. Encapsulating the conditional compilation
10features is in particular a huge simplification for the higher level code that
11composes these engines.
122. The wrappers manage construction of each engine, including skipping it if
13the engine is unavailable or configured to not be used.
143. The wrappers manage whether an engine *can* be used for a particular
15search configuration. For example, `BoundedBacktracker::get` only returns a
16backtracking engine when the haystack is bigger than the maximum supported
17length. The wrappers also sometimes take a position on when an engine *ought*
18to be used, but only in cases where the logic is extremely local to the engine
19itself. Otherwise, things like "choose between the backtracker and the one-pass
20DFA" are managed by the higher level meta strategy code.
21
22There are also corresponding wrappers for the various `Cache` types for each
23regex engine that needs them. If an engine is unavailable or not used, then a
24cache for it will *not* actually be allocated.
25*/
26
27use alloc::vec::Vec;
28
29use crate::{
30    meta::{
31        error::{BuildError, RetryError, RetryFailError},
32        regex::RegexInfo,
33    },
34    nfa::thompson::{pikevm, NFA},
35    util::{prefilter::Prefilter, primitives::NonMaxUsize},
36    HalfMatch, Input, Match, MatchKind, PatternID, PatternSet,
37};
38
39#[cfg(feature = "dfa-build")]
40use crate::dfa;
41#[cfg(feature = "dfa-onepass")]
42use crate::dfa::onepass;
43#[cfg(feature = "hybrid")]
44use crate::hybrid;
45#[cfg(feature = "nfa-backtrack")]
46use crate::nfa::thompson::backtrack;
47
48#[derive(Debug)]
49pub(crate) struct PikeVM(PikeVMEngine);
50
51impl PikeVM {
52    pub(crate) fn new(
53        info: &RegexInfo,
54        pre: Option<Prefilter>,
55        nfa: &NFA,
56    ) -> Result<PikeVM, BuildError> {
57        PikeVMEngine::new(info, pre, nfa).map(PikeVM)
58    }
59
60    pub(crate) fn create_cache(&self) -> PikeVMCache {
61        PikeVMCache::none()
62    }
63
64    #[cfg_attr(feature = "perf-inline", inline(always))]
65    pub(crate) fn get(&self) -> &PikeVMEngine {
66        &self.0
67    }
68}
69
70#[derive(Debug)]
71pub(crate) struct PikeVMEngine(pikevm::PikeVM);
72
73impl PikeVMEngine {
74    pub(crate) fn new(
75        info: &RegexInfo,
76        pre: Option<Prefilter>,
77        nfa: &NFA,
78    ) -> Result<PikeVMEngine, BuildError> {
79        let pikevm_config = pikevm::Config::new()
80            .match_kind(info.config().get_match_kind())
81            .prefilter(pre);
82        let engine = pikevm::Builder::new()
83            .configure(pikevm_config)
84            .build_from_nfa(nfa.clone())
85            .map_err(BuildError::nfa)?;
86        debug!("PikeVM built");
87        Ok(PikeVMEngine(engine))
88    }
89
90    #[cfg_attr(feature = "perf-inline", inline(always))]
91    pub(crate) fn is_match(
92        &self,
93        cache: &mut PikeVMCache,
94        input: &Input<'_>,
95    ) -> bool {
96        self.0.is_match(cache.get(&self.0), input.clone())
97    }
98
99    #[cfg_attr(feature = "perf-inline", inline(always))]
100    pub(crate) fn search_slots(
101        &self,
102        cache: &mut PikeVMCache,
103        input: &Input<'_>,
104        slots: &mut [Option<NonMaxUsize>],
105    ) -> Option<PatternID> {
106        self.0.search_slots(cache.get(&self.0), input, slots)
107    }
108
109    #[cfg_attr(feature = "perf-inline", inline(always))]
110    pub(crate) fn which_overlapping_matches(
111        &self,
112        cache: &mut PikeVMCache,
113        input: &Input<'_>,
114        patset: &mut PatternSet,
115    ) {
116        self.0.which_overlapping_matches(cache.get(&self.0), input, patset)
117    }
118}
119
120#[derive(Clone, Debug)]
121pub(crate) struct PikeVMCache(Option<pikevm::Cache>);
122
123impl PikeVMCache {
124    pub(crate) fn none() -> PikeVMCache {
125        PikeVMCache(None)
126    }
127
128    pub(crate) fn reset(&mut self, builder: &PikeVM) {
129        self.get(&builder.get().0).reset(&builder.get().0);
130    }
131
132    pub(crate) fn memory_usage(&self) -> usize {
133        self.0.as_ref().map_or(0, |c| c.memory_usage())
134    }
135
136    fn get(&mut self, vm: &pikevm::PikeVM) -> &mut pikevm::Cache {
137        self.0.get_or_insert_with(|| vm.create_cache())
138    }
139}
140
141#[derive(Debug)]
142pub(crate) struct BoundedBacktracker(Option<BoundedBacktrackerEngine>);
143
144impl BoundedBacktracker {
145    pub(crate) fn new(
146        info: &RegexInfo,
147        pre: Option<Prefilter>,
148        nfa: &NFA,
149    ) -> Result<BoundedBacktracker, BuildError> {
150        BoundedBacktrackerEngine::new(info, pre, nfa).map(BoundedBacktracker)
151    }
152
153    pub(crate) fn create_cache(&self) -> BoundedBacktrackerCache {
154        BoundedBacktrackerCache::none()
155    }
156
157    #[cfg_attr(feature = "perf-inline", inline(always))]
158    pub(crate) fn get(
159        &self,
160        input: &Input<'_>,
161    ) -> Option<&BoundedBacktrackerEngine> {
162        let engine = self.0.as_ref()?;
163        // It is difficult to make the backtracker give up early if it is
164        // guaranteed to eventually wind up in a match state. This is because
165        // of the greedy nature of a backtracker: it just blindly mushes
166        // forward. Every other regex engine is able to give up more quickly,
167        // so even if the backtracker might be able to zip through faster than
168        // (say) the PikeVM, we prefer the theoretical benefit that some other
169        // engine might be able to scan much less of the haystack than the
170        // backtracker.
171        //
172        // Now, if the haystack is really short already, then we allow the
173        // backtracker to run. (This hasn't been litigated quantitatively with
174        // benchmarks. Just a hunch.)
175        if input.get_earliest() && input.haystack().len() > 128 {
176            return None;
177        }
178        // If the backtracker is just going to return an error because the
179        // haystack is too long, then obviously do not use it.
180        if input.get_span().len() > engine.max_haystack_len() {
181            return None;
182        }
183        Some(engine)
184    }
185}
186
187#[derive(Debug)]
188pub(crate) struct BoundedBacktrackerEngine(
189    #[cfg(feature = "nfa-backtrack")] backtrack::BoundedBacktracker,
190    #[cfg(not(feature = "nfa-backtrack"))] (),
191);
192
193impl BoundedBacktrackerEngine {
194    pub(crate) fn new(
195        info: &RegexInfo,
196        pre: Option<Prefilter>,
197        nfa: &NFA,
198    ) -> Result<Option<BoundedBacktrackerEngine>, BuildError> {
199        #[cfg(feature = "nfa-backtrack")]
200        {
201            if !info.config().get_backtrack()
202                || info.config().get_match_kind() != MatchKind::LeftmostFirst
203            {
204                return Ok(None);
205            }
206            let backtrack_config = backtrack::Config::new().prefilter(pre);
207            let engine = backtrack::Builder::new()
208                .configure(backtrack_config)
209                .build_from_nfa(nfa.clone())
210                .map_err(BuildError::nfa)?;
211            debug!(
212                "BoundedBacktracker built (max haystack length: {:?})",
213                engine.max_haystack_len()
214            );
215            Ok(Some(BoundedBacktrackerEngine(engine)))
216        }
217        #[cfg(not(feature = "nfa-backtrack"))]
218        {
219            Ok(None)
220        }
221    }
222
223    #[cfg_attr(feature = "perf-inline", inline(always))]
224    pub(crate) fn is_match(
225        &self,
226        cache: &mut BoundedBacktrackerCache,
227        input: &Input<'_>,
228    ) -> bool {
229        #[cfg(feature = "nfa-backtrack")]
230        {
231            // OK because we only permit access to this engine when we know
232            // the haystack is short enough for the backtracker to run without
233            // reporting an error.
234            self.0.try_is_match(cache.get(&self.0), input.clone()).unwrap()
235        }
236        #[cfg(not(feature = "nfa-backtrack"))]
237        {
238            // Impossible to reach because this engine is never constructed
239            // if the requisite features aren't enabled.
240            unreachable!()
241        }
242    }
243
244    #[cfg_attr(feature = "perf-inline", inline(always))]
245    pub(crate) fn search_slots(
246        &self,
247        cache: &mut BoundedBacktrackerCache,
248        input: &Input<'_>,
249        slots: &mut [Option<NonMaxUsize>],
250    ) -> Option<PatternID> {
251        #[cfg(feature = "nfa-backtrack")]
252        {
253            // OK because we only permit access to this engine when we know
254            // the haystack is short enough for the backtracker to run without
255            // reporting an error.
256            self.0.try_search_slots(cache.get(&self.0), input, slots).unwrap()
257        }
258        #[cfg(not(feature = "nfa-backtrack"))]
259        {
260            // Impossible to reach because this engine is never constructed
261            // if the requisite features aren't enabled.
262            unreachable!()
263        }
264    }
265
266    #[cfg_attr(feature = "perf-inline", inline(always))]
267    fn max_haystack_len(&self) -> usize {
268        #[cfg(feature = "nfa-backtrack")]
269        {
270            self.0.max_haystack_len()
271        }
272        #[cfg(not(feature = "nfa-backtrack"))]
273        {
274            // Impossible to reach because this engine is never constructed
275            // if the requisite features aren't enabled.
276            unreachable!()
277        }
278    }
279}
280
281#[derive(Clone, Debug)]
282pub(crate) struct BoundedBacktrackerCache(
283    #[cfg(feature = "nfa-backtrack")] Option<backtrack::Cache>,
284    #[cfg(not(feature = "nfa-backtrack"))] (),
285);
286
287impl BoundedBacktrackerCache {
288    pub(crate) fn none() -> BoundedBacktrackerCache {
289        #[cfg(feature = "nfa-backtrack")]
290        {
291            BoundedBacktrackerCache(None)
292        }
293        #[cfg(not(feature = "nfa-backtrack"))]
294        {
295            BoundedBacktrackerCache(())
296        }
297    }
298
299    pub(crate) fn reset(&mut self, builder: &BoundedBacktracker) {
300        #[cfg(feature = "nfa-backtrack")]
301        if let Some(ref e) = builder.0 {
302            self.get(&e.0).reset(&e.0);
303        }
304    }
305
306    pub(crate) fn memory_usage(&self) -> usize {
307        #[cfg(feature = "nfa-backtrack")]
308        {
309            self.0.as_ref().map_or(0, |c| c.memory_usage())
310        }
311        #[cfg(not(feature = "nfa-backtrack"))]
312        {
313            0
314        }
315    }
316
317    #[cfg(feature = "nfa-backtrack")]
318    fn get(
319        &mut self,
320        bb: &backtrack::BoundedBacktracker,
321    ) -> &mut backtrack::Cache {
322        self.0.get_or_insert_with(|| bb.create_cache())
323    }
324}
325
326#[derive(Debug)]
327pub(crate) struct OnePass(Option<OnePassEngine>);
328
329impl OnePass {
330    pub(crate) fn new(info: &RegexInfo, nfa: &NFA) -> OnePass {
331        OnePass(OnePassEngine::new(info, nfa))
332    }
333
334    pub(crate) fn create_cache(&self) -> OnePassCache {
335        OnePassCache::new(self)
336    }
337
338    #[cfg_attr(feature = "perf-inline", inline(always))]
339    pub(crate) fn get(&self, input: &Input<'_>) -> Option<&OnePassEngine> {
340        let engine = self.0.as_ref()?;
341        if !input.get_anchored().is_anchored()
342            && !engine.get_nfa().is_always_start_anchored()
343        {
344            return None;
345        }
346        Some(engine)
347    }
348
349    pub(crate) fn memory_usage(&self) -> usize {
350        self.0.as_ref().map_or(0, |e| e.memory_usage())
351    }
352}
353
354#[derive(Debug)]
355pub(crate) struct OnePassEngine(
356    #[cfg(feature = "dfa-onepass")] onepass::DFA,
357    #[cfg(not(feature = "dfa-onepass"))] (),
358);
359
360impl OnePassEngine {
361    pub(crate) fn new(info: &RegexInfo, nfa: &NFA) -> Option<OnePassEngine> {
362        #[cfg(feature = "dfa-onepass")]
363        {
364            if !info.config().get_onepass() {
365                return None;
366            }
367            // In order to even attempt building a one-pass DFA, we require
368            // that we either have at least one explicit capturing group or
369            // there's a Unicode word boundary somewhere. If we don't have
370            // either of these things, then the lazy DFA will almost certainly
371            // be usable and be much faster. The only case where it might
372            // not is if the lazy DFA isn't utilizing its cache effectively,
373            // but in those cases, the underlying regex is almost certainly
374            // not one-pass or is too big to fit within the current one-pass
375            // implementation limits.
376            if info.props_union().explicit_captures_len() == 0
377                && !info.props_union().look_set().contains_word_unicode()
378            {
379                debug!("not building OnePass because it isn't worth it");
380                return None;
381            }
382            let onepass_config = onepass::Config::new()
383                .match_kind(info.config().get_match_kind())
384                // Like for the lazy DFA, we unconditionally enable this
385                // because it doesn't cost much and makes the API more
386                // flexible.
387                .starts_for_each_pattern(true)
388                .byte_classes(info.config().get_byte_classes())
389                .size_limit(info.config().get_onepass_size_limit());
390            let result = onepass::Builder::new()
391                .configure(onepass_config)
392                .build_from_nfa(nfa.clone());
393            let engine = match result {
394                Ok(engine) => engine,
395                Err(_err) => {
396                    debug!("OnePass failed to build: {_err}");
397                    return None;
398                }
399            };
400            debug!("OnePass built, {} bytes", engine.memory_usage());
401            Some(OnePassEngine(engine))
402        }
403        #[cfg(not(feature = "dfa-onepass"))]
404        {
405            None
406        }
407    }
408
409    #[cfg_attr(feature = "perf-inline", inline(always))]
410    pub(crate) fn search_slots(
411        &self,
412        cache: &mut OnePassCache,
413        input: &Input<'_>,
414        slots: &mut [Option<NonMaxUsize>],
415    ) -> Option<PatternID> {
416        #[cfg(feature = "dfa-onepass")]
417        {
418            // OK because we only permit getting a OnePassEngine when we know
419            // the search is anchored and thus an error cannot occur.
420            self.0
421                .try_search_slots(cache.0.as_mut().unwrap(), input, slots)
422                .unwrap()
423        }
424        #[cfg(not(feature = "dfa-onepass"))]
425        {
426            // Impossible to reach because this engine is never constructed
427            // if the requisite features aren't enabled.
428            unreachable!()
429        }
430    }
431
432    pub(crate) fn memory_usage(&self) -> usize {
433        #[cfg(feature = "dfa-onepass")]
434        {
435            self.0.memory_usage()
436        }
437        #[cfg(not(feature = "dfa-onepass"))]
438        {
439            // Impossible to reach because this engine is never constructed
440            // if the requisite features aren't enabled.
441            unreachable!()
442        }
443    }
444
445    #[cfg_attr(feature = "perf-inline", inline(always))]
446    fn get_nfa(&self) -> &NFA {
447        #[cfg(feature = "dfa-onepass")]
448        {
449            self.0.get_nfa()
450        }
451        #[cfg(not(feature = "dfa-onepass"))]
452        {
453            // Impossible to reach because this engine is never constructed
454            // if the requisite features aren't enabled.
455            unreachable!()
456        }
457    }
458}
459
460#[derive(Clone, Debug)]
461pub(crate) struct OnePassCache(
462    #[cfg(feature = "dfa-onepass")] Option<onepass::Cache>,
463    #[cfg(not(feature = "dfa-onepass"))] (),
464);
465
466impl OnePassCache {
467    pub(crate) fn none() -> OnePassCache {
468        #[cfg(feature = "dfa-onepass")]
469        {
470            OnePassCache(None)
471        }
472        #[cfg(not(feature = "dfa-onepass"))]
473        {
474            OnePassCache(())
475        }
476    }
477
478    pub(crate) fn new(builder: &OnePass) -> OnePassCache {
479        #[cfg(feature = "dfa-onepass")]
480        {
481            OnePassCache(builder.0.as_ref().map(|e| e.0.create_cache()))
482        }
483        #[cfg(not(feature = "dfa-onepass"))]
484        {
485            OnePassCache(())
486        }
487    }
488
489    pub(crate) fn reset(&mut self, builder: &OnePass) {
490        #[cfg(feature = "dfa-onepass")]
491        if let Some(ref e) = builder.0 {
492            self.0.as_mut().unwrap().reset(&e.0);
493        }
494    }
495
496    pub(crate) fn memory_usage(&self) -> usize {
497        #[cfg(feature = "dfa-onepass")]
498        {
499            self.0.as_ref().map_or(0, |c| c.memory_usage())
500        }
501        #[cfg(not(feature = "dfa-onepass"))]
502        {
503            0
504        }
505    }
506}
507
508#[derive(Debug)]
509pub(crate) struct Hybrid(Option<HybridEngine>);
510
511impl Hybrid {
512    pub(crate) fn none() -> Hybrid {
513        Hybrid(None)
514    }
515
516    pub(crate) fn new(
517        info: &RegexInfo,
518        pre: Option<Prefilter>,
519        nfa: &NFA,
520        nfarev: &NFA,
521    ) -> Hybrid {
522        Hybrid(HybridEngine::new(info, pre, nfa, nfarev))
523    }
524
525    pub(crate) fn create_cache(&self) -> HybridCache {
526        HybridCache::new(self)
527    }
528
529    #[cfg_attr(feature = "perf-inline", inline(always))]
530    pub(crate) fn get(&self, _input: &Input<'_>) -> Option<&HybridEngine> {
531        let engine = self.0.as_ref()?;
532        Some(engine)
533    }
534
535    pub(crate) fn is_some(&self) -> bool {
536        self.0.is_some()
537    }
538}
539
540#[derive(Debug)]
541pub(crate) struct HybridEngine(
542    #[cfg(feature = "hybrid")] hybrid::regex::Regex,
543    #[cfg(not(feature = "hybrid"))] (),
544);
545
546impl HybridEngine {
547    pub(crate) fn new(
548        info: &RegexInfo,
549        pre: Option<Prefilter>,
550        nfa: &NFA,
551        nfarev: &NFA,
552    ) -> Option<HybridEngine> {
553        #[cfg(feature = "hybrid")]
554        {
555            if !info.config().get_hybrid() {
556                return None;
557            }
558            let dfa_config = hybrid::dfa::Config::new()
559                .match_kind(info.config().get_match_kind())
560                .prefilter(pre.clone())
561                // Enabling this is necessary for ensuring we can service any
562                // kind of 'Input' search without error. For the lazy DFA,
563                // this is not particularly costly, since the start states are
564                // generated lazily.
565                .starts_for_each_pattern(true)
566                .byte_classes(info.config().get_byte_classes())
567                .unicode_word_boundary(true)
568                .specialize_start_states(pre.is_some())
569                .cache_capacity(info.config().get_hybrid_cache_capacity())
570                // This makes it possible for building a lazy DFA to
571                // fail even though the NFA has already been built. Namely,
572                // if the cache capacity is too small to fit some minimum
573                // number of states (which is small, like 4 or 5), then the
574                // DFA will refuse to build.
575                //
576                // We shouldn't enable this to make building always work, since
577                // this could cause the allocation of a cache bigger than the
578                // provided capacity amount.
579                //
580                // This is effectively the only reason why building a lazy DFA
581                // could fail. If it does, then we simply suppress the error
582                // and return None.
583                .skip_cache_capacity_check(false)
584                // This and enabling heuristic Unicode word boundary support
585                // above make it so the lazy DFA can quit at match time.
586                .minimum_cache_clear_count(Some(3))
587                .minimum_bytes_per_state(Some(10));
588            let result = hybrid::dfa::Builder::new()
589                .configure(dfa_config.clone())
590                .build_from_nfa(nfa.clone());
591            let fwd = match result {
592                Ok(fwd) => fwd,
593                Err(_err) => {
594                    debug!("forward lazy DFA failed to build: {_err}");
595                    return None;
596                }
597            };
598            let result = hybrid::dfa::Builder::new()
599                .configure(
600                    dfa_config
601                        .clone()
602                        .match_kind(MatchKind::All)
603                        .prefilter(None)
604                        .specialize_start_states(false),
605                )
606                .build_from_nfa(nfarev.clone());
607            let rev = match result {
608                Ok(rev) => rev,
609                Err(_err) => {
610                    debug!("reverse lazy DFA failed to build: {_err}");
611                    return None;
612                }
613            };
614            let engine =
615                hybrid::regex::Builder::new().build_from_dfas(fwd, rev);
616            debug!("lazy DFA built");
617            Some(HybridEngine(engine))
618        }
619        #[cfg(not(feature = "hybrid"))]
620        {
621            None
622        }
623    }
624
625    #[cfg_attr(feature = "perf-inline", inline(always))]
626    pub(crate) fn try_search(
627        &self,
628        cache: &mut HybridCache,
629        input: &Input<'_>,
630    ) -> Result<Option<Match>, RetryFailError> {
631        #[cfg(feature = "hybrid")]
632        {
633            let cache = cache.0.as_mut().unwrap();
634            self.0.try_search(cache, input).map_err(|e| e.into())
635        }
636        #[cfg(not(feature = "hybrid"))]
637        {
638            // Impossible to reach because this engine is never constructed
639            // if the requisite features aren't enabled.
640            unreachable!()
641        }
642    }
643
644    #[cfg_attr(feature = "perf-inline", inline(always))]
645    pub(crate) fn try_search_half_fwd(
646        &self,
647        cache: &mut HybridCache,
648        input: &Input<'_>,
649    ) -> Result<Option<HalfMatch>, RetryFailError> {
650        #[cfg(feature = "hybrid")]
651        {
652            let fwd = self.0.forward();
653            let mut fwdcache = cache.0.as_mut().unwrap().as_parts_mut().0;
654            fwd.try_search_fwd(&mut fwdcache, input).map_err(|e| e.into())
655        }
656        #[cfg(not(feature = "hybrid"))]
657        {
658            // Impossible to reach because this engine is never constructed
659            // if the requisite features aren't enabled.
660            unreachable!()
661        }
662    }
663
664    #[cfg_attr(feature = "perf-inline", inline(always))]
665    pub(crate) fn try_search_half_fwd_stopat(
666        &self,
667        cache: &mut HybridCache,
668        input: &Input<'_>,
669    ) -> Result<Result<HalfMatch, usize>, RetryFailError> {
670        #[cfg(feature = "hybrid")]
671        {
672            let dfa = self.0.forward();
673            let mut cache = cache.0.as_mut().unwrap().as_parts_mut().0;
674            crate::meta::stopat::hybrid_try_search_half_fwd(
675                dfa, &mut cache, input,
676            )
677        }
678        #[cfg(not(feature = "hybrid"))]
679        {
680            // Impossible to reach because this engine is never constructed
681            // if the requisite features aren't enabled.
682            unreachable!()
683        }
684    }
685
686    #[cfg_attr(feature = "perf-inline", inline(always))]
687    pub(crate) fn try_search_half_rev(
688        &self,
689        cache: &mut HybridCache,
690        input: &Input<'_>,
691    ) -> Result<Option<HalfMatch>, RetryFailError> {
692        #[cfg(feature = "hybrid")]
693        {
694            let rev = self.0.reverse();
695            let mut revcache = cache.0.as_mut().unwrap().as_parts_mut().1;
696            rev.try_search_rev(&mut revcache, input).map_err(|e| e.into())
697        }
698        #[cfg(not(feature = "hybrid"))]
699        {
700            // Impossible to reach because this engine is never constructed
701            // if the requisite features aren't enabled.
702            unreachable!()
703        }
704    }
705
706    #[cfg_attr(feature = "perf-inline", inline(always))]
707    pub(crate) fn try_search_half_rev_limited(
708        &self,
709        cache: &mut HybridCache,
710        input: &Input<'_>,
711        min_start: usize,
712    ) -> Result<Option<HalfMatch>, RetryError> {
713        #[cfg(feature = "hybrid")]
714        {
715            let dfa = self.0.reverse();
716            let mut cache = cache.0.as_mut().unwrap().as_parts_mut().1;
717            crate::meta::limited::hybrid_try_search_half_rev(
718                dfa, &mut cache, input, min_start,
719            )
720        }
721        #[cfg(not(feature = "hybrid"))]
722        {
723            // Impossible to reach because this engine is never constructed
724            // if the requisite features aren't enabled.
725            unreachable!()
726        }
727    }
728
729    #[inline]
730    pub(crate) fn try_which_overlapping_matches(
731        &self,
732        cache: &mut HybridCache,
733        input: &Input<'_>,
734        patset: &mut PatternSet,
735    ) -> Result<(), RetryFailError> {
736        #[cfg(feature = "hybrid")]
737        {
738            let fwd = self.0.forward();
739            let mut fwdcache = cache.0.as_mut().unwrap().as_parts_mut().0;
740            fwd.try_which_overlapping_matches(&mut fwdcache, input, patset)
741                .map_err(|e| e.into())
742        }
743        #[cfg(not(feature = "hybrid"))]
744        {
745            // Impossible to reach because this engine is never constructed
746            // if the requisite features aren't enabled.
747            unreachable!()
748        }
749    }
750}
751
752#[derive(Clone, Debug)]
753pub(crate) struct HybridCache(
754    #[cfg(feature = "hybrid")] Option<hybrid::regex::Cache>,
755    #[cfg(not(feature = "hybrid"))] (),
756);
757
758impl HybridCache {
759    pub(crate) fn none() -> HybridCache {
760        #[cfg(feature = "hybrid")]
761        {
762            HybridCache(None)
763        }
764        #[cfg(not(feature = "hybrid"))]
765        {
766            HybridCache(())
767        }
768    }
769
770    pub(crate) fn new(builder: &Hybrid) -> HybridCache {
771        #[cfg(feature = "hybrid")]
772        {
773            HybridCache(builder.0.as_ref().map(|e| e.0.create_cache()))
774        }
775        #[cfg(not(feature = "hybrid"))]
776        {
777            HybridCache(())
778        }
779    }
780
781    pub(crate) fn reset(&mut self, builder: &Hybrid) {
782        #[cfg(feature = "hybrid")]
783        if let Some(ref e) = builder.0 {
784            self.0.as_mut().unwrap().reset(&e.0);
785        }
786    }
787
788    pub(crate) fn memory_usage(&self) -> usize {
789        #[cfg(feature = "hybrid")]
790        {
791            self.0.as_ref().map_or(0, |c| c.memory_usage())
792        }
793        #[cfg(not(feature = "hybrid"))]
794        {
795            0
796        }
797    }
798}
799
800#[derive(Debug)]
801pub(crate) struct DFA(Option<DFAEngine>);
802
803impl DFA {
804    pub(crate) fn none() -> DFA {
805        DFA(None)
806    }
807
808    pub(crate) fn new(
809        info: &RegexInfo,
810        pre: Option<Prefilter>,
811        nfa: &NFA,
812        nfarev: &NFA,
813    ) -> DFA {
814        DFA(DFAEngine::new(info, pre, nfa, nfarev))
815    }
816
817    #[cfg_attr(feature = "perf-inline", inline(always))]
818    pub(crate) fn get(&self, _input: &Input<'_>) -> Option<&DFAEngine> {
819        let engine = self.0.as_ref()?;
820        Some(engine)
821    }
822
823    pub(crate) fn is_some(&self) -> bool {
824        self.0.is_some()
825    }
826
827    pub(crate) fn memory_usage(&self) -> usize {
828        self.0.as_ref().map_or(0, |e| e.memory_usage())
829    }
830}
831
832#[derive(Debug)]
833pub(crate) struct DFAEngine(
834    #[cfg(feature = "dfa-build")] dfa::regex::Regex,
835    #[cfg(not(feature = "dfa-build"))] (),
836);
837
838impl DFAEngine {
839    pub(crate) fn new(
840        info: &RegexInfo,
841        pre: Option<Prefilter>,
842        nfa: &NFA,
843        nfarev: &NFA,
844    ) -> Option<DFAEngine> {
845        #[cfg(feature = "dfa-build")]
846        {
847            if !info.config().get_dfa() {
848                return None;
849            }
850            // If our NFA is anything but small, don't even bother with a DFA.
851            if let Some(state_limit) = info.config().get_dfa_state_limit() {
852                if nfa.states().len() > state_limit {
853                    debug!(
854                        "skipping full DFA because NFA has {} states, \
855                         which exceeds the heuristic limit of {}",
856                        nfa.states().len(),
857                        state_limit,
858                    );
859                    return None;
860                }
861            }
862            // We cut the size limit in four because the total heap used by
863            // DFA construction is determinization aux memory and the DFA
864            // itself, and those things are configured independently in the
865            // lower level DFA builder API. And then split that in two because
866            // of forward and reverse DFAs.
867            let size_limit = info.config().get_dfa_size_limit().map(|n| n / 4);
868            let dfa_config = dfa::dense::Config::new()
869                .match_kind(info.config().get_match_kind())
870                .prefilter(pre.clone())
871                // Enabling this is necessary for ensuring we can service any
872                // kind of 'Input' search without error. For the full DFA, this
873                // can be quite costly. But since we have such a small bound
874                // on the size of the DFA, in practice, any multi-regexes are
875                // probably going to blow the limit anyway.
876                .starts_for_each_pattern(true)
877                .byte_classes(info.config().get_byte_classes())
878                .unicode_word_boundary(true)
879                .specialize_start_states(pre.is_some())
880                .determinize_size_limit(size_limit)
881                .dfa_size_limit(size_limit);
882            let result = dfa::dense::Builder::new()
883                .configure(dfa_config.clone())
884                .build_from_nfa(&nfa);
885            let fwd = match result {
886                Ok(fwd) => fwd,
887                Err(_err) => {
888                    debug!("forward full DFA failed to build: {_err}");
889                    return None;
890                }
891            };
892            let result = dfa::dense::Builder::new()
893                .configure(
894                    dfa_config
895                        .clone()
896                        // We never need unanchored reverse searches, so
897                        // there's no point in building it into the DFA, which
898                        // WILL take more space. (This isn't done for the lazy
899                        // DFA because the DFA is, well, lazy. It doesn't pay
900                        // the cost for supporting unanchored searches unless
901                        // you actually do an unanchored search, which we
902                        // don't.)
903                        .start_kind(dfa::StartKind::Anchored)
904                        .match_kind(MatchKind::All)
905                        .prefilter(None)
906                        .specialize_start_states(false),
907                )
908                .build_from_nfa(&nfarev);
909            let rev = match result {
910                Ok(rev) => rev,
911                Err(_err) => {
912                    debug!("reverse full DFA failed to build: {_err}");
913                    return None;
914                }
915            };
916            let engine = dfa::regex::Builder::new().build_from_dfas(fwd, rev);
917            debug!(
918                "fully compiled forward and reverse DFAs built, {} bytes",
919                engine.forward().memory_usage()
920                    + engine.reverse().memory_usage(),
921            );
922            Some(DFAEngine(engine))
923        }
924        #[cfg(not(feature = "dfa-build"))]
925        {
926            None
927        }
928    }
929
930    #[cfg_attr(feature = "perf-inline", inline(always))]
931    pub(crate) fn try_search(
932        &self,
933        input: &Input<'_>,
934    ) -> Result<Option<Match>, RetryFailError> {
935        #[cfg(feature = "dfa-build")]
936        {
937            self.0.try_search(input).map_err(|e| e.into())
938        }
939        #[cfg(not(feature = "dfa-build"))]
940        {
941            // Impossible to reach because this engine is never constructed
942            // if the requisite features aren't enabled.
943            unreachable!()
944        }
945    }
946
947    #[cfg_attr(feature = "perf-inline", inline(always))]
948    pub(crate) fn try_search_half_fwd(
949        &self,
950        input: &Input<'_>,
951    ) -> Result<Option<HalfMatch>, RetryFailError> {
952        #[cfg(feature = "dfa-build")]
953        {
954            use crate::dfa::Automaton;
955            self.0.forward().try_search_fwd(input).map_err(|e| e.into())
956        }
957        #[cfg(not(feature = "dfa-build"))]
958        {
959            // Impossible to reach because this engine is never constructed
960            // if the requisite features aren't enabled.
961            unreachable!()
962        }
963    }
964
965    #[cfg_attr(feature = "perf-inline", inline(always))]
966    pub(crate) fn try_search_half_fwd_stopat(
967        &self,
968        input: &Input<'_>,
969    ) -> Result<Result<HalfMatch, usize>, RetryFailError> {
970        #[cfg(feature = "dfa-build")]
971        {
972            let dfa = self.0.forward();
973            crate::meta::stopat::dfa_try_search_half_fwd(dfa, input)
974        }
975        #[cfg(not(feature = "dfa-build"))]
976        {
977            // Impossible to reach because this engine is never constructed
978            // if the requisite features aren't enabled.
979            unreachable!()
980        }
981    }
982
983    #[cfg_attr(feature = "perf-inline", inline(always))]
984    pub(crate) fn try_search_half_rev(
985        &self,
986        input: &Input<'_>,
987    ) -> Result<Option<HalfMatch>, RetryFailError> {
988        #[cfg(feature = "dfa-build")]
989        {
990            use crate::dfa::Automaton;
991            self.0.reverse().try_search_rev(&input).map_err(|e| e.into())
992        }
993        #[cfg(not(feature = "dfa-build"))]
994        {
995            // Impossible to reach because this engine is never constructed
996            // if the requisite features aren't enabled.
997            unreachable!()
998        }
999    }
1000
1001    #[cfg_attr(feature = "perf-inline", inline(always))]
1002    pub(crate) fn try_search_half_rev_limited(
1003        &self,
1004        input: &Input<'_>,
1005        min_start: usize,
1006    ) -> Result<Option<HalfMatch>, RetryError> {
1007        #[cfg(feature = "dfa-build")]
1008        {
1009            let dfa = self.0.reverse();
1010            crate::meta::limited::dfa_try_search_half_rev(
1011                dfa, input, min_start,
1012            )
1013        }
1014        #[cfg(not(feature = "dfa-build"))]
1015        {
1016            // Impossible to reach because this engine is never constructed
1017            // if the requisite features aren't enabled.
1018            unreachable!()
1019        }
1020    }
1021
1022    #[inline]
1023    pub(crate) fn try_which_overlapping_matches(
1024        &self,
1025        input: &Input<'_>,
1026        patset: &mut PatternSet,
1027    ) -> Result<(), RetryFailError> {
1028        #[cfg(feature = "dfa-build")]
1029        {
1030            use crate::dfa::Automaton;
1031            self.0
1032                .forward()
1033                .try_which_overlapping_matches(input, patset)
1034                .map_err(|e| e.into())
1035        }
1036        #[cfg(not(feature = "dfa-build"))]
1037        {
1038            // Impossible to reach because this engine is never constructed
1039            // if the requisite features aren't enabled.
1040            unreachable!()
1041        }
1042    }
1043
1044    pub(crate) fn memory_usage(&self) -> usize {
1045        #[cfg(feature = "dfa-build")]
1046        {
1047            self.0.forward().memory_usage() + self.0.reverse().memory_usage()
1048        }
1049        #[cfg(not(feature = "dfa-build"))]
1050        {
1051            // Impossible to reach because this engine is never constructed
1052            // if the requisite features aren't enabled.
1053            unreachable!()
1054        }
1055    }
1056}
1057
1058#[derive(Debug)]
1059pub(crate) struct ReverseHybrid(Option<ReverseHybridEngine>);
1060
1061impl ReverseHybrid {
1062    pub(crate) fn none() -> ReverseHybrid {
1063        ReverseHybrid(None)
1064    }
1065
1066    pub(crate) fn new(info: &RegexInfo, nfarev: &NFA) -> ReverseHybrid {
1067        ReverseHybrid(ReverseHybridEngine::new(info, nfarev))
1068    }
1069
1070    pub(crate) fn create_cache(&self) -> ReverseHybridCache {
1071        ReverseHybridCache::new(self)
1072    }
1073
1074    #[cfg_attr(feature = "perf-inline", inline(always))]
1075    pub(crate) fn get(
1076        &self,
1077        _input: &Input<'_>,
1078    ) -> Option<&ReverseHybridEngine> {
1079        let engine = self.0.as_ref()?;
1080        Some(engine)
1081    }
1082}
1083
1084#[derive(Debug)]
1085pub(crate) struct ReverseHybridEngine(
1086    #[cfg(feature = "hybrid")] hybrid::dfa::DFA,
1087    #[cfg(not(feature = "hybrid"))] (),
1088);
1089
1090impl ReverseHybridEngine {
1091    pub(crate) fn new(
1092        info: &RegexInfo,
1093        nfarev: &NFA,
1094    ) -> Option<ReverseHybridEngine> {
1095        #[cfg(feature = "hybrid")]
1096        {
1097            if !info.config().get_hybrid() {
1098                return None;
1099            }
1100            // Since we only use this for reverse searches, we can hard-code
1101            // a number of things like match semantics, prefilters, starts
1102            // for each pattern and so on.
1103            let dfa_config = hybrid::dfa::Config::new()
1104                .match_kind(MatchKind::All)
1105                .prefilter(None)
1106                .starts_for_each_pattern(false)
1107                .byte_classes(info.config().get_byte_classes())
1108                .unicode_word_boundary(true)
1109                .specialize_start_states(false)
1110                .cache_capacity(info.config().get_hybrid_cache_capacity())
1111                .skip_cache_capacity_check(false)
1112                .minimum_cache_clear_count(Some(3))
1113                .minimum_bytes_per_state(Some(10));
1114            let result = hybrid::dfa::Builder::new()
1115                .configure(dfa_config)
1116                .build_from_nfa(nfarev.clone());
1117            let rev = match result {
1118                Ok(rev) => rev,
1119                Err(_err) => {
1120                    debug!("lazy reverse DFA failed to build: {_err}");
1121                    return None;
1122                }
1123            };
1124            debug!("lazy reverse DFA built");
1125            Some(ReverseHybridEngine(rev))
1126        }
1127        #[cfg(not(feature = "hybrid"))]
1128        {
1129            None
1130        }
1131    }
1132
1133    #[cfg_attr(feature = "perf-inline", inline(always))]
1134    pub(crate) fn try_search_half_rev_limited(
1135        &self,
1136        cache: &mut ReverseHybridCache,
1137        input: &Input<'_>,
1138        min_start: usize,
1139    ) -> Result<Option<HalfMatch>, RetryError> {
1140        #[cfg(feature = "hybrid")]
1141        {
1142            let dfa = &self.0;
1143            let mut cache = cache.0.as_mut().unwrap();
1144            crate::meta::limited::hybrid_try_search_half_rev(
1145                dfa, &mut cache, input, min_start,
1146            )
1147        }
1148        #[cfg(not(feature = "hybrid"))]
1149        {
1150            // Impossible to reach because this engine is never constructed
1151            // if the requisite features aren't enabled.
1152            unreachable!()
1153        }
1154    }
1155}
1156
1157#[derive(Clone, Debug)]
1158pub(crate) struct ReverseHybridCache(
1159    #[cfg(feature = "hybrid")] Option<hybrid::dfa::Cache>,
1160    #[cfg(not(feature = "hybrid"))] (),
1161);
1162
1163impl ReverseHybridCache {
1164    pub(crate) fn none() -> ReverseHybridCache {
1165        #[cfg(feature = "hybrid")]
1166        {
1167            ReverseHybridCache(None)
1168        }
1169        #[cfg(not(feature = "hybrid"))]
1170        {
1171            ReverseHybridCache(())
1172        }
1173    }
1174
1175    pub(crate) fn new(builder: &ReverseHybrid) -> ReverseHybridCache {
1176        #[cfg(feature = "hybrid")]
1177        {
1178            ReverseHybridCache(builder.0.as_ref().map(|e| e.0.create_cache()))
1179        }
1180        #[cfg(not(feature = "hybrid"))]
1181        {
1182            ReverseHybridCache(())
1183        }
1184    }
1185
1186    pub(crate) fn reset(&mut self, builder: &ReverseHybrid) {
1187        #[cfg(feature = "hybrid")]
1188        if let Some(ref e) = builder.0 {
1189            self.0.as_mut().unwrap().reset(&e.0);
1190        }
1191    }
1192
1193    pub(crate) fn memory_usage(&self) -> usize {
1194        #[cfg(feature = "hybrid")]
1195        {
1196            self.0.as_ref().map_or(0, |c| c.memory_usage())
1197        }
1198        #[cfg(not(feature = "hybrid"))]
1199        {
1200            0
1201        }
1202    }
1203}
1204
1205#[derive(Debug)]
1206pub(crate) struct ReverseDFA(Option<ReverseDFAEngine>);
1207
1208impl ReverseDFA {
1209    pub(crate) fn none() -> ReverseDFA {
1210        ReverseDFA(None)
1211    }
1212
1213    pub(crate) fn new(info: &RegexInfo, nfarev: &NFA) -> ReverseDFA {
1214        ReverseDFA(ReverseDFAEngine::new(info, nfarev))
1215    }
1216
1217    #[cfg_attr(feature = "perf-inline", inline(always))]
1218    pub(crate) fn get(&self, _input: &Input<'_>) -> Option<&ReverseDFAEngine> {
1219        let engine = self.0.as_ref()?;
1220        Some(engine)
1221    }
1222
1223    pub(crate) fn is_some(&self) -> bool {
1224        self.0.is_some()
1225    }
1226
1227    pub(crate) fn memory_usage(&self) -> usize {
1228        self.0.as_ref().map_or(0, |e| e.memory_usage())
1229    }
1230}
1231
1232#[derive(Debug)]
1233pub(crate) struct ReverseDFAEngine(
1234    #[cfg(feature = "dfa-build")] dfa::dense::DFA<Vec<u32>>,
1235    #[cfg(not(feature = "dfa-build"))] (),
1236);
1237
1238impl ReverseDFAEngine {
1239    pub(crate) fn new(
1240        info: &RegexInfo,
1241        nfarev: &NFA,
1242    ) -> Option<ReverseDFAEngine> {
1243        #[cfg(feature = "dfa-build")]
1244        {
1245            if !info.config().get_dfa() {
1246                return None;
1247            }
1248            // If our NFA is anything but small, don't even bother with a DFA.
1249            if let Some(state_limit) = info.config().get_dfa_state_limit() {
1250                if nfarev.states().len() > state_limit {
1251                    debug!(
1252                        "skipping full reverse DFA because NFA has {} states, \
1253                         which exceeds the heuristic limit of {}",
1254                        nfarev.states().len(),
1255                        state_limit,
1256					);
1257                    return None;
1258                }
1259            }
1260            // We cut the size limit in two because the total heap used by DFA
1261            // construction is determinization aux memory and the DFA itself,
1262            // and those things are configured independently in the lower level
1263            // DFA builder API.
1264            let size_limit = info.config().get_dfa_size_limit().map(|n| n / 2);
1265            // Since we only use this for reverse searches, we can hard-code
1266            // a number of things like match semantics, prefilters, starts
1267            // for each pattern and so on. We also disable acceleration since
1268            // it's incompatible with limited searches (which is the only
1269            // operation we support for this kind of engine at the moment).
1270            let dfa_config = dfa::dense::Config::new()
1271                .match_kind(MatchKind::All)
1272                .prefilter(None)
1273                .accelerate(false)
1274                .start_kind(dfa::StartKind::Anchored)
1275                .starts_for_each_pattern(false)
1276                .byte_classes(info.config().get_byte_classes())
1277                .unicode_word_boundary(true)
1278                .specialize_start_states(false)
1279                .determinize_size_limit(size_limit)
1280                .dfa_size_limit(size_limit);
1281            let result = dfa::dense::Builder::new()
1282                .configure(dfa_config)
1283                .build_from_nfa(&nfarev);
1284            let rev = match result {
1285                Ok(rev) => rev,
1286                Err(_err) => {
1287                    debug!("full reverse DFA failed to build: {_err}");
1288                    return None;
1289                }
1290            };
1291            debug!(
1292                "fully compiled reverse DFA built, {} bytes",
1293                rev.memory_usage()
1294            );
1295            Some(ReverseDFAEngine(rev))
1296        }
1297        #[cfg(not(feature = "dfa-build"))]
1298        {
1299            None
1300        }
1301    }
1302
1303    #[cfg_attr(feature = "perf-inline", inline(always))]
1304    pub(crate) fn try_search_half_rev_limited(
1305        &self,
1306        input: &Input<'_>,
1307        min_start: usize,
1308    ) -> Result<Option<HalfMatch>, RetryError> {
1309        #[cfg(feature = "dfa-build")]
1310        {
1311            let dfa = &self.0;
1312            crate::meta::limited::dfa_try_search_half_rev(
1313                dfa, input, min_start,
1314            )
1315        }
1316        #[cfg(not(feature = "dfa-build"))]
1317        {
1318            // Impossible to reach because this engine is never constructed
1319            // if the requisite features aren't enabled.
1320            unreachable!()
1321        }
1322    }
1323
1324    pub(crate) fn memory_usage(&self) -> usize {
1325        #[cfg(feature = "dfa-build")]
1326        {
1327            self.0.memory_usage()
1328        }
1329        #[cfg(not(feature = "dfa-build"))]
1330        {
1331            // Impossible to reach because this engine is never constructed
1332            // if the requisite features aren't enabled.
1333            unreachable!()
1334        }
1335    }
1336}