1use core::{
2fmt::Debug,
3 panic::{RefUnwindSafe, UnwindSafe},
4};
56use alloc::{borrow::Cow, format, sync::Arc};
78use regex_syntax::hir::{literal, Hir};
910use crate::{
11 meta::{
12 error::{BuildError, RetryError, RetryFailError, RetryQuadraticError},
13 regex::{Cache, RegexInfo},
14reverse_inner, reverse_suffix, wrappers,
15 },
16 nfa::thompson::{self, WhichCaptures, NFA},
17 util::{
18 captures::{Captures, GroupInfo},
19look::LookMatcher,
20 prefilter::{self, Prefilter, PrefilterI},
21 primitives::{NonMaxUsize, PatternID},
22 search::{Anchored, HalfMatch, Input, Match, MatchKind, PatternSet},
23 },
24};
2526/// A trait that represents a single meta strategy. Its main utility is in
27/// providing a way to do dynamic dispatch over a few choices.
28///
29/// Why dynamic dispatch? I actually don't have a super compelling reason, and
30/// importantly, I have not benchmarked it with the main alternative: an enum.
31/// I went with dynamic dispatch initially because the regex engine search code
32/// really can't be inlined into caller code in most cases because it's just
33/// too big. In other words, it is already expected that every regex search
34/// will entail at least the cost of a function call.
35///
36/// I do wonder whether using enums would result in better codegen overall
37/// though. It's a worthwhile experiment to try. Probably the most interesting
38/// benchmark to run in such a case would be one with a high match count. That
39/// is, a benchmark to test the overall latency of a search call.
40pub(super) trait Strategy:
41Debug + Send + Sync + RefUnwindSafe + UnwindSafe + 'static
42{
43#[allow(dead_code)]
44fn name(&self) -> Cow<'static, str>;
4546fn group_info(&self) -> &GroupInfo;
4748fn create_cache(&self) -> Cache;
4950fn reset_cache(&self, cache: &mut Cache);
5152fn is_accelerated(&self) -> bool;
5354fn memory_usage(&self) -> usize;
5556fn search(&self, cache: &mut Cache, input: &Input<'_>) -> Option<Match>;
5758fn search_half(
59&self,
60 cache: &mut Cache,
61 input: &Input<'_>,
62 ) -> Option<HalfMatch>;
6364fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool;
6566fn search_slots(
67&self,
68 cache: &mut Cache,
69 input: &Input<'_>,
70 slots: &mut [Option<NonMaxUsize>],
71 ) -> Option<PatternID>;
7273fn which_overlapping_matches(
74&self,
75 cache: &mut Cache,
76 input: &Input<'_>,
77 patset: &mut PatternSet,
78 );
79}
8081pub(super) fn new(
82 info: &RegexInfo,
83 hirs: &[&Hir],
84) -> Result<Arc<dyn Strategy>, BuildError> {
85// At this point, we're committed to a regex engine of some kind. So pull
86 // out a prefilter if we can, which will feed to each of the constituent
87 // regex engines.
88let pre = if info.is_always_anchored_start() {
89// PERF: I'm not sure we necessarily want to do this... We may want to
90 // run a prefilter for quickly rejecting in some cases. The problem
91 // is that anchored searches overlap quite a bit with the use case
92 // of "run a regex on every line to extract data." In that case, the
93 // regex always matches, so running a prefilter doesn't really help us
94 // there. The main place where a prefilter helps in an anchored search
95 // is if the anchored search is not expected to match frequently. That
96 // is, the prefilter gives us a way to possibly reject a haystack very
97 // quickly.
98 //
99 // Maybe we should do use a prefilter, but only for longer haystacks?
100 // Or maybe we should only use a prefilter when we think it's "fast"?
101 //
102 // Interestingly, I think we currently lack the infrastructure for
103 // disabling a prefilter based on haystack length. That would probably
104 // need to be a new 'Input' option. (Interestingly, an 'Input' used to
105 // carry a 'Prefilter' with it, but I moved away from that.)
106debug!("skipping literal extraction since regex is anchored");
107None108 } else if let Some(pre) = info.config().get_prefilter() {
109debug!(
110"skipping literal extraction since the caller provided a prefilter"
111);
112Some(pre.clone())
113 } else if info.config().get_auto_prefilter() {
114let kind = info.config().get_match_kind();
115let prefixes = crate::util::prefilter::prefixes(kind, hirs);
116// If we can build a full `Strategy` from just the extracted prefixes,
117 // then we can short-circuit and avoid building a regex engine at all.
118if let Some(pre) = Pre::from_prefixes(info, &prefixes) {
119debug!(
120"found that the regex can be broken down to a literal \
121 search, avoiding the regex engine entirely",
122 );
123debug!("using {} strategy", pre.name());
124return Ok(pre);
125 }
126// This now attempts another short-circuit of the regex engine: if we
127 // have a huge alternation of just plain literals, then we can just use
128 // Aho-Corasick for that and avoid the regex engine entirely.
129 //
130 // You might think this case would just be handled by
131 // `Pre::from_prefixes`, but that technique relies on heuristic literal
132 // extraction from the corresponding `Hir`. That works, but part of
133 // heuristics limit the size and number of literals returned. This case
134 // will specifically handle patterns with very large alternations.
135 //
136 // One wonders if we should just roll this our heuristic literal
137 // extraction, and then I think this case could disappear entirely.
138if let Some(pre) = Pre::from_alternation_literals(info, hirs) {
139debug!(
140"found plain alternation of literals, \
141 avoiding regex engine entirely and using Aho-Corasick"
142);
143debug!("using {} strategy", pre.name());
144return Ok(pre);
145 }
146prefixes.literals().and_then(|strings| {
147debug!(
148"creating prefilter from {} literals: {:?}",
149 strings.len(),
150 strings,
151 );
152Prefilter::new(kind, strings)
153 })
154 } else {
155debug!("skipping literal extraction since prefilters were disabled");
156None157 };
158let mut core = Core::new(info.clone(), pre.clone(), hirs)?;
159// Now that we have our core regex engines built, there are a few cases
160 // where we can do a little bit better than just a normal "search forward
161 // and maybe use a prefilter when in a start state." However, these cases
162 // may not always work or otherwise build on top of the Core searcher.
163 // For example, the reverse anchored optimization seems like it might
164 // always work, but only the DFAs support reverse searching and the DFAs
165 // might give up or quit for reasons. If we had, e.g., a PikeVM that
166 // supported reverse searching, then we could avoid building a full Core
167 // engine for this case.
168core = match ReverseAnchored::new(core) {
169Err(core) => core,
170Ok(ra) => {
171debug!("using {} strategy", ra.name());
172return Ok(Arc::new(ra));
173 }
174 };
175core = match ReverseSuffix::new(core, hirs) {
176Err(core) => core,
177Ok(rs) => {
178debug!("using {} strategy", rs.name());
179return Ok(Arc::new(rs));
180 }
181 };
182core = match ReverseInner::new(core, hirs) {
183Err(core) => core,
184Ok(ri) => {
185debug!("using {} strategy", ri.name());
186return Ok(Arc::new(ri));
187 }
188 };
189debug!("using {} strategy", core.name());
190Ok(Arc::new(core))
191}
192193#[derive(#[automatically_derived]impl<P: ::core::clone::Clone> ::core::clone::Clone for
Pre<P> {
#[inline]
fn clone(&self) -> Self {
Pre {
pre: ::core::clone::Clone::clone(&self.pre),
group_info: ::core::clone::Clone::clone(&self.group_info),
}
}
}Clone, #[automatically_derived]impl<P: ::core::fmt::Debug> ::core::fmt::Debug for
Pre<P> {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f,
"Pre pre group_info", &self.pre, &&self.group_info)
}
}Debug)]
194struct Pre<P> {
195 pre: P,
196 group_info: GroupInfo,
197}
198199impl<P: PrefilterI> Pre<P> {
200fn new(pre: P) -> Arc<dyn Strategy> {
201// The only thing we support when we use prefilters directly as a
202 // strategy is the start and end of the overall match for a single
203 // pattern. In other words, exactly one implicit capturing group. Which
204 // is exactly what we use here for a GroupInfo.
205let group_info = GroupInfo::new([[None::<&str>]]).unwrap();
206Arc::new(Pre { pre, group_info })
207 }
208}
209210// This is a little weird, but we don't actually care about the type parameter
211// here because we're selecting which underlying prefilter to use. So we just
212// define it on an arbitrary type.
213impl Pre<()> {
214/// Given a sequence of prefixes, attempt to return a full `Strategy` using
215 /// just the prefixes.
216 ///
217 /// Basically, this occurs when the prefixes given not just prefixes,
218 /// but an enumeration of the entire language matched by the regular
219 /// expression.
220 ///
221 /// A number of other conditions need to be true too. For example, there
222 /// can be only one pattern, the number of explicit capture groups is 0, no
223 /// look-around assertions and so on.
224 ///
225 /// Note that this ignores `Config::get_auto_prefilter` because if this
226 /// returns something, then it isn't a prefilter but a matcher itself.
227 /// Therefore, it shouldn't suffer from the problems typical to prefilters
228 /// (such as a high false positive rate).
229fn from_prefixes(
230 info: &RegexInfo,
231 prefixes: &literal::Seq,
232 ) -> Option<Arc<dyn Strategy>> {
233let kind = info.config().get_match_kind();
234// Check to see if our prefixes are exact, which means we might be
235 // able to bypass the regex engine entirely and just rely on literal
236 // searches.
237if !prefixes.is_exact() {
238return None;
239 }
240// We also require that we have a single regex pattern. Namely,
241 // we reuse the prefilter infrastructure to implement search and
242 // prefilters only report spans. Prefilters don't know about pattern
243 // IDs. The multi-regex case isn't a lost cause, we might still use
244 // Aho-Corasick and we might still just use a regular prefilter, but
245 // that's done below.
246if info.pattern_len() != 1 {
247return None;
248 }
249// We can't have any capture groups either. The literal engines don't
250 // know how to deal with things like '(foo)(bar)'. In that case, a
251 // prefilter will just be used and then the regex engine will resolve
252 // the capture groups.
253if info.props()[0].explicit_captures_len() != 0 {
254return None;
255 }
256// We also require that it has zero look-around assertions. Namely,
257 // literal extraction treats look-around assertions as if they match
258 // *every* empty string. But of course, that isn't true. So for
259 // example, 'foo\bquux' never matches anything, but 'fooquux' is
260 // extracted from that as an exact literal. Such cases should just run
261 // the regex engine. 'fooquux' will be used as a normal prefilter, and
262 // then the regex engine will try to look for an actual match.
263if !info.props()[0].look_set().is_empty() {
264return None;
265 }
266// Finally, currently, our prefilters are all oriented around
267 // leftmost-first match semantics, so don't try to use them if the
268 // caller asked for anything else.
269if kind != MatchKind::LeftmostFirst {
270return None;
271 }
272// The above seems like a lot of requirements to meet, but it applies
273 // to a lot of cases. 'foo', '[abc][123]' and 'foo|bar|quux' all meet
274 // the above criteria, for example.
275 //
276 // Note that this is effectively a latency optimization. If we didn't
277 // do this, then the extracted literals would still get bundled into
278 // a prefilter, and every regex engine capable of running unanchored
279 // searches supports prefilters. So this optimization merely sidesteps
280 // having to run the regex engine at all to confirm the match. Thus, it
281 // decreases the latency of a match.
282283 // OK because we know the set is exact and thus finite.
284let prefixes = prefixes.literals().unwrap();
285debug!(
286"trying to bypass regex engine by creating \
287 prefilter from {} literals: {:?}",
288 prefixes.len(),
289 prefixes,
290 );
291let choice = match prefilter::Choice::new(kind, prefixes) {
292Some(choice) => choice,
293None => {
294debug!(
295"regex bypass failed because no prefilter could be built"
296);
297return None;
298 }
299 };
300let 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 };
309Some(strat)
310 }
311312/// Attempts to extract an alternation of literals, and if it's deemed
313 /// worth doing, returns an Aho-Corasick prefilter as a strategy.
314 ///
315 /// And currently, this only returns something when 'hirs.len() == 1'. This
316 /// could in theory do something if there are multiple HIRs where all of
317 /// them are alternation of literals, but I haven't had the time to go down
318 /// that path yet.
319fn from_alternation_literals(
320 info: &RegexInfo,
321 hirs: &[&Hir],
322 ) -> Option<Arc<dyn Strategy>> {
323use crate::util::prefilter::AhoCorasick;
324325let lits = crate::meta::literal::alternation_literals(info, hirs)?;
326let ac = AhoCorasick::new(MatchKind::LeftmostFirst, &lits)?;
327Some(Pre::new(ac))
328 }
329}
330331// This implements Strategy for anything that implements PrefilterI.
332//
333// Note that this must only be used for regexes of length 1. Multi-regexes
334// don't work here. The prefilter interface only provides the span of a match
335// and not the pattern ID. (I did consider making it more expressive, but I
336// couldn't figure out how to tie everything together elegantly.) Thus, so long
337// as the regex only contains one pattern, we can simply assume that a match
338// corresponds to PatternID::ZERO. And indeed, that's what we do here.
339//
340// In practice, since this impl is used to report matches directly and thus
341// completely bypasses the regex engine, we only wind up using this under the
342// following restrictions:
343//
344// * There must be only one pattern. As explained above.
345// * The literal sequence must be finite and only contain exact literals.
346// * There must not be any look-around assertions. If there are, the literals
347// extracted might be exact, but a match doesn't necessarily imply an overall
348// match. As a trivial example, 'foo\bbar' does not match 'foobar'.
349// * The pattern must not have any explicit capturing groups. If it does, the
350// caller might expect them to be resolved. e.g., 'foo(bar)'.
351//
352// So when all of those things are true, we use a prefilter directly as a
353// strategy.
354//
355// In the case where the number of patterns is more than 1, we don't use this
356// but do use a special Aho-Corasick strategy if all of the regexes are just
357// simple literals or alternations of literals. (We also use the Aho-Corasick
358// strategy when len(patterns)==1 if the number of literals is large. In that
359// case, literal extraction gives up and will return an infinite set.)
360impl<P: PrefilterI> Strategyfor Pre<P> {
361fn name(&self) -> Cow<'static, str> {
362 Cow::Owned(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("prefilter {0}", self.pre.name()))
})format!("prefilter {}", self.pre.name()))
363 }
364365#[cfg_attr(feature = "perf-inline", inline(always))]
366fn group_info(&self) -> &GroupInfo {
367&self.group_info
368 }
369370fn create_cache(&self) -> Cache {
371Cache {
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 }
380381fn reset_cache(&self, _cache: &mut Cache) {}
382383fn is_accelerated(&self) -> bool {
384self.pre.is_fast()
385 }
386387fn memory_usage(&self) -> usize {
388self.pre.memory_usage()
389 }
390391#[cfg_attr(feature = "perf-inline", inline(always))]
392fn search(&self, _cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
393if input.is_done() {
394return None;
395 }
396if input.get_anchored().is_anchored() {
397return self398 .pre
399 .prefix(input.haystack(), input.get_span())
400 .map(|sp| Match::new(PatternID::ZERO, sp));
401 }
402self.pre
403 .find(input.haystack(), input.get_span())
404 .map(|sp| Match::new(PatternID::ZERO, sp))
405 }
406407#[cfg_attr(feature = "perf-inline", inline(always))]
408fn search_half(
409&self,
410 cache: &mut Cache,
411 input: &Input<'_>,
412 ) -> Option<HalfMatch> {
413self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
414 }
415416#[cfg_attr(feature = "perf-inline", inline(always))]
417fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
418self.search(cache, input).is_some()
419 }
420421#[cfg_attr(feature = "perf-inline", inline(always))]
422fn search_slots(
423&self,
424 cache: &mut Cache,
425 input: &Input<'_>,
426 slots: &mut [Option<NonMaxUsize>],
427 ) -> Option<PatternID> {
428let m = self.search(cache, input)?;
429if let Some(slot) = slots.get_mut(0) {
430*slot = NonMaxUsize::new(m.start());
431 }
432if let Some(slot) = slots.get_mut(1) {
433*slot = NonMaxUsize::new(m.end());
434 }
435Some(m.pattern())
436 }
437438#[cfg_attr(feature = "perf-inline", inline(always))]
439fn which_overlapping_matches(
440&self,
441 cache: &mut Cache,
442 input: &Input<'_>,
443 patset: &mut PatternSet,
444 ) {
445if self.search(cache, input).is_some() {
446patset.insert(PatternID::ZERO);
447 }
448 }
449}
450451#[derive(#[automatically_derived]impl ::core::fmt::Debug for Core {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
"Core info pre nfa nfarev pikevm backtrack onepass hybrid dfa";
let values: &[&dyn ::core::fmt::Debug] =
&[&self.info, &self.pre, &self.nfa, &self.nfarev, &self.pikevm,
&self.backtrack, &self.onepass, &self.hybrid, &&self.dfa];
::core::fmt::Formatter::debug_struct_fields_finish(f, names, values)
}
}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}
463464impl Core {
465fn new(
466 info: RegexInfo,
467 pre: Option<Prefilter>,
468 hirs: &[&Hir],
469 ) -> Result<Core, BuildError> {
470let mut lookm = LookMatcher::new();
471lookm.set_line_terminator(info.config().get_line_terminator());
472let thompson_config = info.config().to_thompson_config();
473let nfa = thompson::Compiler::new()
474 .configure(thompson_config.clone())
475 .build_many_from_hir(hirs)
476 .map_err(BuildError::nfa)?;
477// It's possible for the PikeVM or the BB to fail to build, even though
478 // at this point, we already have a full NFA in hand. They can fail
479 // when a Unicode word boundary is used but where Unicode word boundary
480 // support is disabled at compile time, thus making it impossible to
481 // match. (Construction can also fail if the NFA was compiled without
482 // captures, but we always enable that above.)
483let pikevm = wrappers::PikeVM::new(&info, pre.clone(), &nfa)?;
484let backtrack =
485 wrappers::BoundedBacktracker::new(&info, pre.clone(), &nfa)?;
486// The onepass engine can of course fail to build, but we expect it to
487 // fail in many cases because it is an optimization that doesn't apply
488 // to all regexes. The 'OnePass' wrapper encapsulates this failure (and
489 // logs a message if it occurs).
490let onepass = wrappers::OnePass::new(&info, &nfa);
491// We try to encapsulate whether a particular regex engine should be
492 // used within each respective wrapper, but the DFAs need a reverse NFA
493 // to build itself, and we really do not want to build a reverse NFA if
494 // we know we aren't going to use the lazy DFA. So we do a config check
495 // up front, which is in practice the only way we won't try to use the
496 // DFA.
497let (nfarev, hybrid, dfa) =
498if !info.config().get_hybrid() && !info.config().get_dfa() {
499 (None, wrappers::Hybrid::none(), wrappers::DFA::none())
500 } else {
501// FIXME: Technically, we don't quite yet KNOW that we need
502 // a reverse NFA. It's possible for the DFAs below to both
503 // fail to build just based on the forward NFA. In which case,
504 // building the reverse NFA was totally wasted work. But...
505 // fixing this requires breaking DFA construction apart into
506 // two pieces: one for the forward part and another for the
507 // reverse part. Quite annoying. Making it worse, when building
508 // both DFAs fails, it's quite likely that the NFA is large and
509 // that it will take quite some time to build the reverse NFA
510 // too. So... it's really probably worth it to do this!
511let nfarev = thompson::Compiler::new()
512// Currently, reverse NFAs don't support capturing groups,
513 // so we MUST disable them. But even if we didn't have to,
514 // we would, because nothing in this crate does anything
515 // useful with capturing groups in reverse. And of course,
516 // the lazy DFA ignores capturing groups in all cases.
517.configure(
518 thompson_config
519 .which_captures(WhichCaptures::None)
520 .reverse(true),
521 )
522 .build_many_from_hir(hirs)
523 .map_err(BuildError::nfa)?;
524let dfa = if !info.config().get_dfa() {
525 wrappers::DFA::none()
526 } else {
527 wrappers::DFA::new(&info, pre.clone(), &nfa, &nfarev)
528 };
529let hybrid = if !info.config().get_hybrid() {
530 wrappers::Hybrid::none()
531 } else if dfa.is_some() {
532debug!("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 };
539Ok(Core {
540info,
541pre,
542nfa,
543nfarev,
544pikevm,
545backtrack,
546onepass,
547hybrid,
548dfa,
549 })
550 }
551552#[cfg_attr(feature = "perf-inline", inline(always))]
553fn try_search_mayfail(
554&self,
555 cache: &mut Cache,
556 input: &Input<'_>,
557 ) -> Option<Result<Option<Match>, RetryFailError>> {
558if let Some(e) = self.dfa.get(input) {
559trace!("using full DFA for search at {:?}", input.get_span());
560Some(e.try_search(input))
561 } else if let Some(e) = self.hybrid.get(input) {
562trace!("using lazy DFA for search at {:?}", input.get_span());
563Some(e.try_search(&mut cache.hybrid, input))
564 } else {
565None566 }
567 }
568569fn search_nofail(
570&self,
571 cache: &mut Cache,
572 input: &Input<'_>,
573 ) -> Option<Match> {
574let caps = &mut cache.capmatches;
575caps.set_pattern(None);
576// We manually inline 'try_search_slots_nofail' here because we need to
577 // borrow from 'cache.capmatches' in this method, but if we do, then
578 // we can't pass 'cache' wholesale to to 'try_slots_no_hybrid'. It's a
579 // classic example of how the borrow checker inhibits decomposition.
580 // There are of course work-arounds (more types and/or interior
581 // mutability), but that's more annoying than this IMO.
582let pid = if let Some(ref e) = self.onepass.get(input) {
583trace!("using OnePass for search at {:?}", input.get_span());
584e.search_slots(&mut cache.onepass, input, caps.slots_mut())
585 } else if let Some(ref e) = self.backtrack.get(input) {
586trace!(
587"using BoundedBacktracker for search at {:?}",
588 input.get_span()
589 );
590e.search_slots(&mut cache.backtrack, input, caps.slots_mut())
591 } else {
592trace!("using PikeVM for search at {:?}", input.get_span());
593let e = self.pikevm.get();
594e.search_slots(&mut cache.pikevm, input, caps.slots_mut())
595 };
596caps.set_pattern(pid);
597caps.get_match()
598 }
599600fn search_half_nofail(
601&self,
602 cache: &mut Cache,
603 input: &Input<'_>,
604 ) -> Option<HalfMatch> {
605// Only the lazy/full DFA returns half-matches, since the DFA requires
606 // a reverse scan to find the start position. These fallback regex
607 // engines can find the start and end in a single pass, so we just do
608 // that and throw away the start offset to conform to the API.
609let m = self.search_nofail(cache, input)?;
610Some(HalfMatch::new(m.pattern(), m.end()))
611 }
612613fn search_slots_nofail(
614&self,
615 cache: &mut Cache,
616 input: &Input<'_>,
617 slots: &mut [Option<NonMaxUsize>],
618 ) -> Option<PatternID> {
619if let Some(ref e) = self.onepass.get(input) {
620trace!(
621"using OnePass for capture search at {:?}",
622 input.get_span()
623 );
624e.search_slots(&mut cache.onepass, input, slots)
625 } else if let Some(ref e) = self.backtrack.get(input) {
626trace!(
627"using BoundedBacktracker for capture search at {:?}",
628 input.get_span()
629 );
630e.search_slots(&mut cache.backtrack, input, slots)
631 } else {
632trace!(
633"using PikeVM for capture search at {:?}",
634 input.get_span()
635 );
636let e = self.pikevm.get();
637e.search_slots(&mut cache.pikevm, input, slots)
638 }
639 }
640641fn is_match_nofail(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
642if let Some(ref e) = self.onepass.get(input) {
643trace!(
644"using OnePass for is-match search at {:?}",
645 input.get_span()
646 );
647e.search_slots(&mut cache.onepass, input, &mut []).is_some()
648 } else if let Some(ref e) = self.backtrack.get(input) {
649trace!(
650"using BoundedBacktracker for is-match search at {:?}",
651 input.get_span()
652 );
653e.is_match(&mut cache.backtrack, input)
654 } else {
655trace!(
656"using PikeVM for is-match search at {:?}",
657 input.get_span()
658 );
659let e = self.pikevm.get();
660e.is_match(&mut cache.pikevm, input)
661 }
662 }
663664fn is_capture_search_needed(&self, slots_len: usize) -> bool {
665slots_len > self.nfa.group_info().implicit_slot_len()
666 }
667}
668669impl Strategyfor Core {
670fn name(&self) -> Cow<'static, str> {
671 Cow::Borrowed("core")
672 }
673674#[cfg_attr(feature = "perf-inline", inline(always))]
675fn group_info(&self) -> &GroupInfo {
676self.nfa.group_info()
677 }
678679#[cfg_attr(feature = "perf-inline", inline(always))]
680fn create_cache(&self) -> Cache {
681Cache {
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 }
690691#[cfg_attr(feature = "perf-inline", inline(always))]
692fn reset_cache(&self, cache: &mut Cache) {
693cache.pikevm.reset(&self.pikevm);
694cache.backtrack.reset(&self.backtrack);
695cache.onepass.reset(&self.onepass);
696cache.hybrid.reset(&self.hybrid);
697 }
698699fn is_accelerated(&self) -> bool {
700self.pre.as_ref().map_or(false, |pre| pre.is_fast())
701 }
702703fn memory_usage(&self) -> usize {
704self.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 }
711712#[cfg_attr(feature = "perf-inline", inline(always))]
713fn search(&self, cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
714// We manually inline try_search_mayfail here because letting the
715 // compiler do it seems to produce pretty crappy codegen.
716return if let Some(e) = self.dfa.get(input) {
717trace!("using full DFA for full search at {:?}", input.get_span());
718match e.try_search(input) {
719Ok(x) => x,
720Err(_err) => {
721trace!("full DFA search failed: {_err}");
722self.search_nofail(cache, input)
723 }
724 }
725 } else if let Some(e) = self.hybrid.get(input) {
726trace!("using lazy DFA for full search at {:?}", input.get_span());
727match e.try_search(&mut cache.hybrid, input) {
728Ok(x) => x,
729Err(_err) => {
730trace!("lazy DFA search failed: {_err}");
731self.search_nofail(cache, input)
732 }
733 }
734 } else {
735self.search_nofail(cache, input)
736 };
737 }
738739#[cfg_attr(feature = "perf-inline", inline(always))]
740fn search_half(
741&self,
742 cache: &mut Cache,
743 input: &Input<'_>,
744 ) -> Option<HalfMatch> {
745// The main difference with 'search' is that if we're using a DFA, we
746 // can use a single forward scan without needing to run the reverse
747 // DFA.
748if let Some(e) = self.dfa.get(input) {
749trace!("using full DFA for half search at {:?}", input.get_span());
750match e.try_search_half_fwd(input) {
751Ok(x) => x,
752Err(_err) => {
753trace!("full DFA half search failed: {_err}");
754self.search_half_nofail(cache, input)
755 }
756 }
757 } else if let Some(e) = self.hybrid.get(input) {
758trace!("using lazy DFA for half search at {:?}", input.get_span());
759match e.try_search_half_fwd(&mut cache.hybrid, input) {
760Ok(x) => x,
761Err(_err) => {
762trace!("lazy DFA half search failed: {_err}");
763self.search_half_nofail(cache, input)
764 }
765 }
766 } else {
767self.search_half_nofail(cache, input)
768 }
769 }
770771#[cfg_attr(feature = "perf-inline", inline(always))]
772fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
773if let Some(e) = self.dfa.get(input) {
774trace!(
775"using full DFA for is-match search at {:?}",
776 input.get_span()
777 );
778match e.try_search_half_fwd(input) {
779Ok(x) => x.is_some(),
780Err(_err) => {
781trace!("full DFA half search failed: {_err}");
782self.is_match_nofail(cache, input)
783 }
784 }
785 } else if let Some(e) = self.hybrid.get(input) {
786trace!(
787"using lazy DFA for is-match search at {:?}",
788 input.get_span()
789 );
790match e.try_search_half_fwd(&mut cache.hybrid, input) {
791Ok(x) => x.is_some(),
792Err(_err) => {
793trace!("lazy DFA half search failed: {_err}");
794self.is_match_nofail(cache, input)
795 }
796 }
797 } else {
798self.is_match_nofail(cache, input)
799 }
800 }
801802#[cfg_attr(feature = "perf-inline", inline(always))]
803fn search_slots(
804&self,
805 cache: &mut Cache,
806 input: &Input<'_>,
807 slots: &mut [Option<NonMaxUsize>],
808 ) -> Option<PatternID> {
809// Even if the regex has explicit capture groups, if the caller didn't
810 // provide any explicit slots, then it doesn't make sense to try and do
811 // extra work to get offsets for those slots. Ideally the caller should
812 // realize this and not call this routine in the first place, but alas,
813 // we try to save the caller from themselves if they do.
814if !self.is_capture_search_needed(slots.len()) {
815trace!("asked for slots unnecessarily, trying fast path");
816let m = self.search(cache, input)?;
817copy_match_to_slots(m, slots);
818return Some(m.pattern());
819 }
820// If the onepass DFA is available for this search (which only happens
821 // when it's anchored), then skip running a fallible DFA. The onepass
822 // DFA isn't as fast as a full or lazy DFA, but it is typically quite
823 // a bit faster than the backtracker or the PikeVM. So it isn't as
824 // advantageous to try and do a full/lazy DFA scan first.
825 //
826 // We still theorize that it's better to do a full/lazy DFA scan, even
827 // when it's anchored, because it's usually much faster and permits us
828 // to say "no match" much more quickly. This does hurt the case of,
829 // say, parsing each line in a log file into capture groups, because
830 // in that case, the line always matches. So the lazy DFA scan is
831 // usually just wasted work. But, the lazy DFA is usually quite fast
832 // and doesn't cost too much here.
833if self.onepass.get(&input).is_some() {
834return self.search_slots_nofail(cache, &input, slots);
835 }
836let m = match self.try_search_mayfail(cache, input) {
837Some(Ok(Some(m))) => m,
838Some(Ok(None)) => return None,
839Some(Err(_err)) => {
840trace!("fast capture search failed: {_err}");
841return self.search_slots_nofail(cache, input, slots);
842 }
843None => {
844return self.search_slots_nofail(cache, input, slots);
845 }
846 };
847// At this point, now that we've found the bounds of the
848 // match, we need to re-run something that can resolve
849 // capturing groups. But we only need to run on it on the
850 // match bounds and not the entire haystack.
851trace!(
852"match found at {}..{} in capture search, \
853 using another engine to find captures",
854 m.start(),
855 m.end(),
856 );
857let input = input858 .clone()
859 .span(m.start()..m.end())
860 .anchored(Anchored::Pattern(m.pattern()));
861Some(
862self.search_slots_nofail(cache, &input, slots)
863 .expect("should find a match"),
864 )
865 }
866867#[cfg_attr(feature = "perf-inline", inline(always))]
868fn which_overlapping_matches(
869&self,
870 cache: &mut Cache,
871 input: &Input<'_>,
872 patset: &mut PatternSet,
873 ) {
874if let Some(e) = self.dfa.get(input) {
875trace!(
876"using full DFA for overlapping search at {:?}",
877 input.get_span()
878 );
879let _err = match e.try_which_overlapping_matches(input, patset) {
880Ok(()) => return,
881Err(err) => err,
882 };
883trace!("fast overlapping search failed: {_err}");
884 } else if let Some(e) = self.hybrid.get(input) {
885trace!(
886"using lazy DFA for overlapping search at {:?}",
887 input.get_span()
888 );
889let _err = match e.try_which_overlapping_matches(
890&mut cache.hybrid,
891input,
892patset,
893 ) {
894Ok(()) => {
895return;
896 }
897Err(err) => err,
898 };
899trace!("fast overlapping search failed: {_err}");
900 }
901trace!(
902"using PikeVM for overlapping search at {:?}",
903 input.get_span()
904 );
905let e = self.pikevm.get();
906e.which_overlapping_matches(&mut cache.pikevm, input, patset)
907 }
908}
909910#[derive(#[automatically_derived]impl ::core::fmt::Debug for ReverseAnchored {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field1_finish(f,
"ReverseAnchored core", &&self.core)
}
}Debug)]
911struct ReverseAnchored {
912 core: Core,
913}
914915impl ReverseAnchored {
916fn new(core: Core) -> Result<ReverseAnchored, Core> {
917if !core.info.is_always_anchored_end() {
918debug!(
919"skipping reverse anchored optimization because \
920 the regex is not always anchored at the end"
921);
922return Err(core);
923 }
924// Note that the caller can still request an anchored search even when
925 // the regex isn't anchored at the start. We detect that case in the
926 // search routines below and just fallback to the core engine. This
927 // is fine because both searches are anchored. It's just a matter of
928 // picking one. Falling back to the core engine is a little simpler,
929 // since if we used the reverse anchored approach, we'd have to add an
930 // extra check to ensure the match reported starts at the place where
931 // the caller requested the search to start.
932if core.info.is_always_anchored_start() {
933debug!(
934"skipping reverse anchored optimization because \
935 the regex is also anchored at the start"
936);
937return Err(core);
938 }
939// Only DFAs can do reverse searches (currently), so we need one of
940 // them in order to do this optimization. It's possible (although
941 // pretty unlikely) that we have neither and need to give up.
942if !core.hybrid.is_some() && !core.dfa.is_some() {
943debug!(
944"skipping reverse anchored optimization because \
945 we don't have a lazy DFA or a full DFA"
946);
947return Err(core);
948 }
949Ok(ReverseAnchored { core })
950 }
951952#[cfg_attr(feature = "perf-inline", inline(always))]
953fn try_search_half_anchored_rev(
954&self,
955 cache: &mut Cache,
956 input: &Input<'_>,
957 ) -> Result<Option<HalfMatch>, RetryFailError> {
958// We of course always want an anchored search. In theory, the
959 // underlying regex engines should automatically enable anchored
960 // searches since the regex is itself anchored, but this more clearly
961 // expresses intent and is always correct.
962let input = input.clone().anchored(Anchored::Yes);
963if let Some(e) = self.core.dfa.get(&input) {
964trace!(
965"using full DFA for reverse anchored search at {:?}",
966 input.get_span()
967 );
968e.try_search_half_rev(&input)
969 } else if let Some(e) = self.core.hybrid.get(&input) {
970trace!(
971"using lazy DFA for reverse anchored search at {:?}",
972 input.get_span()
973 );
974e.try_search_half_rev(&mut cache.hybrid, &input)
975 } else {
976{
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("ReverseAnchored always has a DFA")));
}unreachable!("ReverseAnchored always has a DFA")977 }
978 }
979}
980981// Note that in this impl, we don't check that 'input.end() ==
982// input.haystack().len()'. In particular, when that condition is false, a
983// match is always impossible because we know that the regex is always anchored
984// at the end (or else 'ReverseAnchored' won't be built). We don't check that
985// here because the 'Regex' wrapper actually does that for us in all cases.
986// Thus, in this impl, we can actually assume that the end position in 'input'
987// is equivalent to the length of the haystack.
988impl Strategyfor ReverseAnchored {
989fn name(&self) -> Cow<'static, str> {
990 Cow::Borrowed("reverse anchored")
991 }
992993#[cfg_attr(feature = "perf-inline", inline(always))]
994fn group_info(&self) -> &GroupInfo {
995self.core.group_info()
996 }
997998#[cfg_attr(feature = "perf-inline", inline(always))]
999fn create_cache(&self) -> Cache {
1000self.core.create_cache()
1001 }
10021003#[cfg_attr(feature = "perf-inline", inline(always))]
1004fn reset_cache(&self, cache: &mut Cache) {
1005self.core.reset_cache(cache);
1006 }
10071008fn is_accelerated(&self) -> bool {
1009// Since this is anchored at the end, a reverse anchored search is
1010 // almost certainly guaranteed to result in a much faster search than
1011 // a standard forward search.
1012true
1013}
10141015fn memory_usage(&self) -> usize {
1016self.core.memory_usage()
1017 }
10181019#[cfg_attr(feature = "perf-inline", inline(always))]
1020fn search(&self, cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
1021if input.get_anchored().is_anchored() {
1022return self.core.search(cache, input);
1023 }
1024match self.try_search_half_anchored_rev(cache, input) {
1025Err(_err) => {
1026trace!("fast reverse anchored search failed: {_err}");
1027self.core.search_nofail(cache, input)
1028 }
1029Ok(None) => None,
1030Ok(Some(hm)) => {
1031Some(Match::new(hm.pattern(), hm.offset()..input.end()))
1032 }
1033 }
1034 }
10351036#[cfg_attr(feature = "perf-inline", inline(always))]
1037fn search_half(
1038&self,
1039 cache: &mut Cache,
1040 input: &Input<'_>,
1041 ) -> Option<HalfMatch> {
1042if input.get_anchored().is_anchored() {
1043return self.core.search_half(cache, input);
1044 }
1045match self.try_search_half_anchored_rev(cache, input) {
1046Err(_err) => {
1047trace!("fast reverse anchored search failed: {_err}");
1048self.core.search_half_nofail(cache, input)
1049 }
1050Ok(None) => None,
1051Ok(Some(hm)) => {
1052// Careful here! 'try_search_half' is a *forward* search that
1053 // only cares about the *end* position of a match. But
1054 // 'hm.offset()' is actually the start of the match. So we
1055 // actually just throw that away here and, since we know we
1056 // have a match, return the only possible position at which a
1057 // match can occur: input.end().
1058Some(HalfMatch::new(hm.pattern(), input.end()))
1059 }
1060 }
1061 }
10621063#[cfg_attr(feature = "perf-inline", inline(always))]
1064fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
1065if input.get_anchored().is_anchored() {
1066return self.core.is_match(cache, input);
1067 }
1068match self.try_search_half_anchored_rev(cache, input) {
1069Err(_err) => {
1070trace!("fast reverse anchored search failed: {_err}");
1071self.core.is_match_nofail(cache, input)
1072 }
1073Ok(None) => false,
1074Ok(Some(_)) => true,
1075 }
1076 }
10771078#[cfg_attr(feature = "perf-inline", inline(always))]
1079fn search_slots(
1080&self,
1081 cache: &mut Cache,
1082 input: &Input<'_>,
1083 slots: &mut [Option<NonMaxUsize>],
1084 ) -> Option<PatternID> {
1085if input.get_anchored().is_anchored() {
1086return self.core.search_slots(cache, input, slots);
1087 }
1088match self.try_search_half_anchored_rev(cache, input) {
1089Err(_err) => {
1090trace!("fast reverse anchored search failed: {_err}");
1091self.core.search_slots_nofail(cache, input, slots)
1092 }
1093Ok(None) => None,
1094Ok(Some(hm)) => {
1095if !self.core.is_capture_search_needed(slots.len()) {
1096trace!("asked for slots unnecessarily, skipping captures");
1097let m = Match::new(hm.pattern(), hm.offset()..input.end());
1098copy_match_to_slots(m, slots);
1099return Some(m.pattern());
1100 }
1101let start = hm.offset();
1102let input = input1103 .clone()
1104 .span(start..input.end())
1105 .anchored(Anchored::Pattern(hm.pattern()));
1106self.core.search_slots_nofail(cache, &input, slots)
1107 }
1108 }
1109 }
11101111#[cfg_attr(feature = "perf-inline", inline(always))]
1112fn which_overlapping_matches(
1113&self,
1114 cache: &mut Cache,
1115 input: &Input<'_>,
1116 patset: &mut PatternSet,
1117 ) {
1118// It seems like this could probably benefit from a reverse anchored
1119 // optimization, perhaps by doing an overlapping reverse search (which
1120 // the DFAs do support). I haven't given it much thought though, and
1121 // I'm currently focus more on the single pattern case.
1122self.core.which_overlapping_matches(cache, input, patset)
1123 }
1124}
11251126#[derive(#[automatically_derived]impl ::core::fmt::Debug for ReverseSuffix {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f,
"ReverseSuffix core pre", &self.core, &&self.pre)
}
}Debug)]
1127struct ReverseSuffix {
1128 core: Core,
1129 pre: Prefilter,
1130}
11311132impl ReverseSuffix {
1133fn new(core: Core, hirs: &[&Hir]) -> Result<ReverseSuffix, Core> {
1134if !core.info.config().get_auto_prefilter() {
1135debug!(
1136"skipping reverse suffix optimization because \
1137 automatic prefilters are disabled"
1138);
1139return Err(core);
1140 }
1141// Also like the reverse inner optimization, a reverse suffix encodes
1142 // leftmost-first match semantics.
1143if core.info.config().get_match_kind() != MatchKind::LeftmostFirst {
1144debug!(
1145"skipping reverse suffix optimization because \
1146 match kind is {:?} but this only supports leftmost-first",
1147 core.info.config().get_match_kind(),
1148 );
1149return Err(core);
1150 }
1151// Like the reverse inner optimization, we don't do this for regexes
1152 // that are always anchored. It could lead to scanning too much, but
1153 // could say "no match" much more quickly than running the regex
1154 // engine if the initial literal scan doesn't match. With that said,
1155 // the reverse suffix optimization has lower overhead, since it only
1156 // requires a reverse scan after a literal match to confirm or reject
1157 // the match. (Although, in the case of confirmation, it then needs to
1158 // do another forward scan to find the end position.)
1159 //
1160 // Note that the caller can still request an anchored search even
1161 // when the regex isn't anchored. We detect that case in the search
1162 // routines below and just fallback to the core engine. Currently this
1163 // optimization assumes all searches are unanchored, so if we do want
1164 // to enable this optimization for anchored searches, it will need a
1165 // little work to support it.
1166if core.info.is_always_anchored_start() {
1167debug!(
1168"skipping reverse suffix optimization because \
1169 the regex is always anchored at the start",
1170 );
1171return Err(core);
1172 }
1173// Only DFAs can do reverse searches (currently), so we need one of
1174 // them in order to do this optimization. It's possible (although
1175 // pretty unlikely) that we have neither and need to give up.
1176if !core.hybrid.is_some() && !core.dfa.is_some() {
1177debug!(
1178"skipping reverse suffix optimization because \
1179 we don't have a lazy DFA or a full DFA"
1180);
1181return Err(core);
1182 }
1183if core.pre.as_ref().map_or(false, |p| p.is_fast()) {
1184debug!(
1185"skipping reverse suffix optimization because \
1186 we already have a prefilter that we think is fast"
1187);
1188return Err(core);
1189 }
1190let kind = core.info.config().get_match_kind();
1191let suffixes = crate::util::prefilter::suffixes(kind, hirs);
1192let lcs = match suffixes.longest_common_suffix() {
1193None => {
1194debug!(
1195"skipping reverse suffix optimization because \
1196 a longest common suffix could not be found",
1197 );
1198return Err(core);
1199 }
1200Some(lcs) if lcs.is_empty() => {
1201debug!(
1202"skipping reverse suffix optimization because \
1203 the longest common suffix is the empty string",
1204 );
1205return Err(core);
1206 }
1207Some(lcs) => lcs,
1208 };
1209let pre = match Prefilter::new(kind, &[lcs]) {
1210Some(pre) => pre,
1211None => {
1212debug!(
1213"skipping reverse suffix optimization because \
1214 a prefilter could not be constructed from the \
1215 longest common suffix",
1216 );
1217return Err(core);
1218 }
1219 };
1220if !pre.is_fast() {
1221debug!(
1222"skipping reverse suffix optimization because \
1223 while we have a suffix prefilter, it is not \
1224 believed to be 'fast'"
1225);
1226return Err(core);
1227 }
1228if !reverse_suffix::has_no_earlier_match(hirs, &lcs) {
1229debug!(
1230"skipping reverse suffix optimization because \
1231 an earlier suffix match could be a complete match \
1232 inside of a larger match"
1233);
1234return Err(core);
1235 }
1236Ok(ReverseSuffix { core, pre })
1237 }
12381239#[cfg_attr(feature = "perf-inline", inline(always))]
1240fn try_search_half_start(
1241&self,
1242 cache: &mut Cache,
1243 input: &Input<'_>,
1244 ) -> Result<Option<HalfMatch>, RetryError> {
1245let mut span = input.get_span();
1246let mut min_start = 0;
1247loop {
1248let litmatch = match self.pre.find(input.haystack(), span) {
1249None => break,
1250Some(span) => span,
1251 };
1252trace!("reverse suffix scan found suffix match at {litmatch:?}");
1253let revinput = input1254 .clone()
1255 .anchored(Anchored::Yes)
1256 .span(input.start()..litmatch.end);
1257if let Some(hm) =
1258self.try_search_half_rev_limited(cache, &revinput, min_start)?
1259{
1260return Ok(Some(hm));
1261 }
12621263if span.start >= span.end {
1264break;
1265 }
1266span.start = litmatch.start.checked_add(1).unwrap();
1267min_start = litmatch.end;
1268 }
1269Ok(None)
1270 }
12711272#[cfg_attr(feature = "perf-inline", inline(always))]
1273fn try_search_half_fwd(
1274&self,
1275 cache: &mut Cache,
1276 input: &Input<'_>,
1277 ) -> Result<Option<HalfMatch>, RetryFailError> {
1278if let Some(e) = self.core.dfa.get(&input) {
1279trace!(
1280"using full DFA for forward reverse suffix search at {:?}",
1281 input.get_span()
1282 );
1283e.try_search_half_fwd(&input)
1284 } else if let Some(e) = self.core.hybrid.get(&input) {
1285trace!(
1286"using lazy DFA for forward reverse suffix search at {:?}",
1287 input.get_span()
1288 );
1289e.try_search_half_fwd(&mut cache.hybrid, &input)
1290 } else {
1291{
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("ReverseSuffix always has a DFA")));
}unreachable!("ReverseSuffix always has a DFA")1292 }
1293 }
12941295#[cfg_attr(feature = "perf-inline", inline(always))]
1296fn try_search_half_rev_limited(
1297&self,
1298 cache: &mut Cache,
1299 input: &Input<'_>,
1300 min_start: usize,
1301 ) -> Result<Option<HalfMatch>, RetryError> {
1302if let Some(e) = self.core.dfa.get(&input) {
1303trace!(
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 );
1309e.try_search_half_rev_limited(&input, min_start)
1310 } else if let Some(e) = self.core.hybrid.get(&input) {
1311trace!(
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 );
1317e.try_search_half_rev_limited(&mut cache.hybrid, &input, min_start)
1318 } else {
1319{
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("ReverseSuffix always has a DFA")));
}unreachable!("ReverseSuffix always has a DFA")1320 }
1321 }
1322}
13231324impl Strategyfor ReverseSuffix {
1325fn name(&self) -> Cow<'static, str> {
1326 Cow::Borrowed("reverse suffix")
1327 }
13281329#[cfg_attr(feature = "perf-inline", inline(always))]
1330fn group_info(&self) -> &GroupInfo {
1331self.core.group_info()
1332 }
13331334#[cfg_attr(feature = "perf-inline", inline(always))]
1335fn create_cache(&self) -> Cache {
1336self.core.create_cache()
1337 }
13381339#[cfg_attr(feature = "perf-inline", inline(always))]
1340fn reset_cache(&self, cache: &mut Cache) {
1341self.core.reset_cache(cache);
1342 }
13431344fn is_accelerated(&self) -> bool {
1345self.pre.is_fast()
1346 }
13471348fn memory_usage(&self) -> usize {
1349self.core.memory_usage() + self.pre.memory_usage()
1350 }
13511352#[cfg_attr(feature = "perf-inline", inline(always))]
1353fn search(&self, cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
1354if input.get_anchored().is_anchored() {
1355return self.core.search(cache, input);
1356 }
1357match self.try_search_half_start(cache, input) {
1358Err(RetryError::Quadratic(_err)) => {
1359trace!("reverse suffix optimization failed: {_err}");
1360self.core.search(cache, input)
1361 }
1362Err(RetryError::Fail(_err)) => {
1363trace!("reverse suffix reverse fast search failed: {_err}");
1364self.core.search_nofail(cache, input)
1365 }
1366Ok(None) => None,
1367Ok(Some(hm_start)) => {
1368let fwdinput = input1369 .clone()
1370 .anchored(Anchored::Pattern(hm_start.pattern()))
1371 .span(hm_start.offset()..input.end());
1372match self.try_search_half_fwd(cache, &fwdinput) {
1373Err(_err) => {
1374trace!(
1375"reverse suffix forward fast search failed: {_err}"
1376);
1377self.core.search_nofail(cache, input)
1378 }
1379Ok(None) => {
1380{
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("suffix match plus reverse match implies there must be a match")));
}unreachable!(
1381"suffix match plus reverse match implies \
1382 there must be a match",
1383 )1384 }
1385Ok(Some(hm_end)) => Some(Match::new(
1386hm_start.pattern(),
1387 hm_start.offset()..hm_end.offset(),
1388 )),
1389 }
1390 }
1391 }
1392 }
13931394#[cfg_attr(feature = "perf-inline", inline(always))]
1395fn search_half(
1396&self,
1397 cache: &mut Cache,
1398 input: &Input<'_>,
1399 ) -> Option<HalfMatch> {
1400if input.get_anchored().is_anchored() {
1401return self.core.search_half(cache, input);
1402 }
1403match self.try_search_half_start(cache, input) {
1404Err(RetryError::Quadratic(_err)) => {
1405trace!("reverse suffix half optimization failed: {_err}");
1406self.core.search_half(cache, input)
1407 }
1408Err(RetryError::Fail(_err)) => {
1409trace!(
1410"reverse suffix reverse fast half search failed: {_err}"
1411);
1412self.core.search_half_nofail(cache, input)
1413 }
1414Ok(None) => None,
1415Ok(Some(hm_start)) => {
1416// This is a bit subtle. It is tempting to just stop searching
1417 // at this point and return a half-match with an offset
1418 // corresponding to where the suffix was found. But the suffix
1419 // match does not necessarily correspond to the end of the
1420 // proper leftmost-first match. Consider /[a-z]+ing/ against
1421 // 'tingling'. The first suffix match is the first 'ing', and
1422 // the /[a-z]+/ matches the 't'. So if we stopped here, then
1423 // we'd report 'ting' as the match. But 'tingling' is the
1424 // correct match because of greediness.
1425let fwdinput = input1426 .clone()
1427 .anchored(Anchored::Pattern(hm_start.pattern()))
1428 .span(hm_start.offset()..input.end());
1429match self.try_search_half_fwd(cache, &fwdinput) {
1430Err(_err) => {
1431trace!(
1432"reverse suffix forward fast search failed: {_err}"
1433);
1434self.core.search_half_nofail(cache, input)
1435 }
1436Ok(None) => {
1437{
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("suffix match plus reverse match implies there must be a match")));
}unreachable!(
1438"suffix match plus reverse match implies \
1439 there must be a match",
1440 )1441 }
1442Ok(Some(hm_end)) => Some(hm_end),
1443 }
1444 }
1445 }
1446 }
14471448#[cfg_attr(feature = "perf-inline", inline(always))]
1449fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
1450if input.get_anchored().is_anchored() {
1451return self.core.is_match(cache, input);
1452 }
1453match self.try_search_half_start(cache, input) {
1454Err(RetryError::Quadratic(_err)) => {
1455trace!("reverse suffix half optimization failed: {_err}");
1456self.core.is_match_nofail(cache, input)
1457 }
1458Err(RetryError::Fail(_err)) => {
1459trace!(
1460"reverse suffix reverse fast half search failed: {_err}"
1461);
1462self.core.is_match_nofail(cache, input)
1463 }
1464Ok(None) => false,
1465Ok(Some(_)) => true,
1466 }
1467 }
14681469#[cfg_attr(feature = "perf-inline", inline(always))]
1470fn search_slots(
1471&self,
1472 cache: &mut Cache,
1473 input: &Input<'_>,
1474 slots: &mut [Option<NonMaxUsize>],
1475 ) -> Option<PatternID> {
1476if input.get_anchored().is_anchored() {
1477return self.core.search_slots(cache, input, slots);
1478 }
1479if !self.core.is_capture_search_needed(slots.len()) {
1480trace!("asked for slots unnecessarily, trying fast path");
1481let m = self.search(cache, input)?;
1482copy_match_to_slots(m, slots);
1483return Some(m.pattern());
1484 }
1485let hm_start = match self.try_search_half_start(cache, input) {
1486Err(RetryError::Quadratic(_err)) => {
1487trace!("reverse suffix captures optimization failed: {_err}");
1488return self.core.search_slots(cache, input, slots);
1489 }
1490Err(RetryError::Fail(_err)) => {
1491trace!(
1492"reverse suffix reverse fast captures search failed: \
1493 {_err}"
1494);
1495return self.core.search_slots_nofail(cache, input, slots);
1496 }
1497Ok(None) => return None,
1498Ok(Some(hm_start)) => hm_start,
1499 };
1500trace!(
1501"match found at {}..{} in capture search, \
1502 using another engine to find captures",
1503 hm_start.offset(),
1504 input.end(),
1505 );
1506let start = hm_start.offset();
1507let input = input1508 .clone()
1509 .span(start..input.end())
1510 .anchored(Anchored::Pattern(hm_start.pattern()));
1511self.core.search_slots_nofail(cache, &input, slots)
1512 }
15131514#[cfg_attr(feature = "perf-inline", inline(always))]
1515fn which_overlapping_matches(
1516&self,
1517 cache: &mut Cache,
1518 input: &Input<'_>,
1519 patset: &mut PatternSet,
1520 ) {
1521self.core.which_overlapping_matches(cache, input, patset)
1522 }
1523}
15241525#[derive(#[automatically_derived]impl ::core::fmt::Debug for ReverseInner {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field5_finish(f,
"ReverseInner core preinner nfarev hybrid dfa", &self.core,
&self.preinner, &self.nfarev, &self.hybrid, &&self.dfa)
}
}Debug)]
1526struct ReverseInner {
1527 core: Core,
1528 preinner: Prefilter,
1529 nfarev: NFA,
1530 hybrid: wrappers::ReverseHybrid,
1531 dfa: wrappers::ReverseDFA,
1532}
15331534impl ReverseInner {
1535fn new(core: Core, hirs: &[&Hir]) -> Result<ReverseInner, Core> {
1536if !core.info.config().get_auto_prefilter() {
1537debug!(
1538"skipping reverse inner optimization because \
1539 automatic prefilters are disabled"
1540);
1541return Err(core);
1542 }
1543// Currently we hard-code the assumption of leftmost-first match
1544 // semantics. This isn't a huge deal because 'all' semantics tend to
1545 // only be used for forward overlapping searches with multiple regexes,
1546 // and this optimization only supports a single pattern at the moment.
1547if core.info.config().get_match_kind() != MatchKind::LeftmostFirst {
1548debug!(
1549"skipping reverse inner optimization because \
1550 match kind is {:?} but this only supports leftmost-first",
1551 core.info.config().get_match_kind(),
1552 );
1553return Err(core);
1554 }
1555// It's likely that a reverse inner scan has too much overhead for it
1556 // to be worth it when the regex is anchored at the start. It is
1557 // possible for it to be quite a bit faster if the initial literal
1558 // scan fails to detect a match, in which case, we can say "no match"
1559 // very quickly. But this could be undesirable, e.g., scanning too far
1560 // or when the literal scan matches. If it matches, then confirming the
1561 // match requires a reverse scan followed by a forward scan to confirm
1562 // or reject, which is a fair bit of work.
1563 //
1564 // Note that the caller can still request an anchored search even
1565 // when the regex isn't anchored. We detect that case in the search
1566 // routines below and just fallback to the core engine. Currently this
1567 // optimization assumes all searches are unanchored, so if we do want
1568 // to enable this optimization for anchored searches, it will need a
1569 // little work to support it.
1570if core.info.is_always_anchored_start() {
1571debug!(
1572"skipping reverse inner optimization because \
1573 the regex is always anchored at the start",
1574 );
1575return Err(core);
1576 }
1577// Only DFAs can do reverse searches (currently), so we need one of
1578 // them in order to do this optimization. It's possible (although
1579 // pretty unlikely) that we have neither and need to give up.
1580if !core.hybrid.is_some() && !core.dfa.is_some() {
1581debug!(
1582"skipping reverse inner optimization because \
1583 we don't have a lazy DFA or a full DFA"
1584);
1585return Err(core);
1586 }
1587if core.pre.as_ref().map_or(false, |p| p.is_fast()) {
1588debug!(
1589"skipping reverse inner optimization because \
1590 we already have a prefilter that we think is fast"
1591);
1592return Err(core);
1593 } else if core.pre.is_some() {
1594debug!(
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 }
1600let prefilter = match reverse_inner::InnerPrefilter::new(hirs) {
1601Some(prefilter) => prefilter,
1602// N.B. the 'new' function emits debug messages explaining
1603 // why we bailed out here.
1604None => return Err(core),
1605 };
1606if !reverse_inner::has_no_earlier_match(
1607&prefilter.prefix,
1608&prefilter.literals,
1609 ) {
1610debug!(
1611"skipping reverse inner optimization because an inner \
1612 literal match could be confirmed before an earlier match"
1613);
1614return Err(core);
1615 }
1616debug!("building reverse NFA for prefix before inner literal");
1617let thompson_config = core1618 .info
1619 .config()
1620 .to_thompson_config()
1621 .reverse(true)
1622 .which_captures(WhichCaptures::None);
1623let result = thompson::Compiler::new()
1624 .configure(thompson_config)
1625 .build_from_hir(&prefilter.prefix);
1626let nfarev = match result {
1627Ok(nfarev) => nfarev,
1628Err(_err) => {
1629debug!(
1630"skipping reverse inner optimization because the \
1631 reverse NFA failed to build: {}",
1632 _err,
1633 );
1634return Err(core);
1635 }
1636 };
1637debug!("building reverse DFA for prefix before inner literal");
1638let dfa = if !core.info.config().get_dfa() {
1639 wrappers::ReverseDFA::none()
1640 } else {
1641 wrappers::ReverseDFA::new(&core.info, &nfarev)
1642 };
1643let hybrid = if !core.info.config().get_hybrid() {
1644 wrappers::ReverseHybrid::none()
1645 } else if dfa.is_some() {
1646debug!(
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 };
1654Ok(ReverseInner { core, preinner: prefilter.pre, nfarev, hybrid, dfa })
1655 }
16561657#[cfg_attr(feature = "perf-inline", inline(always))]
1658fn try_search_full(
1659&self,
1660 cache: &mut Cache,
1661 input: &Input<'_>,
1662 ) -> Result<Option<Match>, RetryError> {
1663let mut span = input.get_span();
1664let mut min_match_start = 0;
1665let mut min_pre_start = 0;
1666loop {
1667let litmatch = match self.preinner.find(input.haystack(), span) {
1668None => break,
1669Some(span) => span,
1670 };
1671if litmatch.start < min_pre_start {
1672trace!(
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 );
1677return Err(RetryError::Quadratic(RetryQuadraticError::new()));
1678 }
1679trace!("reverse inner scan found inner match at {litmatch:?}");
1680let revinput = input1681 .clone()
1682 .anchored(Anchored::Yes)
1683 .span(input.start()..litmatch.start);
1684// Note that in addition to the literal search above scanning past
1685 // our minimum start point, this routine can also return an error
1686 // as a result of detecting possible quadratic behavior if the
1687 // reverse scan goes past the minimum start point. That is, the
1688 // literal search might not, but the reverse regex search for the
1689 // prefix might!
1690if let Some(hm_start) = self.try_search_half_rev_limited(
1691 cache,
1692&revinput,
1693 min_match_start,
1694 )? {
1695let fwdinput = input1696 .clone()
1697 .anchored(Anchored::Pattern(hm_start.pattern()))
1698 .span(hm_start.offset()..input.end());
1699match self.try_search_half_fwd_stopat(cache, &fwdinput)? {
1700Err(stopat) => {
1701min_pre_start = stopat;
1702span.start = litmatch.start.checked_add(1).unwrap();
1703 }
1704Ok(hm_end) => {
1705return Ok(Some(Match::new(
1706hm_start.pattern(),
1707 hm_start.offset()..hm_end.offset(),
1708 )));
1709 }
1710 }
1711 }
17121713if span.start >= span.end {
1714break;
1715 }
1716span.start = litmatch.start.checked_add(1).unwrap();
1717min_match_start = litmatch.end;
1718 }
1719Ok(None)
1720 }
17211722#[cfg_attr(feature = "perf-inline", inline(always))]
1723fn try_search_half_fwd_stopat(
1724&self,
1725 cache: &mut Cache,
1726 input: &Input<'_>,
1727 ) -> Result<Result<HalfMatch, usize>, RetryFailError> {
1728if let Some(e) = self.core.dfa.get(&input) {
1729trace!(
1730"using full DFA for forward reverse inner search at {:?}",
1731 input.get_span()
1732 );
1733e.try_search_half_fwd_stopat(&input)
1734 } else if let Some(e) = self.core.hybrid.get(&input) {
1735trace!(
1736"using lazy DFA for forward reverse inner search at {:?}",
1737 input.get_span()
1738 );
1739e.try_search_half_fwd_stopat(&mut cache.hybrid, &input)
1740 } else {
1741{
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("ReverseInner always has a DFA")));
}unreachable!("ReverseInner always has a DFA")1742 }
1743 }
17441745#[cfg_attr(feature = "perf-inline", inline(always))]
1746fn try_search_half_rev_limited(
1747&self,
1748 cache: &mut Cache,
1749 input: &Input<'_>,
1750 min_start: usize,
1751 ) -> Result<Option<HalfMatch>, RetryError> {
1752if let Some(e) = self.dfa.get(&input) {
1753trace!(
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 );
1759e.try_search_half_rev_limited(&input, min_start)
1760 } else if let Some(e) = self.hybrid.get(&input) {
1761trace!(
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 );
1767e.try_search_half_rev_limited(
1768&mut cache.revhybrid,
1769&input,
1770min_start,
1771 )
1772 } else {
1773{
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("ReverseInner always has a DFA")));
}unreachable!("ReverseInner always has a DFA")1774 }
1775 }
1776}
17771778impl Strategyfor ReverseInner {
1779fn name(&self) -> Cow<'static, str> {
1780 Cow::Borrowed("reverse inner")
1781 }
17821783#[cfg_attr(feature = "perf-inline", inline(always))]
1784fn group_info(&self) -> &GroupInfo {
1785self.core.group_info()
1786 }
17871788#[cfg_attr(feature = "perf-inline", inline(always))]
1789fn create_cache(&self) -> Cache {
1790let mut cache = self.core.create_cache();
1791cache.revhybrid = self.hybrid.create_cache();
1792cache1793 }
17941795#[cfg_attr(feature = "perf-inline", inline(always))]
1796fn reset_cache(&self, cache: &mut Cache) {
1797self.core.reset_cache(cache);
1798cache.revhybrid.reset(&self.hybrid);
1799 }
18001801fn is_accelerated(&self) -> bool {
1802self.preinner.is_fast()
1803 }
18041805fn memory_usage(&self) -> usize {
1806self.core.memory_usage()
1807 + self.preinner.memory_usage()
1808 + self.nfarev.memory_usage()
1809 + self.dfa.memory_usage()
1810 }
18111812#[cfg_attr(feature = "perf-inline", inline(always))]
1813fn search(&self, cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
1814if input.get_anchored().is_anchored() {
1815return self.core.search(cache, input);
1816 }
1817match self.try_search_full(cache, input) {
1818Err(RetryError::Quadratic(_err)) => {
1819trace!("reverse inner optimization failed: {_err}");
1820self.core.search(cache, input)
1821 }
1822Err(RetryError::Fail(_err)) => {
1823trace!("reverse inner fast search failed: {_err}");
1824self.core.search_nofail(cache, input)
1825 }
1826Ok(matornot) => matornot,
1827 }
1828 }
18291830#[cfg_attr(feature = "perf-inline", inline(always))]
1831fn search_half(
1832&self,
1833 cache: &mut Cache,
1834 input: &Input<'_>,
1835 ) -> Option<HalfMatch> {
1836if input.get_anchored().is_anchored() {
1837return self.core.search_half(cache, input);
1838 }
1839match self.try_search_full(cache, input) {
1840Err(RetryError::Quadratic(_err)) => {
1841trace!("reverse inner half optimization failed: {_err}");
1842self.core.search_half(cache, input)
1843 }
1844Err(RetryError::Fail(_err)) => {
1845trace!("reverse inner fast half search failed: {_err}");
1846self.core.search_half_nofail(cache, input)
1847 }
1848Ok(None) => None,
1849Ok(Some(m)) => Some(HalfMatch::new(m.pattern(), m.end())),
1850 }
1851 }
18521853#[cfg_attr(feature = "perf-inline", inline(always))]
1854fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
1855if input.get_anchored().is_anchored() {
1856return self.core.is_match(cache, input);
1857 }
1858match self.try_search_full(cache, input) {
1859Err(RetryError::Quadratic(_err)) => {
1860trace!("reverse inner half optimization failed: {_err}");
1861self.core.is_match_nofail(cache, input)
1862 }
1863Err(RetryError::Fail(_err)) => {
1864trace!("reverse inner fast half search failed: {_err}");
1865self.core.is_match_nofail(cache, input)
1866 }
1867Ok(None) => false,
1868Ok(Some(_)) => true,
1869 }
1870 }
18711872#[cfg_attr(feature = "perf-inline", inline(always))]
1873fn search_slots(
1874&self,
1875 cache: &mut Cache,
1876 input: &Input<'_>,
1877 slots: &mut [Option<NonMaxUsize>],
1878 ) -> Option<PatternID> {
1879if input.get_anchored().is_anchored() {
1880return self.core.search_slots(cache, input, slots);
1881 }
1882if !self.core.is_capture_search_needed(slots.len()) {
1883trace!("asked for slots unnecessarily, trying fast path");
1884let m = self.search(cache, input)?;
1885copy_match_to_slots(m, slots);
1886return Some(m.pattern());
1887 }
1888let m = match self.try_search_full(cache, input) {
1889Err(RetryError::Quadratic(_err)) => {
1890trace!("reverse inner captures optimization failed: {_err}");
1891return self.core.search_slots(cache, input, slots);
1892 }
1893Err(RetryError::Fail(_err)) => {
1894trace!("reverse inner fast captures search failed: {_err}");
1895return self.core.search_slots_nofail(cache, input, slots);
1896 }
1897Ok(None) => return None,
1898Ok(Some(m)) => m,
1899 };
1900trace!(
1901"match found at {}..{} in capture search, \
1902 using another engine to find captures",
1903 m.start(),
1904 m.end(),
1905 );
1906let input = input1907 .clone()
1908 .span(m.start()..m.end())
1909 .anchored(Anchored::Pattern(m.pattern()));
1910self.core.search_slots_nofail(cache, &input, slots)
1911 }
19121913#[cfg_attr(feature = "perf-inline", inline(always))]
1914fn which_overlapping_matches(
1915&self,
1916 cache: &mut Cache,
1917 input: &Input<'_>,
1918 patset: &mut PatternSet,
1919 ) {
1920self.core.which_overlapping_matches(cache, input, patset)
1921 }
1922}
19231924/// Copies the offsets in the given match to the corresponding positions in
1925/// `slots`.
1926///
1927/// In effect, this sets the slots corresponding to the implicit group for the
1928/// pattern in the given match. If the indices for the corresponding slots do
1929/// not exist, then no slots are set.
1930///
1931/// This is useful when the caller provides slots (or captures), but you use a
1932/// regex engine that doesn't operate on slots (like a lazy DFA). This function
1933/// lets you map the match you get back to the slots provided by the caller.
1934#[cfg_attr(feature = "perf-inline", inline(always))]
1935fn copy_match_to_slots(m: Match, slots: &mut [Option<NonMaxUsize>]) {
1936let slot_start = m.pattern().as_usize() * 2;
1937let slot_end = slot_start + 1;
1938if let Some(slot) = slots.get_mut(slot_start) {
1939*slot = NonMaxUsize::new(m.start());
1940 }
1941if let Some(slot) = slots.get_mut(slot_end) {
1942*slot = NonMaxUsize::new(m.end());
1943 }
1944}
19451946// We only test which strategy we get when all literal features are enabled.
1947// Other cases are less substantially less interesting.
1948//
1949// We also don't test this on miri since it takes forever.
1950//
1951// We also require `unicode-perl` since some regexes use `\w`, `\d` and `\s`.
1952#[cfg(all(
1953 feature = "perf-literal-substring",
1954 feature = "perf-literal-multisubstring",
1955 feature = "unicode-perl",
1956 not(miri),
1957))]
1958#[cfg(test)]
1959mod tests {
1960use alloc::{format, string::String, vec::Vec};
19611962use crate::{meta::regex::Config, util::syntax};
19631964use super::*;
19651966fn teddy_probably_available() -> bool {
1967cfg!(any(target_arch = "x86_64", target_arch = "aarch64"))
1968 }
19691970#[track_caller]
1971fn strategy_with(patterns: &[&str], config: Config) -> Arc<dyn Strategy> {
1972let hirs = syntax::parse_many(patterns).unwrap();
1973let hirs: Vec<&Hir> = hirs.iter().collect();
1974let info = RegexInfo::new(config, &hirs);
1975 new(&info, &hirs).unwrap()
1976 }
19771978#[track_caller]
1979fn assert_strategy(name: &'static str, patterns: &[&str]) {
1980 assert_strategy_with(name, patterns, Config::new());
1981 }
19821983#[track_caller]
1984fn assert_strategy_with(
1985 name: &'static str,
1986 patterns: &[&str],
1987 config: Config,
1988 ) {
1989let strategy = strategy_with(patterns, config);
1990assert_eq!(name, strategy.name().as_ref());
1991 }
19921993fn literal_alternation(count: usize) -> String {
1994let mut pattern = String::new();
1995for i in 0..count {
1996if i > 0 {
1997 pattern.push('|');
1998 }
1999 pattern.push_str(&format!("needle{i:05}"));
2000 }
2001 pattern
2002 }
20032004#[test]
2005fn 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"]);
2010if teddy_probably_available() {
2011 assert_strategy(
2012"prefilter teddy",
2013&["Samwise|Gandalf|Holmes|Watson"],
2014 );
2015 }
2016 }
20172018#[test]
2019fn pre_from_prefixes_rejects_inexact_literal() {
2020 assert_strategy("core", &["a+"]);
2021 }
20222023#[test]
2024fn pre_from_prefixes_rejects_empty_literal() {
2025 assert_strategy("core", &[""]);
2026 }
20272028#[test]
2029fn pre_from_prefixes_rejects_multiple_patterns() {
2030 assert_strategy("core", &["a", "b"]);
2031 assert_strategy("core", &["a|b", "c|d"]);
2032 }
20332034#[test]
2035fn pre_from_prefixes_rejects_captures() {
2036 assert_strategy("core", &["(a)"]);
2037// ... but not when it's a non-capture.
2038assert_strategy("prefilter memchr", &["(?:a)"]);
2039// ... or when there is a capture, but it's optimized out.
2040assert_strategy("prefilter memchr", &["(){0}a"]);
2041 }
20422043#[test]
2044fn pre_from_prefixes_rejects_look_around() {
2045 assert_strategy("core", &[r"a\b"]);
2046 }
20472048#[test]
2049fn pre_from_prefixes_rejects_non_leftmost_first() {
2050 assert_strategy_with(
2051"core",
2052&["a"],
2053 Config::new().match_kind(MatchKind::All),
2054 );
2055 }
20562057#[test]
2058fn pre_from_alternation_literals_accepts_large_alternation() {
2059let pattern = literal_alternation(3_000);
2060 assert_strategy("prefilter aho-corasick", &[&pattern]);
2061 }
20622063#[test]
2064fn pre_from_alternation_literals_rejects_small_alternation() {
2065let pattern = literal_alternation(2_999);
2066 assert_strategy("core", &[&pattern]);
2067 }
20682069#[test]
2070fn pre_from_alternation_literals_rejects_captures() {
2071let pattern = format!("({})", literal_alternation(3_000));
2072 assert_strategy("core", &[&pattern]);
2073 }
20742075#[test]
2076fn pre_from_alternation_literals_rejects_look_around() {
2077let pattern = format!(r"(?:{})\b", literal_alternation(3_000));
2078 assert_strategy("core", &[&pattern]);
2079 }
20802081#[test]
2082fn pre_from_alternation_literals_rejects_non_leftmost_first() {
2083let pattern = literal_alternation(3_000);
2084 assert_strategy_with(
2085"core",
2086&[&pattern],
2087 Config::new().match_kind(MatchKind::All),
2088 );
2089 }
20902091#[test]
2092fn pre_from_alternation_literals_rejects_multiple_patterns() {
2093let pattern1 = literal_alternation(3_000);
2094let pattern2 = literal_alternation(3_000);
2095 assert_strategy("core", &[&pattern1, &pattern2]);
2096 }
20972098#[test]
2099fn core_selected_when_no_special_strategy_applies() {
2100 assert_strategy("core", &["[a-z]+"]);
2101 }
21022103#[test]
2104fn 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 }
21112112#[test]
2113fn 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 }
21202121#[test]
2122fn reverse_anchored_accepts_end_anchored() {
2123 assert_strategy("reverse anchored", &[r"\w+Holmes$"]);
2124 }
21252126#[test]
2127fn reverse_anchored_accepts_multiple_end_anchored_patterns() {
2128 assert_strategy("reverse anchored", &[r"\w+Holmes$", r"\w+Watson$"]);
2129 }
21302131#[test]
2132fn 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 }
21392140#[test]
2141fn reverse_anchored_rejects_start_and_end_anchored() {
2142 assert_strategy("core", &[r"^\w+Holmes$"]);
2143 }
21442145#[test]
2146fn 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 }
21532154#[test]
2155fn 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 }
21592160#[test]
2161fn reverse_suffix_rejects_internal_suffix_with_word_boundary() {
2162 assert_strategy("core", &[r"\b\w+nn\b"]);
2163 }
21642165#[test]
2166fn 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 }
21762177#[test]
2178fn 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 }
21852186#[test]
2187fn reverse_suffix_rejects_safe_nfa_overlap() {
2188 assert_strategy("reverse inner", &[r"(a|aa)b"]);
2189 }
21902191#[test]
2192fn reverse_suffix_accepts_fixed_length_prefix() {
2193 assert_strategy("reverse suffix", &[r"[A-Z][0-9]XYZ"]);
2194 }
21952196#[test]
2197fn reverse_suffix_rejects_multiple_patterns_with_common_suffix() {
2198 assert_strategy("core", &[r"\d+XYZ", r"\w+XYZ"]);
2199 }
22002201#[test]
2202fn reverse_suffix_rejects_auto_prefilter_disabled() {
2203 assert_strategy_with(
2204"core",
2205&[r"\d+XYZ"],
2206 Config::new().auto_prefilter(false),
2207 );
2208 }
22092210#[test]
2211fn 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 }
22182219#[test]
2220fn reverse_suffix_rejects_anchored_start() {
2221 assert_strategy("core", &[r"^\d+XYZ"]);
2222 }
22232224#[test]
2225fn reverse_suffix_rejects_variable_length_prefix() {
2226 assert_strategy("core", &[r"(?:[A-Za-z]ab)?b"]);
2227 }
22282229#[test]
2230fn 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 }
22372238#[test]
2239fn reverse_suffix_rejects_existing_fast_prefilter() {
2240 assert_strategy("core", &[r"abc\w+XYZ"]);
2241 }
22422243#[test]
2244fn reverse_suffix_rejects_no_literal_suffix() {
2245 assert_strategy("core", &[r"[a-z]+"]);
2246 }
22472248#[test]
2249fn reverse_suffix_rejects_multiple_patterns_without_common_suffix() {
2250 assert_strategy("core", &[r"\d+XYZ", r"\w+ABC"]);
2251 }
22522253#[test]
2254fn 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 }
22582259#[test]
2260fn reverse_suffix_rejects_unsafe_overlap() {
2261 assert_strategy("core", &[r".abb|b"]);
2262 assert_strategy("core", &[r".bb|b"]);
2263 }
22642265#[test]
2266fn reverse_suffix_rejects_fully_absorbed_literal() {
2267 assert_strategy("core", &[r"(?:[a-wyz]{3}|[a-wyz]).b"]);
2268 }
22692270#[test]
2271fn reverse_suffix_rejects_disjoint_trailing_component() {
2272 assert_strategy("core", &[r"(?:[ac-z]{2}b[ac-z])?[ac-z]b"]);
2273 }
22742275#[test]
2276fn reverse_suffix_rejects_internal_candidate_before_absorber() {
2277 assert_strategy("core", &[r"(?:[a-z]cb|c)[a-z]*b"]);
2278 }
22792280#[test]
2281fn 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 }
22882289#[test]
2290fn reverse_inner_accepts_fixed_length_prefix() {
2291 assert_strategy("reverse inner", &[r"[A-Z][0-9]@(foo|bar)"]);
2292 }
22932294#[test]
2295fn 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+"]);
2299if teddy_probably_available() {
2300 assert_strategy(
2301"reverse inner",
2302&[r"\w+\s+(?:Holmes|Watson)\s+\w+"],
2303 );
2304 }
2305 }
23062307#[test]
2308fn 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 }
23142315#[test]
2316fn 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 }
23202321#[test]
2322fn reverse_inner_rejects_auto_prefilter_disabled() {
2323 assert_strategy_with(
2324"core",
2325&[r"\w+@\w+"],
2326 Config::new().auto_prefilter(false),
2327 );
2328 }
23292330#[test]
2331fn 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 }
23382339#[test]
2340fn reverse_inner_rejects_anchored_start() {
2341 assert_strategy("core", &[r"^\w+@\w+"]);
2342 }
23432344#[test]
2345fn 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 }
23522353#[test]
2354fn reverse_inner_rejects_existing_fast_prefilter() {
2355 assert_strategy("core", &[r"abc\w+@\w+"]);
2356 }
23572358#[test]
2359fn reverse_inner_rejects_multiple_patterns() {
2360 assert_strategy("core", &[r"\w+@\w+", r"\w+#\w+"]);
2361 }
23622363#[test]
2364fn reverse_inner_rejects_no_top_level_concat() {
2365 assert_strategy("core", &[r"\w+"]);
2366 }
23672368#[test]
2369fn reverse_inner_rejects_unsafe_overlap() {
2370 assert_strategy("core", &[r"a.*@\w+"]);
2371 }
23722373#[test]
2374fn reverse_inner_rejects_literal_crossing_prefix_boundary() {
2375 assert_strategy("core", &[r"(?:[ac-z]{3}|[ac-z])(?:aba|baa)"]);
2376 }
23772378#[test]
2379fn reverse_inner_rejects_internal_separator_alignment() {
2380 assert_strategy("core", &[r"(?:.abc)?[a]+[b]+c.*"]);
2381 }
23822383#[test]
2384fn 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}