PHP

индекс
206,76

Тест PHP-функции preg_match

Если PHP-функции preg_match в качестве исходной строки передать строку состоящую из 100000 (ста тысяч) или более силволов, то она вернёт неверный результат. Точнее результатом в этом случае всегда будет false.

Проверяется очень просто.
$max_n=100010;
$n=99900;
$tmp_str=str_repeat('b',$n);
$go=true;
while (($go)&&($n<=$max_n)) {
$n++;
$tmp_str.='b';
$str='a'.$tmp_str.'a';
$go=preg_match('/a.*?a/sim', $str);
if (!$go) {
echo 'error on length '.strlen($str);
die;
}
}
echo ' ok';

Результатом работы данного скрипта будет такое сообщение
error on length 100000

Что говорит о том, что preg_match не осилил строку длинной сто тысяч символов. При более длинных строках результат будет тот же. Хотя мне не до конца понятно почему функция спотыкается именно на строке длинной в 100000 байт. Видимо это особенность реализации.

Проверял на PHP Version 5.2.6-1.

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

Всё выше сказанное также справедливо и для PHP-функции preg_match_all.

UPD Решение проблемы из коментария l2k
ini_set(«pcre.backtrack_limit»,10000000);
Таким образом можно изменить лимит обратных ссылок PCRE.
Причём изменяемость директивы PHP_INI_ALL, что означает что она может быть установлена в любом месте.
+23
29 апреля 2009, 17:09
47

комментарии (42)

НЛО прилетело и опубликовало эту надпись здесь
0
l2k #
Я парсил темплейты. Размеры были более 150 кб.
+5
panandy #
А я делал парсеры к сайтам на ASP.NET и к его ajax. Так там аякс ответы бывают по 250-350 кбайт, причем 40 кбайт из них — hidden поле VIEWSTATE которое хранит состояние контролов и тягается постоянно по всем страницам!
Отличный ajax! :)
+3
kurokikaze #
«Блямс!», сказала бензопила — «А-а-а, блин!», сказали мужики.
+6
vitali #
… «А-а-а, блин!», сказали уже не мужики
+1
Scorpil #
Надо же, незнал. Спасибо.
+3
Holden #
Я тоже не знал. Но когда из-за этой ошибки не работал проект, то потратил не мало времени и сил, чтобы обнаружить это. Конечно, как всегда «никто ничего не трогал». Но особенно удручало, что «всё должно работать».

Поэтому теперь помню, что не все функции работают так как ожидается разработчиком.
0
jeje #
Поведайте же нам. Что вы передавали туда? :)
0
dewil #
html страницы наверно?
–5
jeje #
Размах хороший, но стоит по немного проверять, а то так можно и длину string переплюнуть :)
0
ksn #
А как же unit-тесты?
0
Karsonito #
Веселое решение. Давайте теперь на каждую функцию php будем вешать unit test!
0
ksn #
Ну писать на каждую функцию юнит-тест действительно бред. Писать их надо на логически завершенные части проекта, как мне кажется. Тогда бы автор не потратил много времени и сил на поиск неисправности. Локализовать было бы легче.
+1
Karsonito #
Дело в том, что даже при наличии тестов такие баги будут жить очень долго.
Модуль не прошел тест? — ищем ошибки в коде. До посинения.
Локализировали проблему — пишем локальный подтест. Причем нет гарантии, что тест будет проверять данные объемом 100кб (кто мог подумать...). Тест проходит успешно. И снова пошли за миноискателем.
Тесты — не панацея.
0
ksn #
Да, действительно. По крайней мере я бы не стал писать проверочные данные объёмом больше 100КБ. Действительно кто бы мог подумать. Спасибо.
+1
zerkms #
сразу, возможно — не стали бы (хотя практики подразумевают тестирование как рядовых входных данных, так и пограничных).
но после появления такой проблемы зафиксировать её в тестах просто категорически необходимо.
0
dewil #
А какова она? Разве не пока выделенная память закончится?
0
Ueasley #
Что за длина string?
0
jeje #
Это был сарказм.
+21
l2k #
ini_set(«pcre.backtrack_limit»,10000000);
вас спасёт
+1
XEK #
Мне кажется, или за 100 кб html-кода в одном файле надо бить не файл, а разработчика?
+1
skazkin #
почему это ещё? А как же например парсинг какой-нибудь выгрузки БД?

Да и 100кб html — тоже нормальная цифра для каталогов или ещё чего-нибудь
+3
dewil #
БД лучше стараться парсить средствами БД.
+1
skazkin #
Спорю что-ли?)

Я про реальные случаи, с которыми приходится сталкиваться.
+1
dewil #
недавно хотел страницу в yahoo.pipes пропустить.
яха меня послала. страница 500 кб, а допустимо <=200 кб.

так и подумал, что убивать надо таких.
там новости одной лентой за 3 года.
ни RSS, ни страниц.
0
l2k #
Вам наверное стоит спросить здесь.

А использовать парсер можно, например для подсветки C++/Java/PHP программ.
0
skazkin #
как это обьяснить?:)

  1. Test failed
  2. Test passed!
  3. Test passed!
  4. Test passed!
  5. Test passed!
  6. Test passed!
  7. <?
  8. $i      =   200000;
  9. $x      =   "b".str_repeat("a",$i)."b";
  10. if(preg_match("/^b.*?b$/",$x)){
  11.     echo "Test passed!<br>";
  12. }
  13. else{
  14.     echo "Test failed<br>";
  15. }
  16.  
  17. $i      =   200;
  18. $x      =   "b".str_repeat("a",$i)."b";
  19.  
  20. if(preg_match("/^b.*?b$/",$x)){
  21.     echo "Test passed!<br>";
  22. }
  23. else{
  24.     echo "Test failed<br>";
  25. }
  26.  
  27. $i      =   200000;
  28. $x      =   "b".str_repeat("a",$i)."b";
  29. if(preg_match("/^ba*b$/",$x)){
  30.     echo "Test passed!<br>";
  31. }
  32. else{
  33.     echo "Test failed<br>";
  34. }
  35.  
  36. $i      =   200;
  37. $x      =   "b".str_repeat("a",$i)."b";
  38.  
  39. if(preg_match("/^ba*b$/",$x)){
  40.     echo "Test passed!<br>";
  41. }
  42. else{
  43.     echo "Test failed<br>";
  44. }
  45.  
  46.  
  47. $i      =   200000;
  48. $x      =   "b".str_repeat("a",$i)."b";
  49. if(preg_match("/^b.*b$/",$x)){
  50.     echo "Test passed!<br>";
  51. }
  52. else{
  53.     echo "Test failed<br>";
  54. }
  55.  
  56. $i      =   200;
  57. $x      =   "b".str_repeat("a",$i)."b";
  58.  
  59. if(preg_match("/^b.*b$/",$x)){
  60.     echo "Test passed!<br>";
  61. }
  62. else{
  63.     echo "Test failed<br>";
  64. }
  65.  
  66. highlight_file(__FILE__);
  67.  
  68.  
  69. ?>
  70.  
0
l2k #
Test passed!
Test passed!
Test passed!
Test passed!
Test passed!
Test passed!
<?
ini_set("pcre.backtrack_limit",10000000);
0
skazkin #
Уже прочитал, пасип) Дефолт-лимит — 100 000
0
torkve #
foreach (array(200000, 200, 200000, 200) as $i) {

}
0
Fr3nzy #
Для парсинга есть еще такие замечательные вещи, как phpQuery ) правда, в код я не заглядывал, если честно.
0
Fr3nzy #
* и поэтому не могу сказать, как там дела обстоят с preg'ами. ereg'ами )
+3
TiGR #
Во-первых, как вам уже указали выше, это лечимо.

Во-вторых, проблема не в длине строке, а в самом выражении. Точнее в глубине рекурсии.

В своё время нарвался на эту проблему, пришлось переписывать шаблоны.
+1
alexxxst #
php.ini:

[Pcre]
pcre.recursion_limit=700000
pcre.backtrack_limit=700000

Уже сталкавался с таким, мне пока для шаблонов хватило столько :)
+1
soloweb #
Вообще лучше не применять preg_match и подобные ей веши для прсинга больших данных.
Для больщих данных лучше что-то специфическое или свое решение.
А так для провекри правельности введенных данных с форм к примеру вполне но не более.
0
Novikov #
Вот я помучался с такой штукой, когда писал для себя один скрипт.
0
silent_pro #
Сталкивался с подобной проблемой, но вероятно не в этом было дело, задача была собирать ссылки на внутренние страницы, сайты были больших размеров, на серваке после сбора 1000 ссылок, выходила 502 ошибка, но на локалхосте все работало, подскажите в чем могла быть проблема?
+3
mikkie #
Не стоит также забывать о других ограничениях описанных в www.pcre.org/pcre.txt раздел LIMITATIONS
0
hlomzik #
За такую регулярку на такой длине надо наказывать. Сильно.

/a[^a]a/
0
hlomzik #
Собственно, я хотел сказать, что pcre.backtrack_limit кагбе говорит, что проблема не в php или pcre, а в вашей регулярке.
Я не могу представить регулярку, для которой нужно менять этот лимит.
+3
zerkms #
заголовок провокационный: это не тест конкретной функции пхп, а тест pcre, который один для всех.
0
Kirpichtion #
Угу, и я на него купился

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.