RegExp в функции preg_match, возвращающей ошибку браузера - PullRequest
15 голосов
/ 01 октября 2011

Следующая функция разрывается с регулярным выражением, которое я предоставил в переменной $ pattern. Если я изменю регулярное выражение, я в порядке, так что я думаю, что это проблема. Я не вижу проблемы, и я не получаю стандартную ошибку PHP, даже если они включены.

function parseAPIResults($results){
//Takes results from getAPIResults, returns array.

    $pattern = '/\[(.|\n)+\]/';
    $resultsArray = preg_match($pattern, $results, $matches);

}

Firefox 6: соединение было сброшено

Chrome 14: ошибка 101 (net :: ERR_CONNECTION_RESET): соединение было сброс.

IE 8: Internet Explorer не может отобразить веб-страницу

ОБНОВЛЕНИЕ:
Apache / PHP может быть сбой. Вот журнал ошибок Apache, когда я запускаю скрипт:

[Сб. 01.10 11:41:40 2011] [примечание] Родитель: дочерний процесс завершен с статус 255 - перезапуск.
[Сб. 01.10 11:41:40 2011] [извещение] Apache / 2.2.11 (Win32) PHP / 5.3.0 настроен - возобновление нормального операции

Запуск WAMP 2.0 в Windows 7.

Ответы [ 4 ]

53 голосов
/ 02 октября 2011

Простой вопрос. Комплексный ответ!

Да, этот класс регулярных выражений будет повторять (и молча) сбой 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 могут жаловаться.)
2 голосов
/ 17 ноября 2012

Я столкнулся с той же проблемой.Большое спасибо за ответ, опубликованный ridgerunner.

Хотя полезно знать, почему происходит сбой php, для меня это на самом деле не решает проблему.Чтобы решить эту проблему, мне нужно настроить свое регулярное выражение, чтобы сэкономить память, чтобы php больше не зависал.

Так что вопрос в том, как изменить регулярное выражение. Ссылка на руководство PCRE , размещенное выше, уже описывает решение для примера регулярного выражения, которое очень похоже на ваше.

Так как исправить это регулярное выражение?Во-первых, вы говорите, что хотите соответствовать «а. Или перевод строки».Обратите внимание, что "."это специальный символ в регулярном выражении, который соответствует не только точке, но и любому символу, поэтому вам нужно избегать этого.(Я надеюсь, что я не понял вас неправильно, и это было задумано.)

$pattern = '/\[(\.|\n)+\]/';

Далее мы можем скопировать квантификатор в скобках:

$pattern = '/\[(\.+|\n+)+\]/';

Это не изменитсмысл выражения.Теперь мы используем собственнические квантификаторы вместо обычных:

$pattern = '/\[(\.++|\n++)++\]/';

Так что это должно иметь то же значение, что и исходное регулярное выражение, но работать в php, не прерывая его.Зачем?Притяжательные квантификаторы «съедают» персонажей и не позволяют отказаться.Следовательно, PCRE не должен использовать рекурсию, и стек не будет переполнен.Использование их в квадратных скобках представляется хорошей идеей, поскольку нам часто не требуется количественная оценка альтернативы.

Подводя итог, наилучшая практика выглядит следующим образом:

  • useпритяжательные квантификаторы, где это возможно.Это означает: ++, * +,? + {} + Вместо +, *,?, {}.
  • перемещать квантификаторы внутри альтернативных скобок, где это возможно

СледующийПо этим правилам мне удалось решить собственную проблему, и я надеюсь, что это поможет кому-то еще.

1 голос
/ 07 декабря 2012

У меня была такая же проблема, и вам нужно изменить шаблон на что-то вроде

$pattern = '|/your pattern/|s';

's' в конце означает, что строка рассматривается как одна строка.

0 голосов
/ 01 октября 2011

preg_match возвращает количество совпадений, найденных для шаблона.Когда у вас есть совпадение, это приводит к фатальной ошибке в php (например, print_r(1) вызывает ошибку).print_r (0) (когда вы изменяете шаблон и у вас нет совпадений) не делает и просто печатает 0.

Вы хотите print_r($matches)

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

...