Commit fcc4980
committed
automata: fix bug in reverse suffix/inner optimization
A minimal reproducer of this bug is on a haystack of `zabb` with the
regex `.bb|b`. The `regex` crate will report a match at `2..3`, but the
correct match is `1..4`.
While this seems like a simple regex, there are a pretty specific set
of circumstances required to trigger the bug:
1. There are no prefix literals that activate a standard prefix
literal scan.
2. There needs to be an extractable *suffix* or *inner* literal.
3. An actual match needs to be present in the haystack.
4. The regex and haystack
Crucially, note that because of (3), this bug will never lead to
`Regex::is_match` providing a false positive *or* a false negative.
This bug is strictly about leftmost-first match semantics being
incorrect in some cases and will report an incorrect match span.
(4) could do with a bit more explanation, since it's rather subtle.
Let's trace the minimal example through the regex crate's "reverse
suffix" optimization.
During compilation, there is no prefix literal that can be extracted.
The `.` defeats that class of optimization. Moreover, there is a suffix
literal in the regex. That is, all matches for `.bb|b` must end with
`b`. The regex crate sees this and will scan for matches of `b`. It will
then attempt to match the regex in reverse at each candidate match of
`b`. Let's see what happens:
* Find first occurrence of `b` at offset `2` in `zabb`.
* Start reverse confirmation step at offset `2`.
* The second alternation branch, `b` in `.bb|b`, matches at `2..3`.
* The second alternation branch is reported as the overall match.
This happens because the first alternation branch, `.bb`, does _not_
have a match ending at offset `3`.
The fundamental problem here is that there is an overlap between the
reverse automaton for confirming the match and the literal scan. Small
changes, even to the haystack, can result in the bug disappearing.
For example, with a haystack of `zbb`, the correct match of `0..3` is
reported. This occurs because there is a quadratic "trip wire" that
triggers in this case that causes the search to bail out and fall back
to a DFA without using any literal optimizations.
This bug also applies to the "reverse inner" optimization. This
can happen when the literal is extracted from inside the regex
as opposed to it being a suffix literal. For example, the regex
`(?:..acbb|b)a(?:c|d)` on the haystack `xzbacbbac` reported a match at
`2..5`, but the correct match is `1..9`.
Note that #1355 technically fixes this problem and is much simpler, but
in so doing, makes the reverse suffix and inner optimizations completely
ineffective.
Fixes #1354, Closes #13551 parent 2c7b172 commit fcc4980
3 files changed
Lines changed: 1017 additions & 57 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
54 | 56 | | |
55 | 57 | | |
56 | 58 | | |
| |||
73 | 75 | | |
74 | 76 | | |
75 | 77 | | |
76 | | - | |
| 78 | + | |
77 | 79 | | |
78 | 80 | | |
79 | 81 | | |
| |||
96 | 98 | | |
97 | 99 | | |
98 | 100 | | |
99 | | - | |
100 | | - | |
101 | | - | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
102 | 104 | | |
103 | | - | |
| 105 | + | |
104 | 106 | | |
105 | | - | |
| 107 | + | |
106 | 108 | | |
107 | 109 | | |
108 | 110 | | |
109 | | - | |
| 111 | + | |
110 | 112 | | |
111 | 113 | | |
112 | 114 | | |
| |||
124 | 126 | | |
125 | 127 | | |
126 | 128 | | |
127 | | - | |
| 129 | + | |
128 | 130 | | |
129 | 131 | | |
130 | 132 | | |
| |||
148 | 150 | | |
149 | 151 | | |
150 | 152 | | |
151 | | - | |
152 | | - | |
153 | | - | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
154 | 157 | | |
155 | 158 | | |
156 | 159 | | |
| |||
0 commit comments