Coverage Report

Created: 2026-10-28 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/regex/regex-automata/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
86.7k
pub(super) fn new(
82
86.7k
    info: &RegexInfo,
83
86.7k
    hirs: &[&Hir],
84
86.7k
) -> 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
86.7k
    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
2.11k
        debug!("skipping literal extraction since regex is anchored");
107
2.11k
        None
108
84.6k
    } else if let Some(pre) = info.config().get_prefilter() {
109
0
        debug!(
110
0
            "skipping literal extraction since the caller provided a prefilter"
111
        );
112
0
        Some(pre.clone())
113
84.6k
    } else if info.config().get_auto_prefilter() {
114
84.6k
        let kind = info.config().get_match_kind();
115
84.6k
        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
84.6k
        if let Some(pre) = Pre::from_prefixes(info, &prefixes) {
119
14.1k
            debug!(
120
0
                "found that the regex can be broken down to a literal \
121
0
                 search, avoiding the regex engine entirely",
122
            );
123
14.1k
            debug!("using {} strategy", pre.name());
124
14.1k
            return Ok(pre);
125
70.5k
        }
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
70.5k
        if let Some(pre) = Pre::from_alternation_literals(info, hirs) {
139
357
            debug!(
140
0
                "found plain alternation of literals, \
141
0
                 avoiding regex engine entirely and using Aho-Corasick"
142
            );
143
357
            debug!("using {} strategy", pre.name());
144
357
            return Ok(pre);
145
70.1k
        }
146
70.1k
        prefixes.literals().and_then(|strings| {
147
30.8k
            debug!(
148
0
                "creating prefilter from {} literals: {:?}",
149
0
                strings.len(),
150
                strings,
151
            );
152
30.8k
            Prefilter::new(kind, strings)
153
30.8k
        })
154
    } else {
155
0
        debug!("skipping literal extraction since prefilters were disabled");
156
0
        None
157
    };
158
72.2k
    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
70.0k
    core = match ReverseAnchored::new(core) {
169
68.8k
        Err(core) => core,
170
1.18k
        Ok(ra) => {
171
1.18k
            debug!("using {} strategy", ra.name());
172
1.18k
            return Ok(Arc::new(ra));
173
        }
174
    };
175
68.8k
    core = match ReverseSuffix::new(core, hirs) {
176
66.2k
        Err(core) => core,
177
2.59k
        Ok(rs) => {
178
2.59k
            debug!("using {} strategy", rs.name());
179
2.59k
            return Ok(Arc::new(rs));
180
        }
181
    };
182
66.2k
    core = match ReverseInner::new(core, hirs) {
183
62.1k
        Err(core) => core,
184
4.14k
        Ok(ri) => {
185
4.14k
            debug!("using {} strategy", ri.name());
186
4.14k
            return Ok(Arc::new(ri));
187
        }
188
    };
189
62.1k
    debug!("using {} strategy", core.name());
190
62.1k
    Ok(Arc::new(core))
191
86.7k
}
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
14.5k
    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
14.5k
        let group_info = GroupInfo::new([[None::<&str>]]).unwrap();
206
14.5k
        Arc::new(Pre { pre, group_info })
207
14.5k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::aho_corasick::AhoCorasick>>::new
Line
Count
Source
200
1.31k
    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
1.31k
        let group_info = GroupInfo::new([[None::<&str>]]).unwrap();
206
1.31k
        Arc::new(Pre { pre, group_info })
207
1.31k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::teddy::Teddy>>::new
Line
Count
Source
200
9.13k
    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
9.13k
        let group_info = GroupInfo::new([[None::<&str>]]).unwrap();
206
9.13k
        Arc::new(Pre { pre, group_info })
207
9.13k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr>>::new
Line
Count
Source
200
1.16k
    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
1.16k
        let group_info = GroupInfo::new([[None::<&str>]]).unwrap();
206
1.16k
        Arc::new(Pre { pre, group_info })
207
1.16k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr2>>::new
Line
Count
Source
200
245
    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
245
        let group_info = GroupInfo::new([[None::<&str>]]).unwrap();
206
245
        Arc::new(Pre { pre, group_info })
207
245
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr3>>::new
Line
Count
Source
200
158
    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
158
        let group_info = GroupInfo::new([[None::<&str>]]).unwrap();
206
158
        Arc::new(Pre { pre, group_info })
207
158
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memmem::Memmem>>::new
Line
Count
Source
200
2.46k
    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
2.46k
        let group_info = GroupInfo::new([[None::<&str>]]).unwrap();
206
2.46k
        Arc::new(Pre { pre, group_info })
207
2.46k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::byteset::ByteSet>>::new
Line
Count
Source
200
43
    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
43
        let group_info = GroupInfo::new([[None::<&str>]]).unwrap();
206
43
        Arc::new(Pre { pre, group_info })
207
43
    }
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
84.6k
    fn from_prefixes(
230
84.6k
        info: &RegexInfo,
231
84.6k
        prefixes: &literal::Seq,
232
84.6k
    ) -> Option<Arc<dyn Strategy>> {
233
84.6k
        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
84.6k
        if !prefixes.is_exact() {
238
58.8k
            return None;
239
25.8k
        }
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
25.8k
        if info.pattern_len() != 1 {
247
0
            return None;
248
25.8k
        }
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
25.8k
        if info.props()[0].explicit_captures_len() != 0 {
254
2.68k
            return None;
255
23.1k
        }
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
23.1k
        if !info.props()[0].look_set().is_empty() {
264
6.89k
            return None;
265
16.2k
        }
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
16.2k
        if kind != MatchKind::LeftmostFirst {
270
0
            return None;
271
16.2k
        }
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
16.2k
        let prefixes = prefixes.literals().unwrap();
285
16.2k
        debug!(
286
0
            "trying to bypass regex engine by creating \
287
0
             prefilter from {} literals: {:?}",
288
0
            prefixes.len(),
289
            prefixes,
290
        );
291
16.2k
        let choice = match prefilter::Choice::new(kind, prefixes) {
292
14.1k
            Some(choice) => choice,
293
            None => {
294
2.06k
                debug!(
295
0
                    "regex bypass failed because no prefilter could be built"
296
                );
297
2.06k
                return None;
298
            }
299
        };
300
14.1k
        let strat: Arc<dyn Strategy> = match choice {
301
1.16k
            prefilter::Choice::Memchr(pre) => Pre::new(pre),
302
245
            prefilter::Choice::Memchr2(pre) => Pre::new(pre),
303
158
            prefilter::Choice::Memchr3(pre) => Pre::new(pre),
304
2.46k
            prefilter::Choice::Memmem(pre) => Pre::new(pre),
305
9.13k
            prefilter::Choice::Teddy(pre) => Pre::new(pre),
306
43
            prefilter::Choice::ByteSet(pre) => Pre::new(pre),
307
954
            prefilter::Choice::AhoCorasick(pre) => Pre::new(pre),
308
        };
309
14.1k
        Some(strat)
310
84.6k
    }
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
70.5k
    fn from_alternation_literals(
320
70.5k
        info: &RegexInfo,
321
70.5k
        hirs: &[&Hir],
322
70.5k
    ) -> Option<Arc<dyn Strategy>> {
323
        use crate::util::prefilter::AhoCorasick;
324
325
70.5k
        let lits = crate::meta::literal::alternation_literals(info, hirs)?;
326
357
        let ac = AhoCorasick::new(MatchKind::LeftmostFirst, &lits)?;
327
357
        Some(Pre::new(ac))
328
70.5k
    }
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
16.5k
    fn group_info(&self) -> &GroupInfo {
367
16.5k
        &self.group_info
368
16.5k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::aho_corasick::AhoCorasick> as regex_automata::meta::strategy::Strategy>::group_info
Line
Count
Source
366
759
    fn group_info(&self) -> &GroupInfo {
367
759
        &self.group_info
368
759
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::teddy::Teddy> as regex_automata::meta::strategy::Strategy>::group_info
Line
Count
Source
366
12.0k
    fn group_info(&self) -> &GroupInfo {
367
12.0k
        &self.group_info
368
12.0k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr> as regex_automata::meta::strategy::Strategy>::group_info
Line
Count
Source
366
965
    fn group_info(&self) -> &GroupInfo {
367
965
        &self.group_info
368
965
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr2> as regex_automata::meta::strategy::Strategy>::group_info
Line
Count
Source
366
187
    fn group_info(&self) -> &GroupInfo {
367
187
        &self.group_info
368
187
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr3> as regex_automata::meta::strategy::Strategy>::group_info
Line
Count
Source
366
142
    fn group_info(&self) -> &GroupInfo {
367
142
        &self.group_info
368
142
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memmem::Memmem> as regex_automata::meta::strategy::Strategy>::group_info
Line
Count
Source
366
2.40k
    fn group_info(&self) -> &GroupInfo {
367
2.40k
        &self.group_info
368
2.40k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::byteset::ByteSet> as regex_automata::meta::strategy::Strategy>::group_info
Line
Count
Source
366
66
    fn group_info(&self) -> &GroupInfo {
367
66
        &self.group_info
368
66
    }
369
370
9.10k
    fn create_cache(&self) -> Cache {
371
9.10k
        Cache {
372
9.10k
            capmatches: Captures::all(self.group_info().clone()),
373
9.10k
            pikevm: wrappers::PikeVMCache::none(),
374
9.10k
            backtrack: wrappers::BoundedBacktrackerCache::none(),
375
9.10k
            onepass: wrappers::OnePassCache::none(),
376
9.10k
            hybrid: wrappers::HybridCache::none(),
377
9.10k
            revhybrid: wrappers::ReverseHybridCache::none(),
378
9.10k
        }
379
9.10k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::aho_corasick::AhoCorasick> as regex_automata::meta::strategy::Strategy>::create_cache
Line
Count
Source
370
362
    fn create_cache(&self) -> Cache {
371
362
        Cache {
372
362
            capmatches: Captures::all(self.group_info().clone()),
373
362
            pikevm: wrappers::PikeVMCache::none(),
374
362
            backtrack: wrappers::BoundedBacktrackerCache::none(),
375
362
            onepass: wrappers::OnePassCache::none(),
376
362
            hybrid: wrappers::HybridCache::none(),
377
362
            revhybrid: wrappers::ReverseHybridCache::none(),
378
362
        }
379
362
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::teddy::Teddy> as regex_automata::meta::strategy::Strategy>::create_cache
Line
Count
Source
370
7.18k
    fn create_cache(&self) -> Cache {
371
7.18k
        Cache {
372
7.18k
            capmatches: Captures::all(self.group_info().clone()),
373
7.18k
            pikevm: wrappers::PikeVMCache::none(),
374
7.18k
            backtrack: wrappers::BoundedBacktrackerCache::none(),
375
7.18k
            onepass: wrappers::OnePassCache::none(),
376
7.18k
            hybrid: wrappers::HybridCache::none(),
377
7.18k
            revhybrid: wrappers::ReverseHybridCache::none(),
378
7.18k
        }
379
7.18k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr> as regex_automata::meta::strategy::Strategy>::create_cache
Line
Count
Source
370
224
    fn create_cache(&self) -> Cache {
371
224
        Cache {
372
224
            capmatches: Captures::all(self.group_info().clone()),
373
224
            pikevm: wrappers::PikeVMCache::none(),
374
224
            backtrack: wrappers::BoundedBacktrackerCache::none(),
375
224
            onepass: wrappers::OnePassCache::none(),
376
224
            hybrid: wrappers::HybridCache::none(),
377
224
            revhybrid: wrappers::ReverseHybridCache::none(),
378
224
        }
379
224
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr2> as regex_automata::meta::strategy::Strategy>::create_cache
Line
Count
Source
370
88
    fn create_cache(&self) -> Cache {
371
88
        Cache {
372
88
            capmatches: Captures::all(self.group_info().clone()),
373
88
            pikevm: wrappers::PikeVMCache::none(),
374
88
            backtrack: wrappers::BoundedBacktrackerCache::none(),
375
88
            onepass: wrappers::OnePassCache::none(),
376
88
            hybrid: wrappers::HybridCache::none(),
377
88
            revhybrid: wrappers::ReverseHybridCache::none(),
378
88
        }
379
88
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr3> as regex_automata::meta::strategy::Strategy>::create_cache
Line
Count
Source
370
69
    fn create_cache(&self) -> Cache {
371
69
        Cache {
372
69
            capmatches: Captures::all(self.group_info().clone()),
373
69
            pikevm: wrappers::PikeVMCache::none(),
374
69
            backtrack: wrappers::BoundedBacktrackerCache::none(),
375
69
            onepass: wrappers::OnePassCache::none(),
376
69
            hybrid: wrappers::HybridCache::none(),
377
69
            revhybrid: wrappers::ReverseHybridCache::none(),
378
69
        }
379
69
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memmem::Memmem> as regex_automata::meta::strategy::Strategy>::create_cache
Line
Count
Source
370
1.13k
    fn create_cache(&self) -> Cache {
371
1.13k
        Cache {
372
1.13k
            capmatches: Captures::all(self.group_info().clone()),
373
1.13k
            pikevm: wrappers::PikeVMCache::none(),
374
1.13k
            backtrack: wrappers::BoundedBacktrackerCache::none(),
375
1.13k
            onepass: wrappers::OnePassCache::none(),
376
1.13k
            hybrid: wrappers::HybridCache::none(),
377
1.13k
            revhybrid: wrappers::ReverseHybridCache::none(),
378
1.13k
        }
379
1.13k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::byteset::ByteSet> as regex_automata::meta::strategy::Strategy>::create_cache
Line
Count
Source
370
39
    fn create_cache(&self) -> Cache {
371
39
        Cache {
372
39
            capmatches: Captures::all(self.group_info().clone()),
373
39
            pikevm: wrappers::PikeVMCache::none(),
374
39
            backtrack: wrappers::BoundedBacktrackerCache::none(),
375
39
            onepass: wrappers::OnePassCache::none(),
376
39
            hybrid: wrappers::HybridCache::none(),
377
39
            revhybrid: wrappers::ReverseHybridCache::none(),
378
39
        }
379
39
    }
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
19.5k
    fn search(&self, _cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
393
19.5k
        if input.is_done() {
394
0
            return None;
395
19.5k
        }
396
19.5k
        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
19.5k
        }
402
19.5k
        self.pre
403
19.5k
            .find(input.haystack(), input.get_span())
404
19.5k
            .map(|sp| Match::new(PatternID::ZERO, sp))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::aho_corasick::AhoCorasick> as regex_automata::meta::strategy::Strategy>::search::{closure#1}
Line
Count
Source
404
210
            .map(|sp| Match::new(PatternID::ZERO, sp))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::teddy::Teddy> as regex_automata::meta::strategy::Strategy>::search::{closure#1}
Line
Count
Source
404
2.39k
            .map(|sp| Match::new(PatternID::ZERO, sp))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr> as regex_automata::meta::strategy::Strategy>::search::{closure#1}
Line
Count
Source
404
392
            .map(|sp| Match::new(PatternID::ZERO, sp))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr2> as regex_automata::meta::strategy::Strategy>::search::{closure#1}
Line
Count
Source
404
137
            .map(|sp| Match::new(PatternID::ZERO, sp))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr3> as regex_automata::meta::strategy::Strategy>::search::{closure#1}
Line
Count
Source
404
122
            .map(|sp| Match::new(PatternID::ZERO, sp))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memmem::Memmem> as regex_automata::meta::strategy::Strategy>::search::{closure#1}
Line
Count
Source
404
294
            .map(|sp| Match::new(PatternID::ZERO, sp))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::byteset::ByteSet> as regex_automata::meta::strategy::Strategy>::search::{closure#1}
Line
Count
Source
404
36
            .map(|sp| Match::new(PatternID::ZERO, sp))
405
19.5k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::aho_corasick::AhoCorasick> as regex_automata::meta::strategy::Strategy>::search
Line
Count
Source
392
602
    fn search(&self, _cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
393
602
        if input.is_done() {
394
0
            return None;
395
602
        }
396
602
        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));
401
602
        }
402
602
        self.pre
403
602
            .find(input.haystack(), input.get_span())
404
602
            .map(|sp| Match::new(PatternID::ZERO, sp))
405
602
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::teddy::Teddy> as regex_automata::meta::strategy::Strategy>::search
Line
Count
Source
392
15.2k
    fn search(&self, _cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
393
15.2k
        if input.is_done() {
394
0
            return None;
395
15.2k
        }
396
15.2k
        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));
401
15.2k
        }
402
15.2k
        self.pre
403
15.2k
            .find(input.haystack(), input.get_span())
404
15.2k
            .map(|sp| Match::new(PatternID::ZERO, sp))
405
15.2k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr> as regex_automata::meta::strategy::Strategy>::search
Line
Count
Source
392
576
    fn search(&self, _cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
393
576
        if input.is_done() {
394
0
            return None;
395
576
        }
396
576
        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));
401
576
        }
402
576
        self.pre
403
576
            .find(input.haystack(), input.get_span())
404
576
            .map(|sp| Match::new(PatternID::ZERO, sp))
405
576
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr2> as regex_automata::meta::strategy::Strategy>::search
Line
Count
Source
392
196
    fn search(&self, _cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
393
196
        if input.is_done() {
394
0
            return None;
395
196
        }
396
196
        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));
401
196
        }
402
196
        self.pre
403
196
            .find(input.haystack(), input.get_span())
404
196
            .map(|sp| Match::new(PatternID::ZERO, sp))
405
196
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr3> as regex_automata::meta::strategy::Strategy>::search
Line
Count
Source
392
173
    fn search(&self, _cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
393
173
        if input.is_done() {
394
0
            return None;
395
173
        }
396
173
        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));
401
173
        }
402
173
        self.pre
403
173
            .find(input.haystack(), input.get_span())
404
173
            .map(|sp| Match::new(PatternID::ZERO, sp))
405
173
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memmem::Memmem> as regex_automata::meta::strategy::Strategy>::search
Line
Count
Source
392
2.62k
    fn search(&self, _cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
393
2.62k
        if input.is_done() {
394
0
            return None;
395
2.62k
        }
396
2.62k
        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));
401
2.62k
        }
402
2.62k
        self.pre
403
2.62k
            .find(input.haystack(), input.get_span())
404
2.62k
            .map(|sp| Match::new(PatternID::ZERO, sp))
405
2.62k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::byteset::ByteSet> as regex_automata::meta::strategy::Strategy>::search
Line
Count
Source
392
89
    fn search(&self, _cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
393
89
        if input.is_done() {
394
0
            return None;
395
89
        }
396
89
        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));
401
89
        }
402
89
        self.pre
403
89
            .find(input.haystack(), input.get_span())
404
89
            .map(|sp| Match::new(PatternID::ZERO, sp))
405
89
    }
406
407
    #[cfg_attr(feature = "perf-inline", inline(always))]
408
7.66k
    fn search_half(
409
7.66k
        &self,
410
7.66k
        cache: &mut Cache,
411
7.66k
        input: &Input<'_>,
412
7.66k
    ) -> Option<HalfMatch> {
413
7.66k
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::aho_corasick::AhoCorasick> as regex_automata::meta::strategy::Strategy>::search_half::{closure#0}
Line
Count
Source
413
165
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::teddy::Teddy> as regex_automata::meta::strategy::Strategy>::search_half::{closure#0}
Line
Count
Source
413
973
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr> as regex_automata::meta::strategy::Strategy>::search_half::{closure#0}
Line
Count
Source
413
118
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr2> as regex_automata::meta::strategy::Strategy>::search_half::{closure#0}
Line
Count
Source
413
41
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr3> as regex_automata::meta::strategy::Strategy>::search_half::{closure#0}
Line
Count
Source
413
38
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memmem::Memmem> as regex_automata::meta::strategy::Strategy>::search_half::{closure#0}
Line
Count
Source
413
94
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::byteset::ByteSet> as regex_automata::meta::strategy::Strategy>::search_half::{closure#0}
Line
Count
Source
413
14
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
414
7.66k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::aho_corasick::AhoCorasick> as regex_automata::meta::strategy::Strategy>::search_half
Line
Count
Source
408
325
    fn search_half(
409
325
        &self,
410
325
        cache: &mut Cache,
411
325
        input: &Input<'_>,
412
325
    ) -> Option<HalfMatch> {
413
325
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
414
325
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::teddy::Teddy> as regex_automata::meta::strategy::Strategy>::search_half
Line
Count
Source
408
6.14k
    fn search_half(
409
6.14k
        &self,
410
6.14k
        cache: &mut Cache,
411
6.14k
        input: &Input<'_>,
412
6.14k
    ) -> Option<HalfMatch> {
413
6.14k
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
414
6.14k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr> as regex_automata::meta::strategy::Strategy>::search_half
Line
Count
Source
408
191
    fn search_half(
409
191
        &self,
410
191
        cache: &mut Cache,
411
191
        input: &Input<'_>,
412
191
    ) -> Option<HalfMatch> {
413
191
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
414
191
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr2> as regex_automata::meta::strategy::Strategy>::search_half
Line
Count
Source
408
69
    fn search_half(
409
69
        &self,
410
69
        cache: &mut Cache,
411
69
        input: &Input<'_>,
412
69
    ) -> Option<HalfMatch> {
413
69
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
414
69
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr3> as regex_automata::meta::strategy::Strategy>::search_half
Line
Count
Source
408
52
    fn search_half(
409
52
        &self,
410
52
        cache: &mut Cache,
411
52
        input: &Input<'_>,
412
52
    ) -> Option<HalfMatch> {
413
52
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
414
52
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memmem::Memmem> as regex_automata::meta::strategy::Strategy>::search_half
Line
Count
Source
408
846
    fn search_half(
409
846
        &self,
410
846
        cache: &mut Cache,
411
846
        input: &Input<'_>,
412
846
    ) -> Option<HalfMatch> {
413
846
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
414
846
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::byteset::ByteSet> as regex_automata::meta::strategy::Strategy>::search_half
Line
Count
Source
408
30
    fn search_half(
409
30
        &self,
410
30
        cache: &mut Cache,
411
30
        input: &Input<'_>,
412
30
    ) -> Option<HalfMatch> {
413
30
        self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
414
30
    }
415
416
    #[cfg_attr(feature = "perf-inline", inline(always))]
417
1.44k
    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
418
1.44k
        self.search(cache, input).is_some()
419
1.44k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::aho_corasick::AhoCorasick> as regex_automata::meta::strategy::Strategy>::is_match
Line
Count
Source
417
37
    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
418
37
        self.search(cache, input).is_some()
419
37
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::teddy::Teddy> as regex_automata::meta::strategy::Strategy>::is_match
Line
Count
Source
417
1.04k
    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
418
1.04k
        self.search(cache, input).is_some()
419
1.04k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr> as regex_automata::meta::strategy::Strategy>::is_match
Line
Count
Source
417
33
    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
418
33
        self.search(cache, input).is_some()
419
33
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr2> as regex_automata::meta::strategy::Strategy>::is_match
Line
Count
Source
417
19
    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
418
19
        self.search(cache, input).is_some()
419
19
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr3> as regex_automata::meta::strategy::Strategy>::is_match
Line
Count
Source
417
17
    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
418
17
        self.search(cache, input).is_some()
419
17
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memmem::Memmem> as regex_automata::meta::strategy::Strategy>::is_match
Line
Count
Source
417
292
    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
418
292
        self.search(cache, input).is_some()
419
292
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::byteset::ByteSet> as regex_automata::meta::strategy::Strategy>::is_match
Line
Count
Source
417
9
    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
418
9
        self.search(cache, input).is_some()
419
9
    }
420
421
    #[cfg_attr(feature = "perf-inline", inline(always))]
422
5.22k
    fn search_slots(
423
5.22k
        &self,
424
5.22k
        cache: &mut Cache,
425
5.22k
        input: &Input<'_>,
426
5.22k
        slots: &mut [Option<NonMaxUsize>],
427
5.22k
    ) -> Option<PatternID> {
428
5.22k
        let m = self.search(cache, input)?;
429
975
        if let Some(slot) = slots.get_mut(0) {
430
975
            *slot = NonMaxUsize::new(m.start());
431
975
        }
432
975
        if let Some(slot) = slots.get_mut(1) {
433
975
            *slot = NonMaxUsize::new(m.end());
434
975
        }
435
975
        Some(m.pattern())
436
5.22k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::aho_corasick::AhoCorasick> as regex_automata::meta::strategy::Strategy>::search_slots
Line
Count
Source
422
120
    fn search_slots(
423
120
        &self,
424
120
        cache: &mut Cache,
425
120
        input: &Input<'_>,
426
120
        slots: &mut [Option<NonMaxUsize>],
427
120
    ) -> Option<PatternID> {
428
120
        let m = self.search(cache, input)?;
429
20
        if let Some(slot) = slots.get_mut(0) {
430
20
            *slot = NonMaxUsize::new(m.start());
431
20
        }
432
20
        if let Some(slot) = slots.get_mut(1) {
433
20
            *slot = NonMaxUsize::new(m.end());
434
20
        }
435
20
        Some(m.pattern())
436
120
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::teddy::Teddy> as regex_automata::meta::strategy::Strategy>::search_slots
Line
Count
Source
422
4.05k
    fn search_slots(
423
4.05k
        &self,
424
4.05k
        cache: &mut Cache,
425
4.05k
        input: &Input<'_>,
426
4.05k
        slots: &mut [Option<NonMaxUsize>],
427
4.05k
    ) -> Option<PatternID> {
428
4.05k
        let m = self.search(cache, input)?;
429
656
        if let Some(slot) = slots.get_mut(0) {
430
656
            *slot = NonMaxUsize::new(m.start());
431
656
        }
432
656
        if let Some(slot) = slots.get_mut(1) {
433
656
            *slot = NonMaxUsize::new(m.end());
434
656
        }
435
656
        Some(m.pattern())
436
4.05k
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr> as regex_automata::meta::strategy::Strategy>::search_slots
Line
Count
Source
422
176
    fn search_slots(
423
176
        &self,
424
176
        cache: &mut Cache,
425
176
        input: &Input<'_>,
426
176
        slots: &mut [Option<NonMaxUsize>],
427
176
    ) -> Option<PatternID> {
428
176
        let m = self.search(cache, input)?;
429
125
        if let Some(slot) = slots.get_mut(0) {
430
125
            *slot = NonMaxUsize::new(m.start());
431
125
        }
432
125
        if let Some(slot) = slots.get_mut(1) {
433
125
            *slot = NonMaxUsize::new(m.end());
434
125
        }
435
125
        Some(m.pattern())
436
176
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr2> as regex_automata::meta::strategy::Strategy>::search_slots
Line
Count
Source
422
54
    fn search_slots(
423
54
        &self,
424
54
        cache: &mut Cache,
425
54
        input: &Input<'_>,
426
54
        slots: &mut [Option<NonMaxUsize>],
427
54
    ) -> Option<PatternID> {
428
54
        let m = self.search(cache, input)?;
429
42
        if let Some(slot) = slots.get_mut(0) {
430
42
            *slot = NonMaxUsize::new(m.start());
431
42
        }
432
42
        if let Some(slot) = slots.get_mut(1) {
433
42
            *slot = NonMaxUsize::new(m.end());
434
42
        }
435
42
        Some(m.pattern())
436
54
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memchr::Memchr3> as regex_automata::meta::strategy::Strategy>::search_slots
Line
Count
Source
422
52
    fn search_slots(
423
52
        &self,
424
52
        cache: &mut Cache,
425
52
        input: &Input<'_>,
426
52
        slots: &mut [Option<NonMaxUsize>],
427
52
    ) -> Option<PatternID> {
428
52
        let m = self.search(cache, input)?;
429
38
        if let Some(slot) = slots.get_mut(0) {
430
38
            *slot = NonMaxUsize::new(m.start());
431
38
        }
432
38
        if let Some(slot) = slots.get_mut(1) {
433
38
            *slot = NonMaxUsize::new(m.end());
434
38
        }
435
38
        Some(m.pattern())
436
52
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::memmem::Memmem> as regex_automata::meta::strategy::Strategy>::search_slots
Line
Count
Source
422
741
    fn search_slots(
423
741
        &self,
424
741
        cache: &mut Cache,
425
741
        input: &Input<'_>,
426
741
        slots: &mut [Option<NonMaxUsize>],
427
741
    ) -> Option<PatternID> {
428
741
        let m = self.search(cache, input)?;
429
84
        if let Some(slot) = slots.get_mut(0) {
430
84
            *slot = NonMaxUsize::new(m.start());
431
84
        }
432
84
        if let Some(slot) = slots.get_mut(1) {
433
84
            *slot = NonMaxUsize::new(m.end());
434
84
        }
435
84
        Some(m.pattern())
436
741
    }
<regex_automata::meta::strategy::Pre<regex_automata::util::prefilter::byteset::ByteSet> as regex_automata::meta::strategy::Strategy>::search_slots
Line
Count
Source
422
25
    fn search_slots(
423
25
        &self,
424
25
        cache: &mut Cache,
425
25
        input: &Input<'_>,
426
25
        slots: &mut [Option<NonMaxUsize>],
427
25
    ) -> Option<PatternID> {
428
25
        let m = self.search(cache, input)?;
429
10
        if let Some(slot) = slots.get_mut(0) {
430
10
            *slot = NonMaxUsize::new(m.start());
431
10
        }
432
10
        if let Some(slot) = slots.get_mut(1) {
433
10
            *slot = NonMaxUsize::new(m.end());
434
10
        }
435
10
        Some(m.pattern())
436
25
    }
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
72.2k
    fn new(
466
72.2k
        info: RegexInfo,
467
72.2k
        pre: Option<Prefilter>,
468
72.2k
        hirs: &[&Hir],
469
72.2k
    ) -> Result<Core, BuildError> {
470
72.2k
        let mut lookm = LookMatcher::new();
471
72.2k
        lookm.set_line_terminator(info.config().get_line_terminator());
472
72.2k
        let thompson_config = info.config().to_thompson_config();
473
72.2k
        let nfa = thompson::Compiler::new()
474
72.2k
            .configure(thompson_config.clone())
475
72.2k
            .build_many_from_hir(hirs)
476
72.2k
            .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
70.2k
        let pikevm = wrappers::PikeVM::new(&info, pre.clone(), &nfa)?;
484
70.2k
        let backtrack =
485
70.2k
            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
70.2k
        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
70.0k
        let (nfarev, hybrid, dfa) =
498
70.2k
            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
70.2k
                let nfarev = thompson::Compiler::new()
512
70.2k
                    // Currently, reverse NFAs don't support capturing groups,
513
70.2k
                    // so we MUST disable them. But even if we didn't have to,
514
70.2k
                    // we would, because nothing in this crate does anything
515
70.2k
                    // useful with capturing groups in reverse. And of course,
516
70.2k
                    // the lazy DFA ignores capturing groups in all cases.
517
70.2k
                    .configure(
518
70.2k
                        thompson_config
519
70.2k
                            .which_captures(WhichCaptures::None)
520
70.2k
                            .reverse(true),
521
70.2k
                    )
522
70.2k
                    .build_many_from_hir(hirs)
523
70.2k
                    .map_err(BuildError::nfa)?;
524
70.0k
                let dfa = if !info.config().get_dfa() {
525
0
                    wrappers::DFA::none()
526
                } else {
527
70.0k
                    wrappers::DFA::new(&info, pre.clone(), &nfa, &nfarev)
528
                };
529
70.0k
                let hybrid = if !info.config().get_hybrid() {
530
0
                    wrappers::Hybrid::none()
531
70.0k
                } else if dfa.is_some() {
532
40.8k
                    debug!("skipping lazy DFA because we have a full DFA");
533
40.8k
                    wrappers::Hybrid::none()
534
                } else {
535
29.2k
                    wrappers::Hybrid::new(&info, pre.clone(), &nfa, &nfarev)
536
                };
537
70.0k
                (Some(nfarev), hybrid, dfa)
538
            };
539
70.0k
        Ok(Core {
540
70.0k
            info,
541
70.0k
            pre,
542
70.0k
            nfa,
543
70.0k
            nfarev,
544
70.0k
            pikevm,
545
70.0k
            backtrack,
546
70.0k
            onepass,
547
70.0k
            hybrid,
548
70.0k
            dfa,
549
70.0k
        })
550
72.2k
    }
551
552
    #[cfg_attr(feature = "perf-inline", inline(always))]
553
8.35k
    fn try_search_mayfail(
554
8.35k
        &self,
555
8.35k
        cache: &mut Cache,
556
8.35k
        input: &Input<'_>,
557
8.35k
    ) -> Option<Result<Option<Match>, RetryFailError>> {
558
8.35k
        if let Some(e) = self.dfa.get(input) {
559
3.40k
            trace!("using full DFA for search at {:?}", input.get_span());
560
3.40k
            Some(e.try_search(input))
561
4.95k
        } else if let Some(e) = self.hybrid.get(input) {
562
4.95k
            trace!("using lazy DFA for search at {:?}", input.get_span());
563
4.95k
            Some(e.try_search(&mut cache.hybrid, input))
564
        } else {
565
0
            None
566
        }
567
8.35k
    }
568
569
29.8k
    fn search_nofail(
570
29.8k
        &self,
571
29.8k
        cache: &mut Cache,
572
29.8k
        input: &Input<'_>,
573
29.8k
    ) -> Option<Match> {
574
29.8k
        let caps = &mut cache.capmatches;
575
29.8k
        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
29.8k
        let pid = if let Some(ref e) = self.onepass.get(input) {
583
800
            trace!("using OnePass for search at {:?}", input.get_span());
584
800
            e.search_slots(&mut cache.onepass, input, caps.slots_mut())
585
29.0k
        } else if let Some(ref e) = self.backtrack.get(input) {
586
23.0k
            trace!(
587
0
                "using BoundedBacktracker for search at {:?}",
588
0
                input.get_span()
589
            );
590
23.0k
            e.search_slots(&mut cache.backtrack, input, caps.slots_mut())
591
        } else {
592
5.93k
            trace!("using PikeVM for search at {:?}", input.get_span());
593
5.93k
            let e = self.pikevm.get();
594
5.93k
            e.search_slots(&mut cache.pikevm, input, caps.slots_mut())
595
        };
596
29.8k
        caps.set_pattern(pid);
597
29.8k
        caps.get_match()
598
29.8k
    }
599
600
9.80k
    fn search_half_nofail(
601
9.80k
        &self,
602
9.80k
        cache: &mut Cache,
603
9.80k
        input: &Input<'_>,
604
9.80k
    ) -> 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
9.80k
        let m = self.search_nofail(cache, input)?;
610
2.09k
        Some(HalfMatch::new(m.pattern(), m.end()))
611
9.80k
    }
612
613
6.96k
    fn search_slots_nofail(
614
6.96k
        &self,
615
6.96k
        cache: &mut Cache,
616
6.96k
        input: &Input<'_>,
617
6.96k
        slots: &mut [Option<NonMaxUsize>],
618
6.96k
    ) -> Option<PatternID> {
619
6.96k
        if let Some(ref e) = self.onepass.get(input) {
620
1.46k
            trace!(
621
0
                "using OnePass for capture search at {:?}",
622
0
                input.get_span()
623
            );
624
1.46k
            e.search_slots(&mut cache.onepass, input, slots)
625
5.50k
        } else if let Some(ref e) = self.backtrack.get(input) {
626
4.96k
            trace!(
627
0
                "using BoundedBacktracker for capture search at {:?}",
628
0
                input.get_span()
629
            );
630
4.96k
            e.search_slots(&mut cache.backtrack, input, slots)
631
        } else {
632
538
            trace!(
633
0
                "using PikeVM for capture search at {:?}",
634
0
                input.get_span()
635
            );
636
538
            let e = self.pikevm.get();
637
538
            e.search_slots(&mut cache.pikevm, input, slots)
638
        }
639
6.96k
    }
640
641
5.27k
    fn is_match_nofail(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
642
5.27k
        if let Some(ref e) = self.onepass.get(input) {
643
128
            trace!(
644
0
                "using OnePass for is-match search at {:?}",
645
0
                input.get_span()
646
            );
647
128
            e.search_slots(&mut cache.onepass, input, &mut []).is_some()
648
5.14k
        } else if let Some(ref e) = self.backtrack.get(input) {
649
2.69k
            trace!(
650
0
                "using BoundedBacktracker for is-match search at {:?}",
651
0
                input.get_span()
652
            );
653
2.69k
            e.is_match(&mut cache.backtrack, input)
654
        } else {
655
2.45k
            trace!(
656
0
                "using PikeVM for is-match search at {:?}",
657
0
                input.get_span()
658
            );
659
2.45k
            let e = self.pikevm.get();
660
2.45k
            e.is_match(&mut cache.pikevm, input)
661
        }
662
5.27k
    }
663
664
32.9k
    fn is_capture_search_needed(&self, slots_len: usize) -> bool {
665
32.9k
        slots_len > self.nfa.group_info().implicit_slot_len()
666
32.9k
    }
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
87.5k
    fn group_info(&self) -> &GroupInfo {
676
87.5k
        self.nfa.group_info()
677
87.5k
    }
678
679
    #[cfg_attr(feature = "perf-inline", inline(always))]
680
42.9k
    fn create_cache(&self) -> Cache {
681
42.9k
        Cache {
682
42.9k
            capmatches: Captures::all(self.group_info().clone()),
683
42.9k
            pikevm: self.pikevm.create_cache(),
684
42.9k
            backtrack: self.backtrack.create_cache(),
685
42.9k
            onepass: self.onepass.create_cache(),
686
42.9k
            hybrid: self.hybrid.create_cache(),
687
42.9k
            revhybrid: wrappers::ReverseHybridCache::none(),
688
42.9k
        }
689
42.9k
    }
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
53.2k
    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
53.2k
        return if let Some(e) = self.dfa.get(input) {
717
34.8k
            trace!("using full DFA for full search at {:?}", input.get_span());
718
34.8k
            match e.try_search(input) {
719
19.9k
                Ok(x) => x,
720
14.9k
                Err(_err) => {
721
14.9k
                    trace!("full DFA search failed: {_err}");
722
14.9k
                    self.search_nofail(cache, input)
723
                }
724
            }
725
18.3k
        } else if let Some(e) = self.hybrid.get(input) {
726
18.3k
            trace!("using lazy DFA for full search at {:?}", input.get_span());
727
18.3k
            match e.try_search(&mut cache.hybrid, input) {
728
13.9k
                Ok(x) => x,
729
4.47k
                Err(_err) => {
730
4.47k
                    trace!("lazy DFA search failed: {_err}");
731
4.47k
                    self.search_nofail(cache, input)
732
                }
733
            }
734
        } else {
735
0
            self.search_nofail(cache, input)
736
        };
737
53.2k
    }
738
739
    #[cfg_attr(feature = "perf-inline", inline(always))]
740
25.7k
    fn search_half(
741
25.7k
        &self,
742
25.7k
        cache: &mut Cache,
743
25.7k
        input: &Input<'_>,
744
25.7k
    ) -> 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
25.7k
        if let Some(e) = self.dfa.get(input) {
749
15.8k
            trace!("using full DFA for half search at {:?}", input.get_span());
750
15.8k
            match e.try_search_half_fwd(input) {
751
9.52k
                Ok(x) => x,
752
6.31k
                Err(_err) => {
753
6.31k
                    trace!("full DFA half search failed: {_err}");
754
6.31k
                    self.search_half_nofail(cache, input)
755
                }
756
            }
757
9.87k
        } else if let Some(e) = self.hybrid.get(input) {
758
9.87k
            trace!("using lazy DFA for half search at {:?}", input.get_span());
759
9.87k
            match e.try_search_half_fwd(&mut cache.hybrid, input) {
760
6.56k
                Ok(x) => x,
761
3.30k
                Err(_err) => {
762
3.30k
                    trace!("lazy DFA half search failed: {_err}");
763
3.30k
                    self.search_half_nofail(cache, input)
764
                }
765
            }
766
        } else {
767
0
            self.search_half_nofail(cache, input)
768
        }
769
25.7k
    }
770
771
    #[cfg_attr(feature = "perf-inline", inline(always))]
772
13.6k
    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
773
13.6k
        if let Some(e) = self.dfa.get(input) {
774
8.63k
            trace!(
775
0
                "using full DFA for is-match search at {:?}",
776
0
                input.get_span()
777
            );
778
8.63k
            match e.try_search_half_fwd(input) {
779
5.45k
                Ok(x) => x.is_some(),
780
3.17k
                Err(_err) => {
781
3.17k
                    trace!("full DFA half search failed: {_err}");
782
3.17k
                    self.is_match_nofail(cache, input)
783
                }
784
            }
785
5.05k
        } else if let Some(e) = self.hybrid.get(input) {
786
5.05k
            trace!(
787
0
                "using lazy DFA for is-match search at {:?}",
788
0
                input.get_span()
789
            );
790
5.05k
            match e.try_search_half_fwd(&mut cache.hybrid, input) {
791
3.96k
                Ok(x) => x.is_some(),
792
1.08k
                Err(_err) => {
793
1.08k
                    trace!("lazy DFA half search failed: {_err}");
794
1.08k
                    self.is_match_nofail(cache, input)
795
                }
796
            }
797
        } else {
798
0
            self.is_match_nofail(cache, input)
799
        }
800
13.6k
    }
801
802
    #[cfg_attr(feature = "perf-inline", inline(always))]
803
30.4k
    fn search_slots(
804
30.4k
        &self,
805
30.4k
        cache: &mut Cache,
806
30.4k
        input: &Input<'_>,
807
30.4k
        slots: &mut [Option<NonMaxUsize>],
808
30.4k
    ) -> 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
30.4k
        if !self.is_capture_search_needed(slots.len()) {
815
21.8k
            trace!("asked for slots unnecessarily, trying fast path");
816
21.8k
            let m = self.search(cache, input)?;
817
7.18k
            copy_match_to_slots(m, slots);
818
7.18k
            return Some(m.pattern());
819
8.61k
        }
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
8.61k
        if self.onepass.get(&input).is_some() {
834
256
            return self.search_slots_nofail(cache, &input, slots);
835
8.35k
        }
836
8.35k
        let m = match self.try_search_mayfail(cache, input) {
837
3.56k
            Some(Ok(Some(m))) => m,
838
2.10k
            Some(Ok(None)) => return None,
839
2.68k
            Some(Err(_err)) => {
840
2.68k
                trace!("fast capture search failed: {_err}");
841
2.68k
                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
3.56k
        trace!(
852
0
            "match found at {}..{} in capture search, \
853
0
             using another engine to find captures",
854
0
            m.start(),
855
0
            m.end(),
856
        );
857
3.56k
        let input = input
858
3.56k
            .clone()
859
3.56k
            .span(m.start()..m.end())
860
3.56k
            .anchored(Anchored::Pattern(m.pattern()));
861
3.56k
        Some(
862
3.56k
            self.search_slots_nofail(cache, &input, slots)
863
3.56k
                .expect("should find a match"),
864
3.56k
        )
865
30.4k
    }
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
0
            trace!(
876
0
                "using full DFA for overlapping search at {:?}",
877
0
                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
0
            trace!("fast overlapping search failed: {_err}");
884
0
        } else if let Some(e) = self.hybrid.get(input) {
885
0
            trace!(
886
0
                "using lazy DFA for overlapping search at {:?}",
887
0
                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
0
            trace!("fast overlapping search failed: {_err}");
900
0
        }
901
0
        trace!(
902
0
            "using PikeVM for overlapping search at {:?}",
903
0
            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
70.0k
    fn new(core: Core) -> Result<ReverseAnchored, Core> {
917
70.0k
        if !core.info.is_always_anchored_end() {
918
68.7k
            debug!(
919
0
                "skipping reverse anchored optimization because \
920
0
                 the regex is not always anchored at the end"
921
            );
922
68.7k
            return Err(core);
923
1.26k
        }
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
1.26k
        if core.info.is_always_anchored_start() {
933
83
            debug!(
934
0
                "skipping reverse anchored optimization because \
935
0
                 the regex is also anchored at the start"
936
            );
937
83
            return Err(core);
938
1.18k
        }
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
1.18k
        if !core.hybrid.is_some() && !core.dfa.is_some() {
943
0
            debug!(
944
0
                "skipping reverse anchored optimization because \
945
0
                 we don't have a lazy DFA or a full DFA"
946
            );
947
0
            return Err(core);
948
1.18k
        }
949
1.18k
        Ok(ReverseAnchored { core })
950
70.0k
    }
951
952
    #[cfg_attr(feature = "perf-inline", inline(always))]
953
2.73k
    fn try_search_half_anchored_rev(
954
2.73k
        &self,
955
2.73k
        cache: &mut Cache,
956
2.73k
        input: &Input<'_>,
957
2.73k
    ) -> 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
2.73k
        let input = input.clone().anchored(Anchored::Yes);
963
2.73k
        if let Some(e) = self.core.dfa.get(&input) {
964
2.02k
            trace!(
965
0
                "using full DFA for reverse anchored search at {:?}",
966
0
                input.get_span()
967
            );
968
2.02k
            e.try_search_half_rev(&input)
969
714
        } else if let Some(e) = self.core.hybrid.get(&input) {
970
714
            trace!(
971
0
                "using lazy DFA for reverse anchored search at {:?}",
972
0
                input.get_span()
973
            );
974
714
            e.try_search_half_rev(&mut cache.hybrid, &input)
975
        } else {
976
0
            unreachable!("ReverseAnchored always has a DFA")
977
        }
978
2.73k
    }
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
883
    fn group_info(&self) -> &GroupInfo {
995
883
        self.core.group_info()
996
883
    }
997
998
    #[cfg_attr(feature = "perf-inline", inline(always))]
999
1.04k
    fn create_cache(&self) -> Cache {
1000
1.04k
        self.core.create_cache()
1001
1.04k
    }
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
845
    fn search(&self, cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
1021
845
        if input.get_anchored().is_anchored() {
1022
0
            return self.core.search(cache, input);
1023
845
        }
1024
845
        match self.try_search_half_anchored_rev(cache, input) {
1025
233
            Err(_err) => {
1026
233
                trace!("fast reverse anchored search failed: {_err}");
1027
233
                self.core.search_nofail(cache, input)
1028
            }
1029
386
            Ok(None) => None,
1030
226
            Ok(Some(hm)) => {
1031
226
                Some(Match::new(hm.pattern(), hm.offset()..input.end()))
1032
            }
1033
        }
1034
845
    }
1035
1036
    #[cfg_attr(feature = "perf-inline", inline(always))]
1037
557
    fn search_half(
1038
557
        &self,
1039
557
        cache: &mut Cache,
1040
557
        input: &Input<'_>,
1041
557
    ) -> Option<HalfMatch> {
1042
557
        if input.get_anchored().is_anchored() {
1043
0
            return self.core.search_half(cache, input);
1044
557
        }
1045
557
        match self.try_search_half_anchored_rev(cache, input) {
1046
95
            Err(_err) => {
1047
95
                trace!("fast reverse anchored search failed: {_err}");
1048
95
                self.core.search_half_nofail(cache, input)
1049
            }
1050
203
            Ok(None) => None,
1051
259
            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
259
                Some(HalfMatch::new(hm.pattern(), input.end()))
1059
            }
1060
        }
1061
557
    }
1062
1063
    #[cfg_attr(feature = "perf-inline", inline(always))]
1064
492
    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
1065
492
        if input.get_anchored().is_anchored() {
1066
0
            return self.core.is_match(cache, input);
1067
492
        }
1068
492
        match self.try_search_half_anchored_rev(cache, input) {
1069
45
            Err(_err) => {
1070
45
                trace!("fast reverse anchored search failed: {_err}");
1071
45
                self.core.is_match_nofail(cache, input)
1072
            }
1073
289
            Ok(None) => false,
1074
158
            Ok(Some(_)) => true,
1075
        }
1076
492
    }
1077
1078
    #[cfg_attr(feature = "perf-inline", inline(always))]
1079
845
    fn search_slots(
1080
845
        &self,
1081
845
        cache: &mut Cache,
1082
845
        input: &Input<'_>,
1083
845
        slots: &mut [Option<NonMaxUsize>],
1084
845
    ) -> Option<PatternID> {
1085
845
        if input.get_anchored().is_anchored() {
1086
0
            return self.core.search_slots(cache, input, slots);
1087
845
        }
1088
845
        match self.try_search_half_anchored_rev(cache, input) {
1089
262
            Err(_err) => {
1090
262
                trace!("fast reverse anchored search failed: {_err}");
1091
262
                self.core.search_slots_nofail(cache, input, slots)
1092
            }
1093
358
            Ok(None) => None,
1094
225
            Ok(Some(hm)) => {
1095
225
                if !self.core.is_capture_search_needed(slots.len()) {
1096
182
                    trace!("asked for slots unnecessarily, skipping captures");
1097
182
                    let m = Match::new(hm.pattern(), hm.offset()..input.end());
1098
182
                    copy_match_to_slots(m, slots);
1099
182
                    return Some(m.pattern());
1100
43
                }
1101
43
                let start = hm.offset();
1102
43
                let input = input
1103
43
                    .clone()
1104
43
                    .span(start..input.end())
1105
43
                    .anchored(Anchored::Pattern(hm.pattern()));
1106
43
                self.core.search_slots_nofail(cache, &input, slots)
1107
            }
1108
        }
1109
845
    }
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
68.8k
    fn new(core: Core, hirs: &[&Hir]) -> Result<ReverseSuffix, Core> {
1134
68.8k
        if !core.info.config().get_auto_prefilter() {
1135
0
            debug!(
1136
0
                "skipping reverse suffix optimization because \
1137
0
                 automatic prefilters are disabled"
1138
            );
1139
0
            return Err(core);
1140
68.8k
        }
1141
        // Also like the reverse inner optimization, a reverse suffix encodes
1142
        // leftmost-first match semantics.
1143
68.8k
        if core.info.config().get_match_kind() != MatchKind::LeftmostFirst {
1144
0
            debug!(
1145
0
                "skipping reverse suffix optimization because \
1146
0
                 match kind is {:?} but this only supports leftmost-first",
1147
0
                core.info.config().get_match_kind(),
1148
            );
1149
0
            return Err(core);
1150
68.8k
        }
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
68.8k
        if core.info.is_always_anchored_start() {
1167
2.08k
            debug!(
1168
0
                "skipping reverse suffix optimization because \
1169
0
                 the regex is always anchored at the start",
1170
            );
1171
2.08k
            return Err(core);
1172
66.7k
        }
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
66.7k
        if !core.hybrid.is_some() && !core.dfa.is_some() {
1177
0
            debug!(
1178
0
                "skipping reverse suffix optimization because \
1179
0
                 we don't have a lazy DFA or a full DFA"
1180
            );
1181
0
            return Err(core);
1182
66.7k
        }
1183
66.7k
        if core.pre.as_ref().map_or(false, |p| p.is_fast()) {
1184
15.6k
            debug!(
1185
0
                "skipping reverse suffix optimization because \
1186
0
                 we already have a prefilter that we think is fast"
1187
            );
1188
15.6k
            return Err(core);
1189
51.1k
        }
1190
51.1k
        let kind = core.info.config().get_match_kind();
1191
51.1k
        let suffixes = crate::util::prefilter::suffixes(kind, hirs);
1192
51.1k
        let lcs = match suffixes.longest_common_suffix() {
1193
            None => {
1194
34.8k
                debug!(
1195
0
                    "skipping reverse suffix optimization because \
1196
0
                     a longest common suffix could not be found",
1197
                );
1198
34.8k
                return Err(core);
1199
            }
1200
16.3k
            Some(lcs) if lcs.is_empty() => {
1201
9.24k
                debug!(
1202
0
                    "skipping reverse suffix optimization because \
1203
0
                     the longest common suffix is the empty string",
1204
                );
1205
9.24k
                return Err(core);
1206
            }
1207
7.12k
            Some(lcs) => lcs,
1208
        };
1209
7.12k
        let pre = match Prefilter::new(kind, &[lcs]) {
1210
7.12k
            Some(pre) => pre,
1211
            None => {
1212
0
                debug!(
1213
0
                    "skipping reverse suffix optimization because \
1214
0
                     a prefilter could not be constructed from the \
1215
0
                     longest common suffix",
1216
                );
1217
0
                return Err(core);
1218
            }
1219
        };
1220
7.12k
        if !pre.is_fast() {
1221
0
            debug!(
1222
0
                "skipping reverse suffix optimization because \
1223
0
                 while we have a suffix prefilter, it is not \
1224
0
                 believed to be 'fast'"
1225
            );
1226
0
            return Err(core);
1227
7.12k
        }
1228
7.12k
        if !reverse_suffix::has_no_earlier_match(hirs, &lcs) {
1229
4.53k
            debug!(
1230
0
                "skipping reverse suffix optimization because \
1231
0
                 an earlier suffix match could be a complete match \
1232
0
                 inside of a larger match"
1233
            );
1234
4.53k
            return Err(core);
1235
2.59k
        }
1236
2.59k
        Ok(ReverseSuffix { core, pre })
1237
68.8k
    }
1238
1239
    #[cfg_attr(feature = "perf-inline", inline(always))]
1240
2.26k
    fn try_search_half_start(
1241
2.26k
        &self,
1242
2.26k
        cache: &mut Cache,
1243
2.26k
        input: &Input<'_>,
1244
2.26k
    ) -> Result<Option<HalfMatch>, RetryError> {
1245
2.26k
        let mut span = input.get_span();
1246
2.26k
        let mut min_start = 0;
1247
        loop {
1248
10.2k
            let litmatch = match self.pre.find(input.haystack(), span) {
1249
697
                None => break,
1250
9.57k
                Some(span) => span,
1251
            };
1252
9.57k
            trace!("reverse suffix scan found suffix match at {litmatch:?}");
1253
9.57k
            let revinput = input
1254
9.57k
                .clone()
1255
9.57k
                .anchored(Anchored::Yes)
1256
9.57k
                .span(input.start()..litmatch.end);
1257
277
            if let Some(hm) =
1258
9.57k
                self.try_search_half_rev_limited(cache, &revinput, min_start)?
1259
            {
1260
277
                return Ok(Some(hm));
1261
8.01k
            }
1262
1263
8.01k
            if span.start >= span.end {
1264
0
                break;
1265
8.01k
            }
1266
8.01k
            span.start = litmatch.start.checked_add(1).unwrap();
1267
8.01k
            min_start = litmatch.end;
1268
        }
1269
697
        Ok(None)
1270
2.26k
    }
1271
1272
    #[cfg_attr(feature = "perf-inline", inline(always))]
1273
244
    fn try_search_half_fwd(
1274
244
        &self,
1275
244
        cache: &mut Cache,
1276
244
        input: &Input<'_>,
1277
244
    ) -> Result<Option<HalfMatch>, RetryFailError> {
1278
244
        if let Some(e) = self.core.dfa.get(&input) {
1279
84
            trace!(
1280
0
                "using full DFA for forward reverse suffix search at {:?}",
1281
0
                input.get_span()
1282
            );
1283
84
            e.try_search_half_fwd(&input)
1284
160
        } else if let Some(e) = self.core.hybrid.get(&input) {
1285
160
            trace!(
1286
0
                "using lazy DFA for forward reverse suffix search at {:?}",
1287
0
                input.get_span()
1288
            );
1289
160
            e.try_search_half_fwd(&mut cache.hybrid, &input)
1290
        } else {
1291
0
            unreachable!("ReverseSuffix always has a DFA")
1292
        }
1293
244
    }
1294
1295
    #[cfg_attr(feature = "perf-inline", inline(always))]
1296
9.57k
    fn try_search_half_rev_limited(
1297
9.57k
        &self,
1298
9.57k
        cache: &mut Cache,
1299
9.57k
        input: &Input<'_>,
1300
9.57k
        min_start: usize,
1301
9.57k
    ) -> Result<Option<HalfMatch>, RetryError> {
1302
9.57k
        if let Some(e) = self.core.dfa.get(&input) {
1303
4.41k
            trace!(
1304
0
                "using full DFA for reverse suffix search at {:?}, \
1305
0
                 but will be stopped at {} to avoid quadratic behavior",
1306
0
                input.get_span(),
1307
                min_start,
1308
            );
1309
4.41k
            e.try_search_half_rev_limited(&input, min_start)
1310
5.15k
        } else if let Some(e) = self.core.hybrid.get(&input) {
1311
5.15k
            trace!(
1312
0
                "using lazy DFA for reverse suffix search at {:?}, \
1313
0
                 but will be stopped at {} to avoid quadratic behavior",
1314
0
                input.get_span(),
1315
                min_start,
1316
            );
1317
5.15k
            e.try_search_half_rev_limited(&mut cache.hybrid, &input, min_start)
1318
        } else {
1319
0
            unreachable!("ReverseSuffix always has a DFA")
1320
        }
1321
9.57k
    }
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
1.16k
    fn group_info(&self) -> &GroupInfo {
1331
1.16k
        self.core.group_info()
1332
1.16k
    }
1333
1334
    #[cfg_attr(feature = "perf-inline", inline(always))]
1335
1.02k
    fn create_cache(&self) -> Cache {
1336
1.02k
        self.core.create_cache()
1337
1.02k
    }
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
1.11k
    fn search(&self, cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
1354
1.11k
        if input.get_anchored().is_anchored() {
1355
0
            return self.core.search(cache, input);
1356
1.11k
        }
1357
1.11k
        match self.try_search_half_start(cache, input) {
1358
582
            Err(RetryError::Quadratic(_err)) => {
1359
582
                trace!("reverse suffix optimization failed: {_err}");
1360
582
                self.core.search(cache, input)
1361
            }
1362
78
            Err(RetryError::Fail(_err)) => {
1363
78
                trace!("reverse suffix reverse fast search failed: {_err}");
1364
78
                self.core.search_nofail(cache, input)
1365
            }
1366
343
            Ok(None) => None,
1367
116
            Ok(Some(hm_start)) => {
1368
116
                let fwdinput = input
1369
116
                    .clone()
1370
116
                    .anchored(Anchored::Pattern(hm_start.pattern()))
1371
116
                    .span(hm_start.offset()..input.end());
1372
116
                match self.try_search_half_fwd(cache, &fwdinput) {
1373
10
                    Err(_err) => {
1374
10
                        trace!(
1375
0
                            "reverse suffix forward fast search failed: {_err}"
1376
                        );
1377
10
                        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
106
                    Ok(Some(hm_end)) => Some(Match::new(
1386
106
                        hm_start.pattern(),
1387
106
                        hm_start.offset()..hm_end.offset(),
1388
106
                    )),
1389
                }
1390
            }
1391
        }
1392
1.11k
    }
1393
1394
    #[cfg_attr(feature = "perf-inline", inline(always))]
1395
924
    fn search_half(
1396
924
        &self,
1397
924
        cache: &mut Cache,
1398
924
        input: &Input<'_>,
1399
924
    ) -> Option<HalfMatch> {
1400
924
        if input.get_anchored().is_anchored() {
1401
0
            return self.core.search_half(cache, input);
1402
924
        }
1403
924
        match self.try_search_half_start(cache, input) {
1404
474
            Err(RetryError::Quadratic(_err)) => {
1405
474
                trace!("reverse suffix half optimization failed: {_err}");
1406
474
                self.core.search_half(cache, input)
1407
            }
1408
48
            Err(RetryError::Fail(_err)) => {
1409
48
                trace!(
1410
0
                    "reverse suffix reverse fast half search failed: {_err}"
1411
                );
1412
48
                self.core.search_half_nofail(cache, input)
1413
            }
1414
274
            Ok(None) => None,
1415
128
            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
128
                let fwdinput = input
1426
128
                    .clone()
1427
128
                    .anchored(Anchored::Pattern(hm_start.pattern()))
1428
128
                    .span(hm_start.offset()..input.end());
1429
128
                match self.try_search_half_fwd(cache, &fwdinput) {
1430
0
                    Err(_err) => {
1431
0
                        trace!(
1432
0
                            "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
128
                    Ok(Some(hm_end)) => Some(hm_end),
1443
                }
1444
            }
1445
        }
1446
924
    }
1447
1448
    #[cfg_attr(feature = "perf-inline", inline(always))]
1449
104
    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
1450
104
        if input.get_anchored().is_anchored() {
1451
0
            return self.core.is_match(cache, input);
1452
104
        }
1453
104
        match self.try_search_half_start(cache, input) {
1454
45
            Err(RetryError::Quadratic(_err)) => {
1455
45
                trace!("reverse suffix half optimization failed: {_err}");
1456
45
                self.core.is_match_nofail(cache, input)
1457
            }
1458
7
            Err(RetryError::Fail(_err)) => {
1459
7
                trace!(
1460
0
                    "reverse suffix reverse fast half search failed: {_err}"
1461
                );
1462
7
                self.core.is_match_nofail(cache, input)
1463
            }
1464
35
            Ok(None) => false,
1465
17
            Ok(Some(_)) => true,
1466
        }
1467
104
    }
1468
1469
    #[cfg_attr(feature = "perf-inline", inline(always))]
1470
616
    fn search_slots(
1471
616
        &self,
1472
616
        cache: &mut Cache,
1473
616
        input: &Input<'_>,
1474
616
        slots: &mut [Option<NonMaxUsize>],
1475
616
    ) -> Option<PatternID> {
1476
616
        if input.get_anchored().is_anchored() {
1477
0
            return self.core.search_slots(cache, input, slots);
1478
616
        }
1479
616
        if !self.core.is_capture_search_needed(slots.len()) {
1480
503
            trace!("asked for slots unnecessarily, trying fast path");
1481
503
            let m = self.search(cache, input)?;
1482
75
            copy_match_to_slots(m, slots);
1483
75
            return Some(m.pattern());
1484
113
        }
1485
113
        let hm_start = match self.try_search_half_start(cache, input) {
1486
46
            Err(RetryError::Quadratic(_err)) => {
1487
46
                trace!("reverse suffix captures optimization failed: {_err}");
1488
46
                return self.core.search_slots(cache, input, slots);
1489
            }
1490
6
            Err(RetryError::Fail(_err)) => {
1491
6
                trace!(
1492
0
                    "reverse suffix reverse fast captures search failed: \
1493
0
                     {_err}"
1494
                );
1495
6
                return self.core.search_slots_nofail(cache, input, slots);
1496
            }
1497
45
            Ok(None) => return None,
1498
16
            Ok(Some(hm_start)) => hm_start,
1499
        };
1500
16
        trace!(
1501
0
            "match found at {}..{} in capture search, \
1502
0
             using another engine to find captures",
1503
0
            hm_start.offset(),
1504
0
            input.end(),
1505
        );
1506
16
        let start = hm_start.offset();
1507
16
        let input = input
1508
16
            .clone()
1509
16
            .span(start..input.end())
1510
16
            .anchored(Anchored::Pattern(hm_start.pattern()));
1511
16
        self.core.search_slots_nofail(cache, &input, slots)
1512
616
    }
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
66.2k
    fn new(core: Core, hirs: &[&Hir]) -> Result<ReverseInner, Core> {
1536
66.2k
        if !core.info.config().get_auto_prefilter() {
1537
0
            debug!(
1538
0
                "skipping reverse inner optimization because \
1539
0
                 automatic prefilters are disabled"
1540
            );
1541
0
            return Err(core);
1542
66.2k
        }
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
66.2k
        if core.info.config().get_match_kind() != MatchKind::LeftmostFirst {
1548
0
            debug!(
1549
0
                "skipping reverse inner optimization because \
1550
0
                 match kind is {:?} but this only supports leftmost-first",
1551
0
                core.info.config().get_match_kind(),
1552
            );
1553
0
            return Err(core);
1554
66.2k
        }
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
66.2k
        if core.info.is_always_anchored_start() {
1571
2.08k
            debug!(
1572
0
                "skipping reverse inner optimization because \
1573
0
                 the regex is always anchored at the start",
1574
            );
1575
2.08k
            return Err(core);
1576
64.1k
        }
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
64.1k
        if !core.hybrid.is_some() && !core.dfa.is_some() {
1581
0
            debug!(
1582
0
                "skipping reverse inner optimization because \
1583
0
                 we don't have a lazy DFA or a full DFA"
1584
            );
1585
0
            return Err(core);
1586
64.1k
        }
1587
64.1k
        if core.pre.as_ref().map_or(false, |p| p.is_fast()) {
1588
15.6k
            debug!(
1589
0
                "skipping reverse inner optimization because \
1590
0
                 we already have a prefilter that we think is fast"
1591
            );
1592
15.6k
            return Err(core);
1593
48.5k
        } else if core.pre.is_some() {
1594
10.3k
            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
            );
1599
38.1k
        }
1600
48.5k
        let prefilter = match reverse_inner::InnerPrefilter::new(hirs) {
1601
8.15k
            Some(prefilter) => prefilter,
1602
            // N.B. the 'new' function emits debug messages explaining
1603
            // why we bailed out here.
1604
40.4k
            None => return Err(core),
1605
        };
1606
8.15k
        if !reverse_inner::has_no_earlier_match(
1607
8.15k
            &prefilter.prefix,
1608
8.15k
            &prefilter.literals,
1609
8.15k
        ) {
1610
4.00k
            debug!(
1611
0
                "skipping reverse inner optimization because an inner \
1612
0
                 literal match could be confirmed before an earlier match"
1613
            );
1614
4.00k
            return Err(core);
1615
4.14k
        }
1616
4.14k
        debug!("building reverse NFA for prefix before inner literal");
1617
4.14k
        let thompson_config = core
1618
4.14k
            .info
1619
4.14k
            .config()
1620
4.14k
            .to_thompson_config()
1621
4.14k
            .reverse(true)
1622
4.14k
            .which_captures(WhichCaptures::None);
1623
4.14k
        let result = thompson::Compiler::new()
1624
4.14k
            .configure(thompson_config)
1625
4.14k
            .build_from_hir(&prefilter.prefix);
1626
4.14k
        let nfarev = match result {
1627
4.14k
            Ok(nfarev) => nfarev,
1628
0
            Err(_err) => {
1629
0
                debug!(
1630
0
                    "skipping reverse inner optimization because the \
1631
0
                     reverse NFA failed to build: {}",
1632
                    _err,
1633
                );
1634
0
                return Err(core);
1635
            }
1636
        };
1637
4.14k
        debug!("building reverse DFA for prefix before inner literal");
1638
4.14k
        let dfa = if !core.info.config().get_dfa() {
1639
0
            wrappers::ReverseDFA::none()
1640
        } else {
1641
4.14k
            wrappers::ReverseDFA::new(&core.info, &nfarev)
1642
        };
1643
4.14k
        let hybrid = if !core.info.config().get_hybrid() {
1644
0
            wrappers::ReverseHybrid::none()
1645
4.14k
        } else if dfa.is_some() {
1646
3.27k
            debug!(
1647
0
                "skipping lazy DFA for reverse inner optimization \
1648
0
                 because we have a full DFA"
1649
            );
1650
3.27k
            wrappers::ReverseHybrid::none()
1651
        } else {
1652
864
            wrappers::ReverseHybrid::new(&core.info, &nfarev)
1653
        };
1654
4.14k
        Ok(ReverseInner { core, preinner: prefilter.pre, nfarev, hybrid, dfa })
1655
66.2k
    }
1656
1657
    #[cfg_attr(feature = "perf-inline", inline(always))]
1658
5.71k
    fn try_search_full(
1659
5.71k
        &self,
1660
5.71k
        cache: &mut Cache,
1661
5.71k
        input: &Input<'_>,
1662
5.71k
    ) -> Result<Option<Match>, RetryError> {
1663
5.71k
        let mut span = input.get_span();
1664
5.71k
        let mut min_match_start = 0;
1665
5.71k
        let mut min_pre_start = 0;
1666
        loop {
1667
261k
            let litmatch = match self.preinner.find(input.haystack(), span) {
1668
1.68k
                None => break,
1669
259k
                Some(span) => span,
1670
            };
1671
259k
            if litmatch.start < min_pre_start {
1672
1.20k
                trace!(
1673
0
                    "found inner prefilter match at {litmatch:?}, which starts \
1674
0
                     before the end of the last forward scan at {min_pre_start}, \
1675
0
                     quitting to avoid quadratic behavior",
1676
                );
1677
1.20k
                return Err(RetryError::Quadratic(RetryQuadraticError::new()));
1678
258k
            }
1679
258k
            trace!("reverse inner scan found inner match at {litmatch:?}");
1680
258k
            let revinput = input
1681
258k
                .clone()
1682
258k
                .anchored(Anchored::Yes)
1683
258k
                .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
258k
            if let Some(hm_start) = self.try_search_half_rev_limited(
1691
258k
                cache,
1692
258k
                &revinput,
1693
258k
                min_match_start,
1694
1.86k
            )? {
1695
30.4k
                let fwdinput = input
1696
30.4k
                    .clone()
1697
30.4k
                    .anchored(Anchored::Pattern(hm_start.pattern()))
1698
30.4k
                    .span(hm_start.offset()..input.end());
1699
30.4k
                match self.try_search_half_fwd_stopat(cache, &fwdinput)? {
1700
29.5k
                    Err(stopat) => {
1701
29.5k
                        min_pre_start = stopat;
1702
29.5k
                        span.start = litmatch.start.checked_add(1).unwrap();
1703
29.5k
                    }
1704
407
                    Ok(hm_end) => {
1705
407
                        return Ok(Some(Match::new(
1706
407
                            hm_start.pattern(),
1707
407
                            hm_start.offset()..hm_end.offset(),
1708
407
                        )));
1709
                    }
1710
                }
1711
226k
            }
1712
1713
255k
            if span.start >= span.end {
1714
102
                break;
1715
255k
            }
1716
255k
            span.start = litmatch.start.checked_add(1).unwrap();
1717
255k
            min_match_start = litmatch.end;
1718
        }
1719
1.78k
        Ok(None)
1720
5.71k
    }
1721
1722
    #[cfg_attr(feature = "perf-inline", inline(always))]
1723
30.4k
    fn try_search_half_fwd_stopat(
1724
30.4k
        &self,
1725
30.4k
        cache: &mut Cache,
1726
30.4k
        input: &Input<'_>,
1727
30.4k
    ) -> Result<Result<HalfMatch, usize>, RetryFailError> {
1728
30.4k
        if let Some(e) = self.core.dfa.get(&input) {
1729
10.5k
            trace!(
1730
0
                "using full DFA for forward reverse inner search at {:?}",
1731
0
                input.get_span()
1732
            );
1733
10.5k
            e.try_search_half_fwd_stopat(&input)
1734
19.9k
        } else if let Some(e) = self.core.hybrid.get(&input) {
1735
19.9k
            trace!(
1736
0
                "using lazy DFA for forward reverse inner search at {:?}",
1737
0
                input.get_span()
1738
            );
1739
19.9k
            e.try_search_half_fwd_stopat(&mut cache.hybrid, &input)
1740
        } else {
1741
0
            unreachable!("ReverseInner always has a DFA")
1742
        }
1743
30.4k
    }
1744
1745
    #[cfg_attr(feature = "perf-inline", inline(always))]
1746
258k
    fn try_search_half_rev_limited(
1747
258k
        &self,
1748
258k
        cache: &mut Cache,
1749
258k
        input: &Input<'_>,
1750
258k
        min_start: usize,
1751
258k
    ) -> Result<Option<HalfMatch>, RetryError> {
1752
258k
        if let Some(e) = self.dfa.get(&input) {
1753
142k
            trace!(
1754
0
                "using full DFA for reverse inner search at {:?}, \
1755
0
                 but will be stopped at {} to avoid quadratic behavior",
1756
0
                input.get_span(),
1757
                min_start,
1758
            );
1759
142k
            e.try_search_half_rev_limited(&input, min_start)
1760
116k
        } else if let Some(e) = self.hybrid.get(&input) {
1761
116k
            trace!(
1762
0
                "using lazy DFA for reverse inner search at {:?}, \
1763
0
                 but will be stopped at {} to avoid quadratic behavior",
1764
0
                input.get_span(),
1765
                min_start,
1766
            );
1767
116k
            e.try_search_half_rev_limited(
1768
116k
                &mut cache.revhybrid,
1769
116k
                &input,
1770
116k
                min_start,
1771
            )
1772
        } else {
1773
0
            unreachable!("ReverseInner always has a DFA")
1774
        }
1775
258k
    }
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
2.39k
    fn group_info(&self) -> &GroupInfo {
1785
2.39k
        self.core.group_info()
1786
2.39k
    }
1787
1788
    #[cfg_attr(feature = "perf-inline", inline(always))]
1789
2.24k
    fn create_cache(&self) -> Cache {
1790
2.24k
        let mut cache = self.core.create_cache();
1791
2.24k
        cache.revhybrid = self.hybrid.create_cache();
1792
2.24k
        cache
1793
2.24k
    }
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
2.51k
    fn search(&self, cache: &mut Cache, input: &Input<'_>) -> Option<Match> {
1814
2.51k
        if input.get_anchored().is_anchored() {
1815
0
            return self.core.search(cache, input);
1816
2.51k
        }
1817
2.51k
        match self.try_search_full(cache, input) {
1818
1.21k
            Err(RetryError::Quadratic(_err)) => {
1819
1.21k
                trace!("reverse inner optimization failed: {_err}");
1820
1.21k
                self.core.search(cache, input)
1821
            }
1822
232
            Err(RetryError::Fail(_err)) => {
1823
232
                trace!("reverse inner fast search failed: {_err}");
1824
232
                self.core.search_nofail(cache, input)
1825
            }
1826
1.06k
            Ok(matornot) => matornot,
1827
        }
1828
2.51k
    }
1829
1830
    #[cfg_attr(feature = "perf-inline", inline(always))]
1831
1.04k
    fn search_half(
1832
1.04k
        &self,
1833
1.04k
        cache: &mut Cache,
1834
1.04k
        input: &Input<'_>,
1835
1.04k
    ) -> Option<HalfMatch> {
1836
1.04k
        if input.get_anchored().is_anchored() {
1837
0
            return self.core.search_half(cache, input);
1838
1.04k
        }
1839
1.04k
        match self.try_search_full(cache, input) {
1840
324
            Err(RetryError::Quadratic(_err)) => {
1841
324
                trace!("reverse inner half optimization failed: {_err}");
1842
324
                self.core.search_half(cache, input)
1843
            }
1844
42
            Err(RetryError::Fail(_err)) => {
1845
42
                trace!("reverse inner fast half search failed: {_err}");
1846
42
                self.core.search_half_nofail(cache, input)
1847
            }
1848
599
            Ok(None) => None,
1849
83
            Ok(Some(m)) => Some(HalfMatch::new(m.pattern(), m.end())),
1850
        }
1851
1.04k
    }
1852
1853
    #[cfg_attr(feature = "perf-inline", inline(always))]
1854
1.20k
    fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
1855
1.20k
        if input.get_anchored().is_anchored() {
1856
0
            return self.core.is_match(cache, input);
1857
1.20k
        }
1858
1.20k
        match self.try_search_full(cache, input) {
1859
783
            Err(RetryError::Quadratic(_err)) => {
1860
783
                trace!("reverse inner half optimization failed: {_err}");
1861
783
                self.core.is_match_nofail(cache, input)
1862
            }
1863
131
            Err(RetryError::Fail(_err)) => {
1864
131
                trace!("reverse inner fast half search failed: {_err}");
1865
131
                self.core.is_match_nofail(cache, input)
1866
            }
1867
208
            Ok(None) => false,
1868
78
            Ok(Some(_)) => true,
1869
        }
1870
1.20k
    }
1871
1872
    #[cfg_attr(feature = "perf-inline", inline(always))]
1873
1.73k
    fn search_slots(
1874
1.73k
        &self,
1875
1.73k
        cache: &mut Cache,
1876
1.73k
        input: &Input<'_>,
1877
1.73k
        slots: &mut [Option<NonMaxUsize>],
1878
1.73k
    ) -> Option<PatternID> {
1879
1.73k
        if input.get_anchored().is_anchored() {
1880
0
            return self.core.search_slots(cache, input, slots);
1881
1.73k
        }
1882
1.73k
        if !self.core.is_capture_search_needed(slots.len()) {
1883
777
            trace!("asked for slots unnecessarily, trying fast path");
1884
777
            let m = self.search(cache, input)?;
1885
166
            copy_match_to_slots(m, slots);
1886
166
            return Some(m.pattern());
1887
958
        }
1888
958
        let m = match self.try_search_full(cache, input) {
1889
699
            Err(RetryError::Quadratic(_err)) => {
1890
699
                trace!("reverse inner captures optimization failed: {_err}");
1891
699
                return self.core.search_slots(cache, input, slots);
1892
            }
1893
106
            Err(RetryError::Fail(_err)) => {
1894
106
                trace!("reverse inner fast captures search failed: {_err}");
1895
106
                return self.core.search_slots_nofail(cache, input, slots);
1896
            }
1897
131
            Ok(None) => return None,
1898
22
            Ok(Some(m)) => m,
1899
        };
1900
22
        trace!(
1901
0
            "match found at {}..{} in capture search, \
1902
0
             using another engine to find captures",
1903
0
            m.start(),
1904
0
            m.end(),
1905
        );
1906
22
        let input = input
1907
22
            .clone()
1908
22
            .span(m.start()..m.end())
1909
22
            .anchored(Anchored::Pattern(m.pattern()));
1910
22
        self.core.search_slots_nofail(cache, &input, slots)
1911
1.73k
    }
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
7.60k
fn copy_match_to_slots(m: Match, slots: &mut [Option<NonMaxUsize>]) {
1936
7.60k
    let slot_start = m.pattern().as_usize() * 2;
1937
7.60k
    let slot_end = slot_start + 1;
1938
7.60k
    if let Some(slot) = slots.get_mut(slot_start) {
1939
7.60k
        *slot = NonMaxUsize::new(m.start());
1940
7.60k
    }
1941
7.60k
    if let Some(slot) = slots.get_mut(slot_end) {
1942
7.60k
        *slot = NonMaxUsize::new(m.end());
1943
7.60k
    }
1944
7.60k
}
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
}