15

以下函数与我在 $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 错误日志:

[2011 年 10 月 1 日星期六 11:41:40] [通知] 父进程:子进程以状态 255 退出 -- 正在重新启动。
[2011 年 10 月 1 日星期六 11:41:40] [通知] Apache/2.2.11 (Win32) PHP/5.3.0 已配置 -- 恢复正常操作

在 Windows 7 上运行 WAMP 2.0。

4

4 回答 4

54

简单的问题。复杂的答案!

是的,这类正则表达式会重复地(并且默默地)使 Apache/PHP 崩溃,并由于堆栈溢出而导致未处理的分段错误!

背景:

PHPpreg_*系列的正则表达式函数使用 Philip Hazel 强大的PCRE 库。使用这个库,有一类正则表达式需要对其内部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

因此,对于 Apache webserver ( httpd.exe) 的 Win32 版本,其堆栈大小(相对较小)为 256KB,正确的值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。
  • 每当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保留太高的默认值,这允许您测量导致可执行文件崩溃的字符串长度。

注释:

  • 在调查这个问题的答案之前,我不知道PCRE 库中发生错误时preg_match()无法返回的 PHP 错误。FALSE这个错误肯定会质疑很多使用preg_match! (我当然会清点我自己的 PHP 代码。)
  • 在 Windows 下,Apache webserver 可执行文件 ( httpd.exe) 的堆栈大小为 256KB。PHP 命令行可执行文件 ( php.exe) 的堆栈大小为 8MB。安全值pcre.recursion_limit应根据脚本运行的可执行文件(分别为 524 和 16777)设置。
  • 在 *nix 系统下,Apache webserver 和命令行可执行文件通常都使用 8MB 的堆栈大小构建,因此不会经常遇到这个问题。
  • PHP 开发人员应将默认值设置pcre.recursion_limit为安全值。
  • PHP 开发人员应将preg_match()错误修复应用于 PHP 5.2 版。
  • 可以使用CFF Explorer免费软件程序手动修改 Windows 可执行文件的堆栈大小。您可以使用此程序来增加 Apachehttpd.exe可执行文件的堆栈大小。(这在 XP 下有效,但 Vista 和 Win7 可能会抱怨。)
于 2011-10-02T17:23:35.723 回答
2

我遇到了同样的问题。非常感谢 ridgerunner 发布的答案。

虽然了解 php 崩溃的原因很有帮助,但对我来说,这并不能真正解决问题。为了解决这个问题,我需要调整我的正则表达式以节省内存,这样 php 就不会再崩溃了。

所以问题是如何更改正则表达式。上面发布的 PCRE 手册的链接已经描述了一个与您的非常相似的示例正则表达式的解决方案。

那么如何修复你的正则表达式呢?首先,您说要匹配“a . or a newline”。注意 ”。” 是正则表达式中的一个特殊字符,它不仅匹配点,还匹配任何字符,因此您需要对其进行转义。(我希望我在这里没有误会你,这是故意的。)

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

接下来,我们可以复制括号内的量词:

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

这不会改变表达式的含义。现在我们使用所有格量词而不是普通的量词:

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

所以这应该与你原来的正则表达式具有相同的含义,但在 php 中工作而不会崩溃。为什么?所有格量词“吃掉”字符并且不允许回溯。因此,PCRE 不必使用递归,堆栈也不会溢出。在括号内使用它们似乎是一个好主意,因为我们不需要经常量化替代方案。

总而言之,最佳实践似乎是:

  • 尽可能使用所有格量词。这意味着:++、*+、?+ {}+ 而不是 +、*、?、{}。
  • 尽可能将量词移到替代括号内

遵循这些规则,我能够解决我自己的问题,我希望这对其他人有帮助。

于 2012-11-17T00:27:55.763 回答
1

我遇到了同样的问题,您需要将模式更改为类似

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

末尾的 's' 基本上意味着将字符串视为单行。

于 2012-12-07T13:46:04.120 回答
0

preg_match返回为该模式找到的匹配数。当您有匹配项时,它会导致 php 中的致命错误(print_r(1)例如,导致错误)。print_r(0) (当您更改模式并且没有匹配项时)不会,只是打印出 0。

你要print_r($matches)

顺便说一句,您的模式没有正确转义。使用双引号意味着您需要转义括号前的反斜杠。

于 2011-10-01T14:48:29.593 回答