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
26pub(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 let pre = if info.is_always_anchored_start() {
89 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 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 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 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 let group_info = GroupInfo::new([[None::<&str>]]).unwrap();
206 Arc::new(Pre { pre, group_info })
207 }
208}
209
210impl Pre<()> {
214 fn from_prefixes(
230 info: &RegexInfo,
231 prefixes: &literal::Seq,
232 ) -> Option<Arc<dyn Strategy>> {
233 let kind = info.config().get_match_kind();
234 if !prefixes.is_exact() {
238 return None;
239 }
240 if info.pattern_len() != 1 {
247 return None;
248 }
249 if info.props()[0].explicit_captures_len() != 0 {
254 return None;
255 }
256 if !info.props()[0].look_set().is_empty() {
264 return None;
265 }
266 if kind != MatchKind::LeftmostFirst {
270 return None;
271 }
272 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 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
331impl<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 let pikevm = wrappers::PikeVM::new(&info, pre.clone(), &nfa)?;
484 let backtrack =
485 wrappers::BoundedBacktracker::new(&info, pre.clone(), &nfa)?;
486 let onepass = wrappers::OnePass::new(&info, &nfa);
491 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 let nfarev = thompson::Compiler::new()
512 .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 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 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 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 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 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 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 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 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 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 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
981impl 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 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 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 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 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 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 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 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 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 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 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 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 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#[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#[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 assert_strategy("prefilter memchr", &["(?:a)"]);
2039 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}