en.javascript.info/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/article.md
2015-03-22 00:36:11 +03:00

82 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Чёрная дыра бэктрекинга [todo]
Некоторые регулярные выражения, с виду являясь простыми, могут выполняться оооочень долго, и даже подвешивать браузер.
[cut]
Например, попробуйте пример ниже в Chrome или IE (осторожно, подвесит браузер!):
```js
//+ run
alert( '123456789012345678901234567890z'.match(/(\d+)*$/) );
```
Некоторые движки регулярных выражений (Firefox) справляются с таким регэкспом, а некоторые (IE, Chrome) -- нет.
В чём же дело, что не так с регэкспом?
Да с регэкспом-то всё так, синтаксис вполне допустимый. Проблема в том, как выполняется поиск по нему.
Для краткости рассмотрим более короткую строку: <code class="subject">1234567890z</code>:
<ol>
<li>Первым делом, движок регэкспов пытается найти <code class="pattern">\d+</code>. Плюс <code class="pattern">+</code> является жадным по умолчанию, так что он хватает все цифры, какие может.
Затем движок пытается применить звёздочку вокруг скобок <code class="pattern">(\d+)*</code>, но больше цифр нет, так что звёздочка не даёт повторений.
После этого в паттерне остаётся <code class="pattern">$</code>, а в тексте -- символ <code class="subject">z</code>.
<img src="bad_backtrack_greedy1.png">
Так как соответствия нет, то жадный плюс <code class="pattern">+</code> отступает на один символ (бэктрекинг, зелёная стрелка на рисунке выше).
</li>
<li>После бэктрекинга, <code class="pattern">\d+</code> содержит всё число, кроме последней цифры. Затем движок снова пытается найти совпадение, уже с новой позиции (`9`).
Звёздочка <code class="pattern">(\d+)*</code> теперь может быть применена -- она даёт ещё одно число <code class="match">9</code>:
<img src="bad_backtrack_greedy11.png">
Движок пытается найти `$`, но это ему не удаётся -- на его пути опять `z`:
<img src="bad_backtrack_greedy2.png">
Так как совпадения нет, то поисковой движок отступает назад ещё раз.
</li>
<li>Теперь первое число <code class="pattern">\d+</code> будет содержать 8 цифр, а остаток строки <code class="subject">90</code> становится вторым <code class="pattern">\d+</code>:
<img src="bad_backtrack_greedy3.png">
Увы, всё ещё нет соответствия для <code class="pattern">$</code>.
Поисковой движок снова должен отступить назад. При этом последний жадный квантификатор отпускает символ. В данном случае это означает, что укорачивается второй <code class="pattern">\d+</code>, до одного символа <code class="subject">9</code>.
</li>
<li>Теперь движок регулярных выражений снова может применить звёздочку и находит третье число <code class="pattern">\d+</code>:
<img src="bad_backtrack_greedy4.png">
...И снова неудача. Второе и третье <code class="pattern">\d+</code> отступили по-максимуму, так что сокращается снова первое число.
</li>
<li>Теперь есть 7 цифр в первом <code class="pattern">\d+</code>. Поисковой движок видит место для второго <code class="pattern">\d+</code>, теперь уже с позиции 8:
<img src="bad_backtrack_greedy5.png">
Так как совпадения нет, второй <code class="pattern">\d+</code> отступает назад....
</li>
<li>...И так далее, легко видеть, что поисковой движок будет перебирать *все возможные комбинации* <code class="pattern">\d+</code> в числе. А их много.</li>
</ol>
На этом месте умный читатель может воскликнуть: "Бэктрекинг? Давайте включим ленивый режим -- и не будет никакого бэктрекинга!"
Что ж, заменим <code class="pattern">\d+</code> на <code class="pattern">\d+?</code> и посмотрим (аккуратно, может подвесить браузер):
```js
//+ run
alert( '123456789012345678901234567890z'.match(/(\d+?)*$/) );
```
Не помогло!
**Ленивые регулярные выражения делают то же самое, но в обратном порядке.**
Просто подумайте о том, как будет в этом случае работать поисковой движок.
Некоторые движки регулярных выражений, например Firefox, содержат хитрые проверки, в дополнение к алгоритму выше, которые позволяют избежать бесконечного перебора или кардинально ускорить его, но все движки и не всегда.