Регулярное выражение Javascript зависает (с использованием v8)

Я использую это регулярное выражение, чтобы получить содержимое тега в файле.

var regex = new RegExp("<tag:main>((?:.|\\s)*)</tag:main>");

Это приводит к тому, что двигатель v8 зависает на неопределенный срок.

Теперь, если я использую new RegExp("<tag:main>([\s\S]*)</tag:main>"), все хорошо.

Кто-нибудь знает, почему первый занимает слишком много времени?


person Heinrich Lee Yu    schedule 09.03.2010    source источник
comment
создание регулярного выражения зависает или его применение? Линия, которую вы разместили, отлично работает для меня   -  person cobbal    schedule 09.03.2010
comment
Создание не зависает, только используя его через test или match. использование длинных строк   -  person Heinrich Lee Yu    schedule 09.03.2010
comment
Вы пробовали нежадный матч? var regex = new RegExp("<tag:main>((?:.|\\s)*?)</tag:main>");. Ваше регулярное выражение может вызвать проблемы, если в документе есть несколько элементов тега.   -  person Andy E    schedule 09.03.2010


Ответы (3)


Это приводит к катастрофическим откатам длинных последовательностей пробелов, которые появляются после последнего закрывающего тега </tag:main>. Рассмотрим случай, когда тематическая строка заканчивается 100 пробелами. Сначала он сопоставляет их всех с . слева от чередования. Это не удается, потому что нет закрывающего тега, поэтому вместо этого он пытается сопоставить последний символ с \s. Это тоже не удается, поэтому он пытается сопоставить предпоследний пробел как \s, а последний пробел как .. Это не удается (по-прежнему нет закрывающего тега), поэтому он пытается использовать последний пробел как \s. Когда это не удается, он сопоставляет предпоследний пробел как \s и пытается всеми 4 способами сопоставить последние два пробела. Когда это не удается, он пытается использовать предпоследний пробел как \s и все 8 способов для последних 3 пробелов. Затем 16, 32 и т. д. Вселенная заканчивается, не дойдя до сотого предпоследнего места.

Различные виртуальные машины по-разному реагируют на совпадения регулярных выражений, которые занимают вечность из-за катастрофического возврата. Некоторые просто сообщат об отсутствии совпадений. В V8 это похоже на написание любого другого бесконечного или почти бесконечного цикла.

Использование нежадного * будет делать то, что вы хотите (вы хотите остановиться на первом </tag:main>, а не на последнем), но все равно приведет к катастрофическому возврату для длинных строк пробелов, где отсутствует закрывающая последовательность.

Убедившись, что одни и те же символы во внутренней скобке не могут соответствовать обеим сторонам чередования, мы уменьшим проблему с экспоненциальной до линейной по длине строки. Используйте класс символов вместо чередования или поставьте \n справа от полосы чередования. \n не пересекается с ., поэтому, если вы нажмете длинную последовательность пробелов, механизм регулярных выражений не будет пробовать все комбинации влево-вправо-влево и т. д. перед завершением.

person Erik Corry    schedule 09.03.2010
comment
Хорошее объяснение. Вы случайно не знаете, включает ли точка \r? - person Martin Smith; 09.03.2010
comment
@Martin: в JavaScript . эквивалентно [^\r\n\u2028\u2029] - person Alan Moore; 09.03.2010

Я предполагаю, что это катастрофическое отставание.

Я думаю, что часть проблемы может заключаться в том, что точка и \s не исключают друг друга.

Если я изменю ваше выражение на

<tag:main>((?:.|[\r\n])*)</tag:main>

и запустите его в отладчике Regex Buddy, он выйдет из строя намного быстрее, если тестовая строка не совпадает.

person Martin Smith    schedule 09.03.2010
comment
.|\s соответствует всем символам. Так как . соответствует всем символам, кроме новой строки. - person Heinrich Lee Yu; 09.03.2010
comment
Я не думаю, что это будет делать. Я вставил ваше регулярное выражение в RegexBuddy и вставил его дерево комментариев в свой пост. - person Martin Smith; 09.03.2010
comment
Вы должны удалить лишние \ перед вставкой в ​​RegexBuddy. \\ используется, потому что это строка javascript, переданная конструктору RegExp. - person Heinrich Lee Yu; 09.03.2010
comment
Упс! Если вы сделаете его ленивым, а не жадным, остановит ли это проблему? ‹tag:main›((?:.|\s)*?)‹/tag:main› - person Martin Smith; 09.03.2010
comment
Я полностью переписал свой ответ сейчас! - person Martin Smith; 09.03.2010

Вместо (?:.|\s)* вы можете использовать [^]* для соответствия любому символу, включая различные формы новой строки.

Здесь нет чередования, поэтому нет риска катастрофического возврата.

person Jim Shark    schedule 07.02.2015