Skip to main content

regex_automata/dfa/
search.rs

1use crate::{
2    dfa::{
3        accel,
4        automaton::{Automaton, OverlappingState},
5    },
6    util::{
7        prefilter::Prefilter,
8        primitives::StateID,
9        search::{Anchored, HalfMatch, Input, Span},
10    },
11    MatchError,
12};
13
14#[inline(never)]
15pub fn find_fwd<A: Automaton + ?Sized>(
16    dfa: &A,
17    input: &Input<'_>,
18) -> Result<Option<HalfMatch>, MatchError> {
19    if input.is_done() {
20        return Ok(None);
21    }
22    let pre = if input.get_anchored().is_anchored() {
23        None
24    } else {
25        dfa.get_prefilter()
26    };
27    // Searching with a pattern ID is always anchored, so we should never use
28    // a prefilter.
29    if pre.is_some() {
30        if input.get_earliest() {
31            find_fwd_imp(dfa, input, pre, true)
32        } else {
33            find_fwd_imp(dfa, input, pre, false)
34        }
35    } else {
36        if input.get_earliest() {
37            find_fwd_imp(dfa, input, None, true)
38        } else {
39            find_fwd_imp(dfa, input, None, false)
40        }
41    }
42}
43
44#[cfg_attr(feature = "perf-inline", inline(always))]
45fn find_fwd_imp<A: Automaton + ?Sized>(
46    dfa: &A,
47    input: &Input<'_>,
48    pre: Option<&'_ Prefilter>,
49    earliest: bool,
50) -> Result<Option<HalfMatch>, MatchError> {
51    // See 'prefilter_restart' docs for explanation.
52    let universal_start = dfa.universal_start_state(Anchored::No).is_some();
53    let mut mat = None;
54    let mut sid = init_fwd(dfa, input)?;
55    let mut at = input.start();
56    // This could just be a closure, but then I think it would be unsound
57    // because it would need to be safe to invoke. This way, the lack of safety
58    // is clearer in the code below.
59    macro_rules! next_unchecked {
60        ($sid:expr, $at:expr) => {{
61            let byte = *input.haystack().get_unchecked($at);
62            dfa.next_state_unchecked($sid, byte)
63        }};
64    }
65
66    if let Some(ref pre) = pre {
67        let span = Span::from(at..input.end());
68        // If a prefilter doesn't report false positives, then we don't need to
69        // touch the DFA at all. However, since all matches include the pattern
70        // ID, and the prefilter infrastructure doesn't report pattern IDs, we
71        // limit this optimization to cases where there is exactly one pattern.
72        // In that case, any match must be the 0th pattern.
73        match pre.find(input.haystack(), span) {
74            None => return Ok(mat),
75            Some(ref span) => {
76                at = span.start;
77                if !universal_start {
78                    sid = prefilter_restart(dfa, &input, at)?;
79                }
80            }
81        }
82    }
83    while at < input.end() {
84        // SAFETY: There are two safety invariants we need to uphold here in
85        // the loops below: that 'sid' and 'prev_sid' are valid state IDs
86        // for this DFA, and that 'at' is a valid index into 'haystack'.
87        // For the former, we rely on the invariant that next_state* and
88        // start_state_forward always returns a valid state ID (given a valid
89        // state ID in the former case). For the latter safety invariant, we
90        // always guard unchecked access with a check that 'at' is less than
91        // 'end', where 'end <= haystack.len()'. In the unrolled loop below, we
92        // ensure that 'at' is always in bounds.
93        //
94        // PERF: See a similar comment in src/hybrid/search.rs that justifies
95        // this extra work to make the search loop fast. The same reasoning and
96        // benchmarks apply here.
97        let mut prev_sid;
98        while at < input.end() {
99            prev_sid = unsafe { {
    let byte = *input.haystack().get_unchecked(at);
    dfa.next_state_unchecked(sid, byte)
}next_unchecked!(sid, at) };
100            if dfa.is_special_state(prev_sid) || at + 3 >= input.end() {
101                core::mem::swap(&mut prev_sid, &mut sid);
102                break;
103            }
104            at += 1;
105
106            sid = unsafe { {
    let byte = *input.haystack().get_unchecked(at);
    dfa.next_state_unchecked(prev_sid, byte)
}next_unchecked!(prev_sid, at) };
107            if dfa.is_special_state(sid) {
108                break;
109            }
110            at += 1;
111
112            prev_sid = unsafe { {
    let byte = *input.haystack().get_unchecked(at);
    dfa.next_state_unchecked(sid, byte)
}next_unchecked!(sid, at) };
113            if dfa.is_special_state(prev_sid) {
114                core::mem::swap(&mut prev_sid, &mut sid);
115                break;
116            }
117            at += 1;
118
119            sid = unsafe { {
    let byte = *input.haystack().get_unchecked(at);
    dfa.next_state_unchecked(prev_sid, byte)
}next_unchecked!(prev_sid, at) };
120            if dfa.is_special_state(sid) {
121                break;
122            }
123            at += 1;
124        }
125        if dfa.is_special_state(sid) {
126            if dfa.is_start_state(sid) {
127                if let Some(ref pre) = pre {
128                    // A prefilter finds possible starts of new matches. Once
129                    // a match is pending, the DFA may still be resolving its
130                    // priority. Skipping ahead here could discard that match
131                    // and replace it with a later one.
132                    if mat.is_none() {
133                        let span = Span::from(at..input.end());
134                        match pre.find(input.haystack(), span) {
135                            None => return Ok(mat),
136                            Some(ref span) => {
137                                // We want to skip any update to 'at' below
138                                // at the end of this iteration and just
139                                // jump immediately back to the next state
140                                // transition at the leading position of the
141                                // candidate match.
142                                //
143                                // ... but only if we actually made progress
144                                // with our prefilter, otherwise if the start
145                                // state has a self-loop, we can get stuck.
146                                if span.start > at {
147                                    at = span.start;
148                                    if !universal_start {
149                                        sid = prefilter_restart(
150                                            dfa, &input, at,
151                                        )?;
152                                    }
153                                    continue;
154                                }
155                            }
156                        }
157                    }
158                } else if dfa.is_accel_state(sid) {
159                    let needles = dfa.accelerator(sid);
160                    at = accel::find_fwd(needles, input.haystack(), at + 1)
161                        .unwrap_or(input.end());
162                    continue;
163                }
164            } else if dfa.is_match_state(sid) {
165                let pattern = dfa.match_pattern(sid, 0);
166                mat = Some(HalfMatch::new(pattern, at));
167                if earliest {
168                    return Ok(mat);
169                }
170                if dfa.is_accel_state(sid) {
171                    let needles = dfa.accelerator(sid);
172                    at = accel::find_fwd(needles, input.haystack(), at + 1)
173                        .unwrap_or(input.end());
174                    continue;
175                }
176            } else if dfa.is_accel_state(sid) {
177                let needs = dfa.accelerator(sid);
178                at = accel::find_fwd(needs, input.haystack(), at + 1)
179                    .unwrap_or(input.end());
180                continue;
181            } else if dfa.is_dead_state(sid) {
182                return Ok(mat);
183            } else {
184                // It's important that this is a debug_assert, since this can
185                // actually be tripped even if DFA::from_bytes succeeds and
186                // returns a supposedly valid DFA.
187                return Err(MatchError::quit(input.haystack()[at], at));
188            }
189        }
190        at += 1;
191    }
192    eoi_fwd(dfa, input, &mut sid, &mut mat)?;
193    Ok(mat)
194}
195
196#[inline(never)]
197pub fn find_rev<A: Automaton + ?Sized>(
198    dfa: &A,
199    input: &Input<'_>,
200) -> Result<Option<HalfMatch>, MatchError> {
201    if input.is_done() {
202        return Ok(None);
203    }
204    if input.get_earliest() {
205        find_rev_imp(dfa, input, true)
206    } else {
207        find_rev_imp(dfa, input, false)
208    }
209}
210
211#[cfg_attr(feature = "perf-inline", inline(always))]
212fn find_rev_imp<A: Automaton + ?Sized>(
213    dfa: &A,
214    input: &Input<'_>,
215    earliest: bool,
216) -> Result<Option<HalfMatch>, MatchError> {
217    let mut mat = None;
218    let mut sid = init_rev(dfa, input)?;
219    // In reverse search, the loop below can't handle the case of searching an
220    // empty slice. Ideally we could write something congruent to the forward
221    // search, i.e., 'while at >= start', but 'start' might be 0. Since we use
222    // an unsigned offset, 'at >= 0' is trivially always true. We could avoid
223    // this extra case handling by using a signed offset, but Rust makes it
224    // annoying to do. So... We just handle the empty case separately.
225    if input.start() == input.end() {
226        eoi_rev(dfa, input, &mut sid, &mut mat)?;
227        return Ok(mat);
228    }
229
230    let mut at = input.end() - 1;
231    macro_rules! next_unchecked {
232        ($sid:expr, $at:expr) => {{
233            let byte = *input.haystack().get_unchecked($at);
234            dfa.next_state_unchecked($sid, byte)
235        }};
236    }
237    loop {
238        // SAFETY: See comments in 'find_fwd' for a safety argument.
239        let mut prev_sid;
240        while at >= input.start() {
241            prev_sid = unsafe { {
    let byte = *input.haystack().get_unchecked(at);
    dfa.next_state_unchecked(sid, byte)
}next_unchecked!(sid, at) };
242            if dfa.is_special_state(prev_sid)
243                || at <= input.start().saturating_add(3)
244            {
245                core::mem::swap(&mut prev_sid, &mut sid);
246                break;
247            }
248            at -= 1;
249
250            sid = unsafe { {
    let byte = *input.haystack().get_unchecked(at);
    dfa.next_state_unchecked(prev_sid, byte)
}next_unchecked!(prev_sid, at) };
251            if dfa.is_special_state(sid) {
252                break;
253            }
254            at -= 1;
255
256            prev_sid = unsafe { {
    let byte = *input.haystack().get_unchecked(at);
    dfa.next_state_unchecked(sid, byte)
}next_unchecked!(sid, at) };
257            if dfa.is_special_state(prev_sid) {
258                core::mem::swap(&mut prev_sid, &mut sid);
259                break;
260            }
261            at -= 1;
262
263            sid = unsafe { {
    let byte = *input.haystack().get_unchecked(at);
    dfa.next_state_unchecked(prev_sid, byte)
}next_unchecked!(prev_sid, at) };
264            if dfa.is_special_state(sid) {
265                break;
266            }
267            at -= 1;
268        }
269        if dfa.is_special_state(sid) {
270            if dfa.is_start_state(sid) {
271                if dfa.is_accel_state(sid) {
272                    let needles = dfa.accelerator(sid);
273                    at = accel::find_rev(needles, input.haystack(), at)
274                        .map(|i| i + 1)
275                        .unwrap_or(input.start());
276                }
277            } else if dfa.is_match_state(sid) {
278                let pattern = dfa.match_pattern(sid, 0);
279                // Since reverse searches report the beginning of a match
280                // and the beginning is inclusive (not exclusive like the
281                // end of a match), we add 1 to make it inclusive.
282                mat = Some(HalfMatch::new(pattern, at + 1));
283                if earliest {
284                    return Ok(mat);
285                }
286                if dfa.is_accel_state(sid) {
287                    let needles = dfa.accelerator(sid);
288                    at = accel::find_rev(needles, input.haystack(), at)
289                        .map(|i| i + 1)
290                        .unwrap_or(input.start());
291                }
292            } else if dfa.is_accel_state(sid) {
293                let needles = dfa.accelerator(sid);
294                // If the accelerator returns nothing, why don't we quit the
295                // search? Well, if the accelerator doesn't find anything, that
296                // doesn't mean we don't have a match. It just means that we
297                // can't leave the current state given one of the 255 possible
298                // byte values. However, there might be an EOI transition. So
299                // we set 'at' to the end of the haystack, which will cause
300                // this loop to stop and fall down into the EOI transition.
301                at = accel::find_rev(needles, input.haystack(), at)
302                    .map(|i| i + 1)
303                    .unwrap_or(input.start());
304            } else if dfa.is_dead_state(sid) {
305                return Ok(mat);
306            } else {
307                return Err(MatchError::quit(input.haystack()[at], at));
308            }
309        }
310        if at == input.start() {
311            break;
312        }
313        at -= 1;
314    }
315    eoi_rev(dfa, input, &mut sid, &mut mat)?;
316    Ok(mat)
317}
318
319#[inline(never)]
320pub fn find_overlapping_fwd<A: Automaton + ?Sized>(
321    dfa: &A,
322    input: &Input<'_>,
323    state: &mut OverlappingState,
324) -> Result<(), MatchError> {
325    state.mat = None;
326    if input.is_done() {
327        return Ok(());
328    }
329    let pre = if input.get_anchored().is_anchored() {
330        None
331    } else {
332        dfa.get_prefilter()
333    };
334    if pre.is_some() {
335        find_overlapping_fwd_imp(dfa, input, pre, state)
336    } else {
337        find_overlapping_fwd_imp(dfa, input, None, state)
338    }
339}
340
341#[cfg_attr(feature = "perf-inline", inline(always))]
342fn find_overlapping_fwd_imp<A: Automaton + ?Sized>(
343    dfa: &A,
344    input: &Input<'_>,
345    pre: Option<&'_ Prefilter>,
346    state: &mut OverlappingState,
347) -> Result<(), MatchError> {
348    // See 'prefilter_restart' docs for explanation.
349    let universal_start = dfa.universal_start_state(Anchored::No).is_some();
350    let mut sid = match state.id {
351        None => {
352            state.at = input.start();
353            init_fwd(dfa, input)?
354        }
355        Some(sid) => {
356            if let Some(match_index) = state.next_match_index {
357                let match_len = dfa.match_len(sid);
358                if match_index < match_len {
359                    state.next_match_index = Some(match_index + 1);
360                    let pattern = dfa.match_pattern(sid, match_index);
361                    state.mat = Some(HalfMatch::new(pattern, state.at));
362                    return Ok(());
363                }
364            }
365            // Once we've reported all matches at a given position, we need to
366            // advance the search to the next position.
367            state.at += 1;
368            if state.at > input.end() {
369                return Ok(());
370            }
371            sid
372        }
373    };
374
375    // NOTE: We don't optimize the crap out of this routine primarily because
376    // it seems like most find_overlapping searches will have higher match
377    // counts, and thus, throughput is perhaps not as important. But if you
378    // have a use case for something faster, feel free to file an issue.
379    while state.at < input.end() {
380        sid = dfa.next_state(sid, input.haystack()[state.at]);
381        if dfa.is_special_state(sid) {
382            state.id = Some(sid);
383            if dfa.is_start_state(sid) {
384                if let Some(ref pre) = pre {
385                    let span = Span::from(state.at..input.end());
386                    match pre.find(input.haystack(), span) {
387                        None => return Ok(()),
388                        Some(ref span) => {
389                            if span.start > state.at {
390                                state.at = span.start;
391                                if !universal_start {
392                                    sid = prefilter_restart(
393                                        dfa, &input, state.at,
394                                    )?;
395                                }
396                                continue;
397                            }
398                        }
399                    }
400                } else if dfa.is_accel_state(sid) {
401                    let needles = dfa.accelerator(sid);
402                    state.at = accel::find_fwd(
403                        needles,
404                        input.haystack(),
405                        state.at + 1,
406                    )
407                    .unwrap_or(input.end());
408                    continue;
409                }
410            } else if dfa.is_match_state(sid) {
411                state.next_match_index = Some(1);
412                let pattern = dfa.match_pattern(sid, 0);
413                state.mat = Some(HalfMatch::new(pattern, state.at));
414                return Ok(());
415            } else if dfa.is_accel_state(sid) {
416                let needs = dfa.accelerator(sid);
417                // If the accelerator returns nothing, why don't we quit the
418                // search? Well, if the accelerator doesn't find anything, that
419                // doesn't mean we don't have a match. It just means that we
420                // can't leave the current state given one of the 255 possible
421                // byte values. However, there might be an EOI transition. So
422                // we set 'at' to the end of the haystack, which will cause
423                // this loop to stop and fall down into the EOI transition.
424                state.at =
425                    accel::find_fwd(needs, input.haystack(), state.at + 1)
426                        .unwrap_or(input.end());
427                continue;
428            } else if dfa.is_dead_state(sid) {
429                return Ok(());
430            } else {
431                return Err(MatchError::quit(
432                    input.haystack()[state.at],
433                    state.at,
434                ));
435            }
436        }
437        state.at += 1;
438    }
439
440    let result = eoi_fwd(dfa, input, &mut sid, &mut state.mat);
441    state.id = Some(sid);
442    if state.mat.is_some() {
443        // '1' is always correct here since if we get to this point, this
444        // always corresponds to the first (index '0') match discovered at
445        // this position. So the next match to report at this position (if
446        // it exists) is at index '1'.
447        state.next_match_index = Some(1);
448    }
449    result
450}
451
452#[inline(never)]
453pub(crate) fn find_overlapping_rev<A: Automaton + ?Sized>(
454    dfa: &A,
455    input: &Input<'_>,
456    state: &mut OverlappingState,
457) -> Result<(), MatchError> {
458    state.mat = None;
459    if input.is_done() {
460        return Ok(());
461    }
462    let mut sid = match state.id {
463        None => {
464            let sid = init_rev(dfa, input)?;
465            state.id = Some(sid);
466            if input.start() == input.end() {
467                state.rev_eoi = true;
468            } else {
469                state.at = input.end() - 1;
470            }
471            sid
472        }
473        Some(sid) => {
474            if let Some(match_index) = state.next_match_index {
475                let match_len = dfa.match_len(sid);
476                if match_index < match_len {
477                    state.next_match_index = Some(match_index + 1);
478                    let pattern = dfa.match_pattern(sid, match_index);
479                    state.mat = Some(HalfMatch::new(pattern, state.at));
480                    return Ok(());
481                }
482            }
483            // Once we've reported all matches at a given position, we need
484            // to advance the search to the next position. However, if we've
485            // already followed the EOI transition, then we know we're done
486            // with the search and there cannot be any more matches to report.
487            if state.rev_eoi {
488                return Ok(());
489            } else if state.at == input.start() {
490                // At this point, we should follow the EOI transition. This
491                // will cause us the skip the main loop below and fall through
492                // to the final 'eoi_rev' transition.
493                state.rev_eoi = true;
494            } else {
495                // We haven't hit the end of the search yet, so move on.
496                state.at -= 1;
497            }
498            sid
499        }
500    };
501    while !state.rev_eoi {
502        sid = dfa.next_state(sid, input.haystack()[state.at]);
503        if dfa.is_special_state(sid) {
504            state.id = Some(sid);
505            if dfa.is_start_state(sid) {
506                if dfa.is_accel_state(sid) {
507                    let needles = dfa.accelerator(sid);
508                    state.at =
509                        accel::find_rev(needles, input.haystack(), state.at)
510                            .map(|i| i + 1)
511                            .unwrap_or(input.start());
512                }
513            } else if dfa.is_match_state(sid) {
514                state.next_match_index = Some(1);
515                let pattern = dfa.match_pattern(sid, 0);
516                state.mat = Some(HalfMatch::new(pattern, state.at + 1));
517                return Ok(());
518            } else if dfa.is_accel_state(sid) {
519                let needles = dfa.accelerator(sid);
520                // If the accelerator returns nothing, why don't we quit the
521                // search? Well, if the accelerator doesn't find anything, that
522                // doesn't mean we don't have a match. It just means that we
523                // can't leave the current state given one of the 255 possible
524                // byte values. However, there might be an EOI transition. So
525                // we set 'at' to the end of the haystack, which will cause
526                // this loop to stop and fall down into the EOI transition.
527                state.at =
528                    accel::find_rev(needles, input.haystack(), state.at)
529                        .map(|i| i + 1)
530                        .unwrap_or(input.start());
531            } else if dfa.is_dead_state(sid) {
532                return Ok(());
533            } else {
534                return Err(MatchError::quit(
535                    input.haystack()[state.at],
536                    state.at,
537                ));
538            }
539        }
540        if state.at == input.start() {
541            break;
542        }
543        state.at -= 1;
544    }
545
546    let result = eoi_rev(dfa, input, &mut sid, &mut state.mat);
547    state.rev_eoi = true;
548    state.id = Some(sid);
549    if state.mat.is_some() {
550        // '1' is always correct here since if we get to this point, this
551        // always corresponds to the first (index '0') match discovered at
552        // this position. So the next match to report at this position (if
553        // it exists) is at index '1'.
554        state.next_match_index = Some(1);
555    }
556    result
557}
558
559#[cfg_attr(feature = "perf-inline", inline(always))]
560fn init_fwd<A: Automaton + ?Sized>(
561    dfa: &A,
562    input: &Input<'_>,
563) -> Result<StateID, MatchError> {
564    let sid = dfa.start_state_forward(input)?;
565    // Start states can never be match states, since all matches are delayed
566    // by 1 byte.
567    if true {
    if !!dfa.is_match_state(sid) {
        ::core::panicking::panic("assertion failed: !dfa.is_match_state(sid)")
    };
};debug_assert!(!dfa.is_match_state(sid));
568    Ok(sid)
569}
570
571#[cfg_attr(feature = "perf-inline", inline(always))]
572fn init_rev<A: Automaton + ?Sized>(
573    dfa: &A,
574    input: &Input<'_>,
575) -> Result<StateID, MatchError> {
576    let sid = dfa.start_state_reverse(input)?;
577    // Start states can never be match states, since all matches are delayed
578    // by 1 byte.
579    if true {
    if !!dfa.is_match_state(sid) {
        ::core::panicking::panic("assertion failed: !dfa.is_match_state(sid)")
    };
};debug_assert!(!dfa.is_match_state(sid));
580    Ok(sid)
581}
582
583#[cfg_attr(feature = "perf-inline", inline(always))]
584fn eoi_fwd<A: Automaton + ?Sized>(
585    dfa: &A,
586    input: &Input<'_>,
587    sid: &mut StateID,
588    mat: &mut Option<HalfMatch>,
589) -> Result<(), MatchError> {
590    let sp = input.get_span();
591    match input.haystack().get(sp.end) {
592        Some(&b) => {
593            *sid = dfa.next_state(*sid, b);
594            if dfa.is_match_state(*sid) {
595                let pattern = dfa.match_pattern(*sid, 0);
596                *mat = Some(HalfMatch::new(pattern, sp.end));
597            } else if dfa.is_quit_state(*sid) {
598                return Err(MatchError::quit(b, sp.end));
599            }
600        }
601        None => {
602            *sid = dfa.next_eoi_state(*sid);
603            if dfa.is_match_state(*sid) {
604                let pattern = dfa.match_pattern(*sid, 0);
605                *mat = Some(HalfMatch::new(pattern, input.haystack().len()));
606            }
607        }
608    }
609    Ok(())
610}
611
612#[cfg_attr(feature = "perf-inline", inline(always))]
613fn eoi_rev<A: Automaton + ?Sized>(
614    dfa: &A,
615    input: &Input<'_>,
616    sid: &mut StateID,
617    mat: &mut Option<HalfMatch>,
618) -> Result<(), MatchError> {
619    let sp = input.get_span();
620    if sp.start > 0 {
621        let byte = input.haystack()[sp.start - 1];
622        *sid = dfa.next_state(*sid, byte);
623        if dfa.is_match_state(*sid) {
624            let pattern = dfa.match_pattern(*sid, 0);
625            *mat = Some(HalfMatch::new(pattern, sp.start));
626        } else if dfa.is_quit_state(*sid) {
627            return Err(MatchError::quit(byte, sp.start - 1));
628        }
629    } else {
630        *sid = dfa.next_eoi_state(*sid);
631        if dfa.is_match_state(*sid) {
632            let pattern = dfa.match_pattern(*sid, 0);
633            *mat = Some(HalfMatch::new(pattern, 0));
634        }
635    }
636    Ok(())
637}
638
639/// Re-compute the starting state that a DFA should be in after finding a
640/// prefilter candidate match at the position `at`.
641///
642/// The function with the same name has a bit more docs in hybrid/search.rs.
643#[cfg_attr(feature = "perf-inline", inline(always))]
644fn prefilter_restart<A: Automaton + ?Sized>(
645    dfa: &A,
646    input: &Input<'_>,
647    at: usize,
648) -> Result<StateID, MatchError> {
649    let mut input = input.clone();
650    input.set_start(at);
651    init_fwd(dfa, &input)
652}