Регулярные выражения мощны, но неаккуратное использование в production — будь то валидация форм в SPA, парсинг данных на Node.js бэкенде или обработка логов — может привести к зависанию сервера. Самая частая ошибка — catastrophic backtracking, когда движок перебирает экспоненциальное количество комбинаций из-за вложенных квантификаторов.
Как возникает катастрофический возврат
Классический пример — валидация email:
/^([a-z]+)+@domain\.com$/
Пускаем строку
aaaaaaaaaaaaaaaaaaaa!@domain.com. Движок не находит совпадение после ! и начинает перебирать варианты группировки внутри (a+)+: 20 символов, потом 19+1, 18+2... Получается ~2^20 путей. На 30 символах время выполнения растёт с миллисекунд до минут.Необходимы три условия:
* Вложенные квантификаторы —
+ внутри + или * внутри *.* Отсутствие фиксированного символа, прерывающего перебор.
* Неудачное совпадение в конце — движок возвращается к предыдущим вариантам.
Типичные паттерны-ловушки
Часто встречаются в коде парсинга HTML или валидации чисел:
/<(.+)>.+<\/\1>/ — разбор тегов
/^(-?\d+(\.\d+)?)+$/ — валидация чисел
Оба дают экспоненту на длинных строках с ошибкой. В боевом API-клиенте или SDK такое приведёт к зависанию при обработке вредоносного ввода.
Как защититься
Первый способ — эмулировать atomic groups через lookahead:
/(?=(a+))\1/ — захват без возврата
Второй — конкретизировать границы. Вместо
.* используйте точные классы. Для email:/^([a-z\d._%+-]+@[a-z\d.-]+\.[a-z]{2,})$/iТретий — timeout. На Node.js используйте библиотеку
re2 — она гарантирует O(n) и не залипает. Или оборачивайте регулярку в Web Worker с таймером.Цифры и инструменты
На строке из 30 символов простая регулярка отрабатывает ~0.01ms, катастрофическая — >5000ms, рост экспоненциальный. Для дебага используйте regex101.com — там видно количество шагов. В Node.js можно увеличить
--stack-size, но проще переписать паттерн.Вывод:
Избегайте вложенных квантификаторов на одинаковых группах символов — это предотвратит catastrophic backtracking и спасёт CPU вашего production.