Coverage Report

Created: 2026-08-13 06:34

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