Skip to content

Commit a125c72

Browse files
committed
fixes & suggestions
1 parent 19ed211 commit a125c72

File tree

1 file changed

+19
-19
lines changed
  • 9-regular-expressions/15-regexp-infinite-backtracking-problem

1 file changed

+19
-19
lines changed

9-regular-expressions/15-regexp-infinite-backtracking-problem/article.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Проблема поиска с бесконечным бэктрекингом
1+
# Проблема поиска с бесконечным возвратом
22

33
Некоторые регулярные выражения, с виду являясь простыми, могут выполняться оооочень долго, и даже "подвешивать" интерпретатор JavaScript.
44

@@ -8,7 +8,7 @@
88

99
В веб-браузере такой случай убивает страницу. Явно плохая ситуация.
1010

11-
Такой код, выполняемый на стороне сервера, может стать уязвимостью, так как он использует регулярные выражения для обработки пользовательских данных. Некорректный ввод данных приведет к зависанию процесса и, как следствие, отказу сервиса. Автор лично видел и сообщал о таких уязвимостях даже для очень известных и широко используемых программ.
11+
Ну а для серверного JavaScript это может стать серьёзной уязвимостью, так как регулярные выражения используются для обработки пользовательских данных. Некорректный ввод данных приведет к зависанию процесса и, как следствие, отказу сервиса. Автор лично видел и сообщал о таких уязвимостях даже для очень известных и широко используемых программ.
1212

1313
Так что проблема, несомненно, достойна рассмотрения.
1414

@@ -22,11 +22,11 @@
2222

2323
Например, давайте рассмотрим поиск тегов в HTML.
2424

25-
Мы хотим найти все теги с атрибутами (или без них) типа: `subject:<a href="..." class="doc" ...>`. Нужно, чтобы регулярное выражение работало надёжно, так как HTML приходит из Интернета и может быть "грязным".
25+
Мы хотим найти все теги с атрибутами (или без них) типа: `subject:<a href="..." class="doc" ...>`. Нужно, чтобы регулярное выражение работало надёжно, так как HTML приходит из Интернета и может быть некорректным.
2626

27-
В частности, чтобы регулярное выражение находило теги типа: `<a test="<>" href="#">` -- т.е. с символами `<` и `>` внутри атрибутов, так как это поддерживается [стандартом HTML](https://html.spec.whatwg.org/multipage/syntax.html#syntax-attributes).
27+
В частности, нам нужно, чтобы регулярное выражение находило теги типа: `<a test="<>" href="#">` -- т.е. с символами `<` и `>` внутри атрибутов, так как это поддерживается [стандартом HTML](https://html.spec.whatwg.org/multipage/syntax.html#syntax-attributes).
2828

29-
Как видим, простое регулярное выражение `pattern:<[^>]+>` не работает, потому что оно останавливает поиск на первом `>`, а нам нужно игнорировать `<>`, если они являются частью атрибута.
29+
Простое регулярное выражение `pattern:<[^>]+>` не работает, потому что оно останавливает поиск на первом `>`, а нам нужно игнорировать `<>`, если они являются частью атрибута.
3030

3131
```js run
3232
// поиск не достигает конца тега - неверно!
@@ -41,7 +41,7 @@ alert( '<a test="<>" href="#">'.match(/<[^>]+>/) ); // <a test="<>
4141

4242
Если мы подставим это в паттерн, описанный выше, и добавим дополнительные пробелы `pattern:\s`, то получим следующее: `pattern:<\w+(\s*\w+="[^"]*"\s*)*>`.
4343

44-
Это регулярное выражение неидеально! Оно всё ещё не поддерживает все детали HTML, например, значения в кавычках, и, хотя и есть способы улучшить его, давайте не будем его усложнять. Оно продемонстрирует нам проблему.
44+
Это регулярное выражение неидеально! Оно не поддерживает все детали синтаксиса HTML, например, значения без кавычек, есть способы улучшить его, но давайте не будем его усложнять. Оно продемонстрирует нам проблему.
4545

4646
Кажется, регулярное выражение работает:
4747

@@ -55,9 +55,9 @@ alert( str.match(reg) ); // <a test="<>" href="#">, <b>
5555

5656
Отлично! Нашло длинный `match:<a test="<>" href="#">` и короткий `match:<b>` теги.
5757

58-
Теперь у нас есть рабочее на вид решение. А теперь – демонстрация проблемы.
58+
Теперь когда у нас есть рабочее на вид решение, взглянем на проблему.
5959

60-
## Чёрная дыра бэктрекинга
60+
## Бесконечный возврат
6161

6262
Если запустить пример ниже, то он может подвесить браузер (или другую среду, где выполняется JavaScript):
6363

@@ -122,15 +122,15 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) );
122122
```
123123
2. Затем движок пытается применить квантификатор `pattern:*`, но больше цифр нет, так что звёздочка ничего не даёт.
124124

125-
3. Затем в шаблоне идёт символ конца строки `pattern:$`, а в тексте символ `subject:z`, так что соответствий нет:
125+
3. Далее по шаблону ожидается конец строки `pattern:$`, а в тексте символ `subject:z`, так что соответствий нет:
126126

127127
```
128128
X
129129
\d+........$
130130
(123456789)z
131131
```
132132

133-
4. Так как соответствие не найдено, то "жадный" квантификатор `pattern:+` отступает на один символ (бэктрекинг).
133+
4. Так как соответствие не найдено, то "жадный" квантификатор `pattern:+` отступает на один символ (возврат).
134134

135135
Теперь `\d+` – это все цифры, за исключением последней:
136136
```
@@ -165,7 +165,7 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) );
165165

166166
...увы, всё ещё нет соответствия для `pattern:$`.
167167

168-
Поисковый движок снова должен отступить назад. В общем, бэктрекинг работает так: последний жадный квантификатор понижает количество повторений до тех пор, пока это возможно. Затем понижает предыдущий "жадный" квантификатор и т.д. В нашем случае последний "жадный" квантификатор -- это второй `pattern:\d+`, сокращающий `subject:89` до `subject:8`, а звёздочка берёт `subject:9`:
168+
Поисковый движок снова должен отступить назад. В общем, возврат работает так: последний жадный квантификатор понижает количество повторений до тех пор, пока это возможно. Затем понижает предыдущий "жадный" квантификатор и т.д. В нашем случае последний "жадный" квантификатор -- это второй `pattern:\d+`, сокращающий `subject:89` до `subject:8`, а звёздочка берёт `subject:9`:
169169

170170
```
171171
X
@@ -193,12 +193,12 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) );
193193

194194
Что же делать?
195195

196-
Может нам стоит использовать ленивый режим?
196+
Может нам стоит использовать "ленивый" режим?
197197

198198
К сожалению, нет: если мы заменим `pattern:\d+` на `pattern:\d+?`, то регулярное выражение всё ещё будет "зависать" (осторожно! Может "подвесить" браузер):
199199

200200
```js run
201-
// мееедленно ~доооолго~
201+
// доооолго
202202
alert( '12345678901234567890123456789123456789z'.match(/(\d+?)*$/) );
203203
```
204204

@@ -212,7 +212,7 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+?)*$/) );
212212

213213
В примере выше, когда в строке `subject:<a=b a=b a=b a=b` мы ищем теги по паттерну `pattern:<(\s*\w+=\w+\s*)*>`, происходит то же самое.
214214

215-
В конце строки нет `>`, поэтому совпадение невозможно, но движок не в курсе этого и, отступая, пробует другие комбинации `pattern:(\s*\w+=\w+\s*)`:
215+
В конце строки нет `>`, поэтому совпадение невозможно, но движок не в курсе этого и, отступая, пробует другие комбинации `pattern:(\s*\w+=\w+\s*)`. Таких комбинаций много, поэтому это и занимает много времени.:
216216
```
217217
(a=b a=b a=b) (a=b)
218218
(a=b a=b) (a=b a=b)
@@ -240,30 +240,30 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+?)*$/) );
240240

241241
Никаких "откатов" здесь не нужно.
242242

243-
Другими словами, если мы нашли три пары `name=value`, а `>` после них найти не можем, то не нужно понижать число повторений. Последнего (`>`) точно нет после предыдущих двух пар `name=value` (мы "откатились" на одну пару `name=value`):
243+
Другими словами, если мы нашли три пары `name=value`, а `>` после них найти не можем, то не нужно понижать число повторений. Последнего (`>`) точно нет после предыдущих двух пар `name=value` (мы "откатились" на одну пару `name=value`, там находится эта пара):
244244

245245
```
246246
(name=value) name=value
247247
```
248248

249-
В современных регулярных выражениях для решения этой проблемы придумали сверхжадные ("possessive") квантификаторы, которые вообще не используют бэктрегинг. То есть, они даже проще, чем "жадные" – берут максимальное количество символов и всё. Поиск продолжается дальше. Также есть "атомарные скобочные группы" -- средство, запрещающее перебор внутри скобок.
249+
В современных регулярных выражениях для решения этой проблемы придумали сверхжадные ("possessive") квантификаторы, которые вообще не используют возврат. То есть, они даже проще, чем "жадные" – берут максимальное количество символов и всё. Поиск продолжается дальше. Также есть "атомарные скобочные группы" -- средство, запрещающее перебор внутри скобок.
250250

251251
К сожалению, в JavaScript они все не поддерживаются.
252252

253253
### Предпросмотр в помощь!
254254

255255
Но мы можем исключить бэктрекинг с помощью предпросмотра.
256256

257-
Паттерн, совершающий максимальное количество повторений без "отката", выглядит так: `pattern:(?=(a+))\1`.
257+
Паттерн, совершающий максимальное количество повторений без возврата, выглядит так: `pattern:(?=(a+))\1`.
258258

259259
Другими словами:
260260
- Предпросмотр `pattern:?=` ищет максимальное количество `pattern:a+`, доступных с текущей позиции.
261261
- А затем они "берутся в результат" обратной ссылкой `pattern:\1` (`pattern:\1` соответствует содержимому вторых скобок, т.е. `pattern:a+`)
262262

263-
Откат в этой логике в принципе не предусмотрен, поскольку предпросмотр "откатываться" не умеет. То есть, если предпросмотр нашёл 5 `pattern:a+`, и в результате поиск не удался, то он не будет откатываться на 4 повторения.
263+
Возврат в этой логике в принципе не предусмотрен, поскольку предпросмотр "откатываться" не умеет. То есть, если предпросмотр нашёл 5 `pattern:a+`, и в результате поиск не удался, то он не будет откатываться на 4 повторения.
264264

265265
```smart
266-
Больше о взаимодействиях сверхжадных квантификаторов и предпросмотра вы можете найти в статьях [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) и [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups).
266+
Больше о связи между сверхжадных квантификаторов и предпросмотра вы можете найти в статьях [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) и [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups).
267267
```
268268

269269
Такой метод нивелирует проблему.

0 commit comments

Comments
 (0)