preg_match 函数中的正则表达式返回浏览器错误

Posted

技术标签:

【中文标题】preg_match 函数中的正则表达式返回浏览器错误【英文标题】:RegExp in preg_match function returning browser error 【发布时间】:2011-11-29 01:44:09 【问题描述】:

以下函数与我在 $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。

【问题讨论】:

所以您正在寻找一个 .还是换行? 我正在寻找一个 .或括号内的新行。正则表达式检查 regexpal.com @stereofrog 你可能是对的。这是我运行脚本时的 apache 错误日志:[Sat Oct 01 11:41:40 2011] [notice] Parent: child process exited with status 255 -- Restarting. 您看到的崩溃不是新的,而是由于某类正则表达式应用于较大的主题字符串,PCRE 库中未处理的堆栈溢出。将 PHP 升级到最新版本 (5.3.8) 将无济于事。我目前正在研究这个问题的详细答案(这不是微不足道的)。待命...与此同时,您可以看看前段时间同样的问题如何影响 Drupal 项目:Optimize CSS option causes php cgi to segfault in pcre function "match" @stereofrog - 是的。短篇小说:PHP 的 pcre.recursion_limit 默认为 100,000,这太高了。这个值需要根据PCRE Documentation设置为stacksize除以500。对于httpd.exe 的 Win32 构建(具有 256KB 堆栈),pcre.recursion_limit 需要设置为 524。在 *nix 系统上(可执行文件通常具有 8MB 堆栈)需要减少到 16777。跨度> 【参考方案1】:

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

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

背景:

PHP preg_* 系列正则表达式函数使用 Philip Hazel 强大的 PCRE library。使用这个库,有一类正则表达式需要对其内部 match() 函数进行大量递归调用,这会占用大量堆栈空间(并且使用的堆栈空间与主题字符串的大小成正比)被匹配)。因此,如果主题字符串太长,就会发生堆栈溢出和相应的分段错误。此行为在末尾标题为 pcrestack 的部分下的 PCRE documentation 中进行了描述。

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 网络服务器 (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。

preg_match() 的 PHP 文档说它在出错时返回 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) 以外的任何内容时,都需要显式检查PCRE 错误。 如果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() 无法返回 FALSE 的 PHP 错误。这个错误肯定会质疑很多使用preg_match 的代码! (我肯定会清点我自己的 PHP 代码。) 在 Windows 下,Apache 网络服务器可执行文件 (httpd.exe) 的堆栈大小为 256KB。 PHP 命令行可执行文件 (php.exe) 的堆栈大小为 8MB。 pcre.recursion_limit 的安全值应根据运行脚本的可执行文件(分别为 524 和 16777)设置。 在 *nix 系统下,Apache 网络服务器和命令行可执行文件通常都使用 8MB 的堆栈大小构建,因此不会经常遇到此问题。 PHP 开发人员应将默认值pcre.recursion_limit 设置为安全值。 PHP 开发人员应将preg_match() 错误修复应用于 PHP 5.2 版。 可以使用CFF Explorer 免费软件程序手动修改Windows 可执行文件的堆栈大小。您可以使用此程序来增加 Apache httpd.exe 可执行文件的堆栈大小。 (这在 XP 下有效,但 Vista 和 Win7 可能会报错。)

【讨论】:

设置大量递归限制对我不起作用:&lt;?php ini_set("pcre.recursion_limit", "524"); $contents = 'd' . str_repeat('a', 1900) . 'b'; $contents = preg_replace('/d(a)+b/', '\1', $contents); 在 Win7、PHP v5.3.9 上崩溃 嗨,克里斯。感谢您的反馈。是的,我的测试表明您更简单的表达式:/d(a)+b/ 导致与我的答案中描述的行为相同。似乎(x)+ 导致每个代表一次递归。很高兴知道。 使用 ini_set("pcre.recursion_limit", "524"); 设置 Stacksize=256 KB似乎对我有用。 它帮助了我:***.com/questions/5058845/… thx,这为我在 linux (systemd) 上更改堆栈大小节省了时间:freedesktop.org/software/systemd/man/systemd.exec.html【参考方案2】:

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

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

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

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

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

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

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

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

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

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

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

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

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

【讨论】:

【参考方案3】:

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

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

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

【讨论】:

尽管这是最短的答案,但它实际上解决了问题。【参考方案4】:

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

你想要print_r($matches)

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

【讨论】:

你是对的,虽然我不认为 print_r 函数是杀死它的原因。当我删除该行时,脚本失败并出现相同的浏览器错误。

以上是关于preg_match 函数中的正则表达式返回浏览器错误的主要内容,如果未能解决你的问题,请参考以下文章

PHP中使用正则表达式详解 preg_match() preg_replace() preg_mat

PHP中嵌入正则表达式常用的函数

PHP 正则表达式匹配 preg_match 与 preg_match_all 函数

js 如何正则匹配多个,像php 的preg_match()

前端学PHP之正则表达式函数

PHP常用正则表达式汇总