首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >RegExp在preg_match函数中返回浏览器错误

问RegExp在preg_match函数中返回浏览器错误
EN

Stack Overflow用户
提问于 2011-10-01 14:42:37
回答 4查看 12.7K关注 0票数 15

下面的函数与我在$pattern变量中提供的regexp中断。如果我改变了regexp,我很好,所以我认为这就是问题所在。不过,我没有看到这个问题,即使打开了PHP,我也不会收到标准的PHP错误。

代码语言:javascript
复制
function parseAPIResults($results){
//Takes results from getAPIResults, returns array.

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

}

火狐6:连接被重置,Chrome 14: Error 101 (net::ERR_CONNECTION_RESET):连接被重置。IE 8:无法显示网页

更新:

Apache/PHP可能会崩溃。下面是运行脚本时的Apache错误日志:

2011年10月1日(星期六) 11:41:40家长:子进程退出状态255 --重新启动。 2011年10月11日:41:40 Apache/2.2.11 (Win32) PHP5.3.0配置--恢复正常操作

在Windows 7上运行WAMP 2.0。

EN

回答 4

Stack Overflow用户

发布于 2011-10-02 17:23:35

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

是的,这类regex将重复(并且静默地)使Apache/PHP崩溃,导致堆栈溢出导致未处理的分段错误!

背景:

PHP preg_*系列regex函数使用Philip的强大PCRE文库。对于这个库,有一类正则表达式需要对其内部match()函数进行大量递归调用,这占用了大量的堆栈空间(所使用的堆栈空间与所匹配的主题字符串的大小成正比)。因此,如果主题字符串太长,则会发生堆栈溢出和相应的分段错误。在最后的PCRE文档中,在标题为:[医]烟囱的部分中描述了这种行为。

PHP 1: PHP:pcre.recursion_limit太大了。

PCRE文档描述了如何通过将递归深度限制在大致等于链接应用程序的堆栈大小除以500的安全值来避免堆栈溢出分段错误。当按建议适当限制递归深度时,库不会生成堆栈溢出,而是使用错误代码优雅地退出。在PHP下,这个最大递归深度由pcre.recursion_limit配置变量指定,(不幸的是)默认值设置为100,000。这个值太大了!这里的是各种可执行堆栈大小的pcre.recursion_limit安全值表:

代码语言:javascript
复制
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构建(其堆栈大小(相对较小)为256 to ),pcre.recursion_limit的正确值应该设置为524。这可以通过以下PHP代码行实现:

代码语言:javascript
复制
ini_set("pcre.recursion_limit", "524"); // PHP default is 100,000.

当将此代码添加到PHP脚本时,不会发生堆栈溢出,而是生成有意义的错误代码。也就是说,它应该生成一个错误代码!(但不幸的是,由于另一个PHP,preg_match()没有。)

PHP 2:preg_match()在出错时不会返回FALSE。

preg_match()的documentation说,它在错误时返回FALSE。不幸的是,PHP5.3.3及以下版本有一个bug (#52732),其中preg_match()不返回错误时的FALSE (相反,它返回int(0),在不匹配的情况下返回的值相同)。这个错误是在PHP版本5.3.4中修复的。

解决方案:

假设您将继续使用WAMP 2.0 (使用PHP5.3.0),则解决方案需要考虑上述两个bug。以下是我的建议:

  • 需要将pcre.recursion_limit降低到一个安全值: 524。
  • 每当preg_match()返回int(1)以外的任何内容时,都需要显式检查PCRE错误。
  • 如果preg_match()返回int(1),那么匹配就成功了。
  • 如果preg_match()返回int(0),那么匹配要么不成功,要么出现错误。

下面是脚本的修改版本(设计用于从命令行运行),该版本确定了导致递归限制错误的主题字符串长度:

代码语言:javascript
复制
<?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,当PCRE库中发生错误时,preg_match()无法返回FALSE。这个bug肯定会让很多使用preg_match的代码产生疑问!(我肯定会清点我自己的PHP代码。)
  • 在Windows下,Apache可执行文件(httpd.exe)的堆栈大小为256 of。PHP命令行可执行文件(php.exe)的堆栈大小为8MB。应该根据脚本正在运行的可执行文件设置pcre.recursion_limit的安全值(分别为524和16777 )。
  • 在*nix系统下,Apache 8MB服务器和命令行可执行文件通常都是以8MB的堆栈大小构建的,因此这个问题并不常见。
  • PHP开发人员应该将pcre.recursion_limit的默认值设置为安全值。
  • PHP开发人员应该将preg_match()修补程序应用于PHPVersion5.2。
  • 可以使用CFF探索者免费软件程序手动修改Windows可执行文件的堆栈大小。您可以使用此程序来增加Apache httpd.exe可执行文件的堆栈大小。(这适用于XP,但Vista和Win7可能会抱怨。)
票数 54
EN

Stack Overflow用户

发布于 2012-11-17 00:27:55

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

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

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

那么如何修复你的牢笼呢?首先,你说你想要匹配"a .或换行符“。请注意“。”是正则表达式中的一个特殊字符,它不仅与点匹配,而且与任何字符匹配,因此您需要转义。(我希望我没有误解你的意思,这是我的本意。)

代码语言:javascript
复制
$pattern = '/\[(\.|\n)+\]/';

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

代码语言:javascript
复制
$pattern = '/\[(\.+|\n+)+\]/';

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

代码语言:javascript
复制
$pattern = '/\[(\.++|\n++)++\]/';

因此,这应该具有与原始regex相同的含义,但是在php中工作而不会破坏它。为什么?所有格量词“吃掉”字符,不允许回溯。因此,PCRE不必使用递归,堆栈也不会溢出。在括号内使用它们似乎是个好主意,因为我们通常不需要对替代方案进行量化。

总而言之,最佳做法似乎是:

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

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

票数 3
EN

Stack Overflow用户

发布于 2012-12-07 13:46:04

我也有同样的问题,你需要给这个模式做些类似的事情

代码语言:javascript
复制
$pattern = '|/your pattern/|s';

结尾的's‘基本上意味着将字符串看作一行。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7620910

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档