Ответ 1
Простой вопрос. Комплексный ответ!
Да, этот класс регулярного выражения будет повторяться (и молча) сбой Apache/PHP с необработанной ошибкой сегментации из-за!
История:
Семейство регулярных выражений PHP preg_*
использует мощную библиотеку PCRE Филиппа Хейзеля. В этой библиотеке существует определенный класс регулярных выражений, который требует много рекурсивных вызовов для своей внутренней функции match()
, и это использует много пространства стека (и используемое пространство стека прямо пропорционально размеру строки субъекта соответствие). Таким образом, если строка субъекта слишком длинная, произойдет переполнение стека и соответствующая ошибка сегментации. Это описание описано в документации PCRE в конце под заголовком: pcrestack.
PHP Bug 1: PHP устанавливает: pcre.recursion_limit
слишком большой.
Документация PCRE описывает, как избежать ошибки сегментации, ограничивая глубину рекурсии до безопасного значения, примерно равного размеру стека связанного приложения, деленного на 500. Когда глубина рекурсии должным образом ограничена, как рекомендовано, библиотека не генерирует переполнение стека и вместо этого изящно выходит с кодом ошибки. В PHP эта максимальная глубина рекурсии задается с помощью переменной конфигурации pcre.recursion_limit
и (к сожалению) значение по умолчанию равно 100 000. Это значение TOO BIG! Ниже приведена таблица безопасных значений pcre.recursion_limit
для различных размеров исполняемого стека:
Stacksize pcre.recursion_limit
64 MB 134217
32 MB 67108
16 MB 33554
8 MB 16777
4 MB 8388
2 MB 4194
1 MB 2097
512 KB 1048
256 KB 524
Таким образом, для сборки Win32 веб-сервера Apache (httpd.exe
), который имеет (относительно небольшой) размер стека 256 КБ, правильное значение pcre.recursion_limit
должно быть установлено равным 524. Это может быть выполнено с помощью следующая строка кода PHP:
ini_set("pcre.recursion_limit", "524"); // PHP default is 100,000.
Когда этот код добавляется в PHP script, переполнение стека НЕ происходит, а вместо этого генерирует значимый код ошибки. То есть, СЛЕДУЕТ генерировать код ошибки! (Но, к сожалению, из-за другой ошибки PHP, preg_match()
нет.)
Ошибка PHP 2: preg_match()
не возвращает FALSE при ошибке.
Документация PHP для preg_match()
говорит, что она возвращает FALSE при ошибке. К сожалению, PHP версии 5.3.3 и ниже имеют ошибку (# 52732), где preg_match()
НЕ возвращает FALSE
при ошибке (вместо этого возвращает int(0)
, это то же значение, возвращаемое в случае несоответствия). Эта ошибка была исправлена в PHP версии 5.3.4.
Решение:
Предполагая, что вы продолжите использовать WAMP 2.0 (с PHP 5.3.0), решение должно учитывать оба вышеупомянутых ошибки. Вот что я бы рекомендовал:
- Необходимо уменьшить
pcre.recursion_limit
до безопасного значения: 524. - Необходимо явно проверять наличие ошибки PCRE, когда
preg_match()
возвращает что-либо, кромеint(1)
. - Если
preg_match()
возвращаетint(1)
, то совпадение было успешным. - Если
preg_match()
возвращаетint(0)
, то совпадение было либо неудачным, либо произошла ошибка.
Ниже приведена измененная версия вашего script (предназначенная для запуска из командной строки), которая определяет длину строки темы, которая приводит к ошибке ограничения рекурсии:
<?php
// This test script is designed to be run from the command line.
// It measures the subject string length that results in a
// PREG_RECURSION_LIMIT_ERROR error in the preg_match() function.
echo("Entering TEST.PHP...\n");
// Set and display pcre.recursion_limit. (set to stacksize / 500).
// Under Win32 httpd.exe has a stack = 256KB and 8MB for php.exe.
//ini_set("pcre.recursion_limit", "524"); // Stacksize = 256KB.
ini_set("pcre.recursion_limit", "16777"); // Stacksize = 8MB.
echo(sprintf("PCRE pcre.recursion_limit is set to %s\n",
ini_get("pcre.recursion_limit")));
function parseAPIResults($results){
$pattern = "/\[(.|\n)+\]/";
$resultsArray = preg_match($pattern, $results, $matches);
if ($resultsArray === 1) {
$msg = 'Successful match.';
} else {
// Either an unsuccessful match, or a PCRE error occurred.
$pcre_err = preg_last_error(); // PHP 5.2 and above.
if ($pcre_err === PREG_NO_ERROR) {
$msg = 'Successful non-match.';
} else {
// preg_match error!
switch ($pcre_err) {
case PREG_INTERNAL_ERROR:
$msg = 'PREG_INTERNAL_ERROR';
break;
case PREG_BACKTRACK_LIMIT_ERROR:
$msg = 'PREG_BACKTRACK_LIMIT_ERROR';
break;
case PREG_RECURSION_LIMIT_ERROR:
$msg = 'PREG_RECURSION_LIMIT_ERROR';
break;
case PREG_BAD_UTF8_ERROR:
$msg = 'PREG_BAD_UTF8_ERROR';
break;
case PREG_BAD_UTF8_OFFSET_ERROR:
$msg = 'PREG_BAD_UTF8_OFFSET_ERROR';
break;
default:
$msg = 'Unrecognized PREG error';
break;
}
}
}
return($msg);
}
// Build a matching test string of increasing size.
function buildTestString() {
static $content = "";
$content .= "A";
return '['. $content .']';
}
// Find subject string length that results in error.
for (;;) { // Infinite loop. Break out.
$str = buildTestString();
$msg = parseAPIResults($str);
printf("Length =%10d\r", strlen($str));
if ($msg !== 'Successful match.') break;
}
echo(sprintf("\nPCRE_ERROR = \"%s\" at subject string length = %d\n",
$msg, strlen($str)));
echo("Exiting TEST.PHP...");
?>
Когда вы запускаете этот script, он обеспечивает непрерывное считывание текущей длины строки темы. Если значение pcre.recursion_limit
остается слишком высоким по умолчанию, это позволяет вам измерять длину строки, которая приводит к сбою исполняемого файла.
Комментарии:
- Прежде чем исследовать ответ на этот вопрос, я не знал о ошибке PHP, где
preg_match()
не удалось вернутьFALSE
, когда в библиотеке PCRE возникла ошибка. Эта ошибка, безусловно, ставит под вопрос LOT кода, который используетpreg_match
! (Я, конечно, собираюсь сделать инвентаризацию своего собственного PHP-кода.) - В Windows, исполняемый файл веб-сервера Apache (
httpd.exe
) построен со стекизацией 256 КБ. Исполняемая строка командной строки PHP (php.exe
) построена с помощью стекирования 8 МБ. Безопасное значение дляpcre.recursion_limit
должно быть установлено в соответствии с исполняемым файлом, в котором выполняется script (524 и 16777 соответственно). - В системах * nix, веб-сервер Apache и исполняемые файлы командной строки, как правило, построены с помощью стекирования 8 МБ, поэтому эта проблема не встречается так часто.
- Разработчики PHP должны установить безопасное значение по умолчанию
pcre.recursion_limit
. - Разработчики PHP должны применить исправление
preg_match()
к PHP версии 5.2. - Стекирование исполняемого файла Windows можно вручную изменить с помощью CFF Explorerбесплатная программа. Вы можете использовать эту программу для увеличения стекирования исполняемого файла Apache
httpd.exe
. (Это работает под XP, но Vista и Win7 могут жаловаться.)