Простой вопрос. Комплексный ответ!
Да, этот класс регулярных выражений будет повторять (и молча) сбой Apache / PHP с необработанной ошибкой сегментации из-за переполнения стека!
Справочная информация:
Семейство функций регулярных выражений PHP preg_*
использует мощную библиотеку PCRE от Philip Hazel. С этой библиотекой существует определенный класс регулярных выражений, который требует много рекурсивных вызовов к своей внутренней функции match()
, и это занимает много места в стеке (и используемое пространство в стеке прямо пропорционально размеру строки объекта). подбирается). Таким образом, если строка объекта слишком длинная, произойдет переполнение стека и соответствующая ошибка сегментации. Это поведение описано в документации PCRE в конце раздела под заголовком: pcrestack .
Ошибка PHP 1: PHP устанавливает: pcre.recursion_limit
слишком большой.
Документация PCRE описывает, как избежать ошибки сегментации переполнения стека, ограничивая глубину рекурсии безопасным значением, примерно равным размеру стека связанного приложения, деленному на 500. Когда глубина рекурсии должным образом ограничена, как рекомендуется, библиотека не генерирует переполнение стека и вместо этого корректно завершается с кодом ошибки. В PHP эта максимальная глубина рекурсии указывается с помощью переменной конфигурации pcre.recursion_limit
, и (к сожалению) значение по умолчанию равно 100 000. Это значение СЛИШКОМ БОЛЬШОЕ! Вот таблица безопасных значений 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, переполнение стека НЕ происходит, а генерирует значимый код ошибки. То есть СЛЕДУЕТ генерировать код ошибки! (Но, к сожалению, из-за другой ошибки 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)
, то совпадение либо не удалось, либо произошла ошибка.
Вот модифицированная версия вашего скрипта (предназначенная для запуска из командной строки), которая определяет длину строки темы, которая приводит к ошибке предела рекурсии:
<?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...");
?>
Когда вы запускаете этот скрипт, он обеспечивает непрерывное считывание текущей длины строки темы. Если pcre.recursion_limit
оставить слишком высокое значение по умолчанию, это позволит вам измерить длину строки, которая вызывает сбой исполняемого файла.
Комментарии:
- До изучения ответа на этот вопрос я не знал об ошибке PHP, когда
preg_match()
не возвращает FALSE
, когда в библиотеке PCRE возникает ошибка. Эта ошибка, безусловно, ставит под сомнение много кода, который использует preg_match
! (Я определенно собираюсь провести инвентаризацию моего собственного кода PHP.) - В Windows исполняемый файл веб-сервера Apache (
httpd.exe
) имеет размер стека 256 КБ. Исполняемый файл командной строки PHP (php.exe
) имеет размер стека 8 МБ. Безопасное значение для pcre.recursion_limit
должно быть установлено в соответствии с исполняемым файлом, под которым выполняется скрипт (524 и 16777 соответственно).
- В системах * nix исполняемые файлы веб-сервера Apache и командной строки обычно имеют размер стека 8 МБ, поэтому такая проблема встречается не так часто.
- Разработчики PHP должны установить безопасное значение по умолчанию
pcre.recursion_limit
.
- Разработчики PHP должны применить исправление
preg_match()
к PHP версии 5.2.
- Размер стека исполняемого файла Windows можно изменить вручную с помощью бесплатной программы CFF Explorer . Вы можете использовать эту программу, чтобы увеличить размер стека исполняемого файла Apache
httpd.exe
. (Это работает под XP, но Vista и Win7 могут жаловаться.)