Эта тема на forum.dklab.ru


gri: Предел для preg_match. Может ли повиснуть?
Есть у меня один класс, активно использующий preg_match, причем выражение PCRE генерируется по ходу выполнения программы, и к концу скрипта длина этого выражения исчисляется несколькими сотнями символов и несколькими десятками карманов.
И вот когда скрипт отвисел положенные ему 30 секунд, я обнаружил, что застрял он на линиии с preg_match...
if(preg_match( $this->Expr['mask'], $this->code, $P, PREG_OFFSET_CAPTURE, $offset))
Вставил везде в циклах и рекурсиях счетчик, нашел на каком витке виснет путем if(counter >= max_counter) exit();. Ставил эту строчку перед и после многострадального preg_match и окончательно убедился, что висит именно он.
Чтоб не быть голословным прилагаю сам класс c тестовым скриптом. Запускать test_SRC_segment.php под PHP5 apache на удаленном сайте.
В смысле не на домашнем сервере, а то до удаленного адреса (в данном случае http://forum.dklub.ru) будет не добраться.
Впрочем может зря я на preg_match грешу, и если кто-нибудь найдет в чем баг - буду очень благодарен.
gri:
Почему то файл два раза добавился...
Дмитрий Котеров:
К сожалению, тут очень редко занимаются поиском багов в чужих скриптах.
Могу порекомендовать Вам вывести это регулярное выражение куда-нибудь, а потом - тестировать его отдельно, если уж оно такое сложное.
gri:
Вот собственно говоря суть проблемы:

<?php
ini_set('max_execution_time', 10);
// Получаем код удаленной страницы
$code = file_get_contents('http://forum.dklab.ru');
/* ВЫРАЖЕНИЯ PCRE */
// Выражение открывающего тега table
$open = '(< \s* table [^>]*? > .*?)';
// Выражение закрывающего тега table
$close = '(< \s* / \s* table \s* > .*?)';
// Выражение, захватывающее возможные втречи открывающего тега.
// Ставится между двумя закрывающими тегами, означает что не должно быть открывающего.
$no_open = '((?:< \s* table \s* > .*?)?)';
// Аналогично - не должно быть закрывающего.
$no_close = '((?:< \s* / \s* table \s* > .*?)?)';

// Правильное выражение. Ему соответсвует часть кода сайта.
/**/
$expr = '| '
.$open
.$no_close
.$open
.$no_close
.$open
.$close
.$open
.$close
.$no_open
.$close
.$open
.$close
.'|ixs';
$exe_if = '!$P[2][0] && !$P[4][0] && !$P[9][0]';
/**/
// Неправильное выражение. Preg_match вместо того, чтобы вернуть 'false' виснет.
/*/
$expr = '| '
.$close
.$no_open
.$close
.$no_open
.$close
.$open
.$no_close
.$open
.$close
.$open
.$close
.$no_open
.$close
.$open
.$close
.'|ixs';
$exe_if = '!$P[2][0] && !$P[4][0] && !$P[7][0] && !$P[12][0]';
/**/
$res = ''; //строка результата, где собирается подходящий фрагмент кода
$offset = 0; //текущая позиция поиска
$flag = true; //флаг выхода из цикла
$counter = 0; //счетчик
$max_counter = 10; //макс значение по которому производится выхол из цикла

do {
$counter++;
if(preg_match($expr, $code, $P, PREG_OFFSET_CAPTURE, $offset)) { // Вот здесь и виснем!
eval ('$exe = '.$exe_if.' ;');
// Если "паразитные" карманы пусты
if($exe) {
// Значит все в порядке
$flag = false;
// Считываем содержимое карманов в строку $ret
for($i = 1; $i < count($P); $i++)
$res .= "[$i] --> ".$P[$i][0];
}
// "Паразитные" карманы не пусты - сдвигаем позицию поиска.
else $offset = $P[1][1]+1;
echo "Pocket[1][1] = ".$P[1][1]."<br>";
}
echo "counter = $counter; offset = $offset; flag = $flag <br>";
// Повторяем, пока не получим необходимый участок кода или пока счетчик не зашкалит
} while ($flag && $counter < $max_counter);
// Запишем результат поиска в лог файл
$f = fopen(__FILE__.'.log', 'w');
fwrite($f, $res);
fclose($f);
?>

Если закомнтить одно выражение и раскоментить другое, то скрипт висит. Интересно почему?
Дмитрий Котеров:
$no_open = '((?:< \s* table \s* > .*?)?)';
Непонятно, почему оно у Вас называется no_open. По смыслу-то как раз должно быть open - проверяет, что дальше идет <table> и что угодно, либо пусто.

eval ('$exe = '.$exe_if.' ;');
Уберите от греха! Ну не должно быть eval-ов в нормальных скриптах.
gri:
Непонятно, почему оно у Вас называется no_open
Этого <table> не должено быть в итоге.
$exe_if = '!$P[2][0] && !$P[4][0] && !$P[7][0] && !$P[12][0]';  
eval ('$exe = '.$exe_if.' ;');
    if($exe) {
В этих строках это проверяется.
Уберите от греха! Ну не должно быть eval-ов в нормальных скриптах.
Это всего лишь скрипт для проверки регулярного выражения. В самом классе что выражение, что строка для if формируютя по ходу выполнения.
Можете подсказать другой путь, помимо моего eval-а?
И все же интересно - почему регулярное выражение вида ' (< \s* table [^>]*? > .*?) ((?:< \s* table \s* > .*?)?)' вызывает зависание preg_match если ему не соответсвует участок строки? Оно же не содержит позитивных просмотров назад например.
Дмитрий Котеров:
Да я не уверен, что именно оно вызывает зависание.
Попробуйте локализовать ошибку, убрать всякие циклы оттуда и другой мусор.
gri:
Убрав всю шелуху и проведя серию экспериментов, я пришел к выводу, что preg_match в данном случае не виснет, а просто работает оооочееень меедлееенноо.
Если для моего выражения существует нужный фрагмент строки (участок кода), то preg_match находит у меня его примерно за 1 секунду. Если нужного фрагмента нет, то работа ф-ции занимает более 7-ми секунд. У меня в скрипте preg_match находитсяв цикле и вполне может на некотрое количество итераций потратить время большее max_execution_time.
Значит надо делать подругому.
Дмитрий Котеров Спасибо за толику уделенного внимания.

Вопрос решен, тему можно закрыть.
Дмитрий Котеров:
Кстати, о случаях, когда скорость работы PCRE-выражений падает экспоненциально по длине входной строки (когда совпадения нет), можно почитать в книге Фридла "Mastering Regular Expressions". Эффект довольно известный.

Тема закрыта.

Эта тема на forum.dklab.ru