/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 | | } |