PHP PREG_JIT_STACKLIMIT_ERROR - 低效的正则表达式

时间:2016-09-25 10:34:47

标签: php regex php-7

使用更长的字符串时,我在preg_replace_callback()函数中收到PREG_JIT_STACKLIMIT_ERROR错误。超过2000个字符,它不是woking(超过2000个字符匹配正则表达式,而不是2000个字符串) 我已经读过它是由效率低下的正则表达式引起的,但我不能使我的正则表达式更简单。这是我的正则表达式:

/\{@([a-z0-9_]+)-((%?[a-z0-9_]+(:[a-z0-9_]+)*)+)\|(((?R)|.)*)@\}/Us

它应匹配以下字符串:

1){@if-statement|echo this|echo otherwise@}

2){@if-statement:sub|echo this|echo otherwise@}

3){@if-statement%statament2:sub|echo this@}

并且还嵌套,如下所示:

4){@if-statement|echo this| {@if-statement2|echo this|echo otherwise@} @}

我试图将其简化为:

/\{@([a-z0-9_]+)-([a-z0-9_]+)\|(((?R)|.)*)@\}/Us

但看起来错误是由(((?R)|.)*)部分引起的。有什么建议吗?

测试代码:

$string = '{@if-is_not_logged_homepage|
<header id="header_home">
    <div class="in">
        <div class="top">
            <h1 class="logo"><a href="/"><img src="/img/logo-home.png" alt=""></a></h1>
            <div class="login_outer_wrapper">
                <button id="login"><div class="a"><i class="stripe"><i></i></i>Log in</div></button>
                <div id="login_wrapper">
                    <form method="post" action="{^login^}" id="form_login_global">
                        <div class="form_field no_description">
                            <label>{!auth:login_email!}</label>
                            <div class="input"><input type="text" name="form[login]"></div>
                        </div>
                        <div class="form_field no_description password">
                            <label>{!auth:password!}</label>
                            <div class="input"><input type="password" name="form[password]"></div>
                        </div>
                        <div class="remember">
                            <input type="checkbox" name="remember" id="remember_me_check" checked>
                            <label for="remember_me_check"><i class="fa fa-check" aria-hidden="true"></i>Remember</label>
                        </div>
                        <div class="submit_box">
                            <button class="btn btn_check">Log in</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
        <div class="content clr">
            <div class="main_menu">
                <a href="">
                    <i class="ico a"><i class="fa fa-lightbulb-o" aria-hidden="true"></i></i>
                    <span>Idea</span>
                    <div>&nbsp;</div>
                </a>
                <a href="">
                    <i class="ico b"><i class="fa fa-user" aria-hidden="true"></i></i>
                    <span>FFa</span>
                </a>
                <a href="">
                    <i class="ico c"><i class="fa fa-briefcase" aria-hidden="true"></i></i>
                    <span>Buss</span>
                </a>
            </div>
            <div class="text_wrapper">

                <div>
                    <div class="register_wrapper">
                        <a id="main_register" class="btn register">Załóż konto</a>
                        <form method="post" action="{^login^}" id="form_register_home">
                            <div class="form_field no_description">
                                <label>{!auth:email!}</label>
                                <div class="input"><input type="text" name="form2[email]"></div>
                            </div>
                            <div class="form_field no_description password">
                                <label>{!auth:password!}</label>
                                <div class="input tooltip"><input type="password" name="form2[password]"><i class="fa fa-info-circle tooltip_open" aria-hidden="true" title="{!auth:password_format!}"></i></div>

                            </div>
                            <div class="form_field terms no_description">
                                <div class="input">
                                    <input type="checkbox" name="form2[terms]" id="terms_check">
                                    <label for="terms_check"><i class="fa fa-check" aria-hidden="true"></i>Agree</label>
                                </div>
                            </div>
                            <div class="form_field no_description">
                                <div class="input captcha_wrapper">
                                    <div class="g-recaptcha" data-sitekey="{%captcha_public_key%}"></div>
                                </div>
                            </div>
                            <div class="submit_box">
                                <button class="btn btn_check">{!auth:register_btn!}</button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
</header>
@}';

$if_counter = 0;

$parsed_view = preg_replace_callback( '/\{@([a-z0-9_]+)-((%?[a-z0-9_]+(:[a-z0-9_]+)*)+)\|(((?R)|.)*)@\}/Us',
        function( $match ) use( &$if_counter ){
            return '<-{'. ( $if_counter ++ ) .'}->';
        }, $string );


var_dump($parsed_view); // NULL

2 个答案:

答案 0 :(得分:4)

什么是PCRE JIT

  

即时编译是一项重量级的优化,可以大大提高   加快模式匹配。但是,它需要额外的费用   在执行比赛之前进行处理。 因此,它是最重要的   当相同的模式多次匹配时会受益。

它基本上是如何工作的?

  

PCRE(和JIT)是一个递归的,深度优先的引擎,所以它需要一个堆栈   其中当前节点的本地数据在检查之前被推送   子节点...当编译的JIT代码运行时,它需要一个块   用作堆栈的内存。默认情况下,它在计算机上使用 32K   堆。但是,一些大型或复杂的模式需要更多   这个。如果没有,则会给出错误PCRE_ERROR_JIT_STACKLIMIT   足够的堆栈。

首先引用你会理解JIT是一个可选功能,默认情况下在PHP [v7。*] PCRE中打开。因此,您可以轻松将其关闭:pcre.jit = 0(虽然不推荐)

但是,在接收6函数的错误代码#preg_*时,这意味着JIT可能会达到堆栈大小限制。

由于捕获组比非捕获组消耗更多内存(根据簇的量词类型,更多的内存将用于):

  1. 捕获小组OP_CBRApcre_jit_compile.c:#1138) - (真实记忆不止于此):
  2. case OP_CBRA:
    case OP_SCBRA:
    bracketlen = 1 + LINK_SIZE + IMM2_SIZE;
    break;
    
    1. 非捕获组OP_BRApcre_jit_compile.c:#1134) - (真实 记忆超过这个):
    2. case OP_BRA:
      bracketlen = 1 + LINK_SIZE;
      break;
      

      因此,在您自己的RegEx中将捕获组更改为非捕获组会使其提供正确的输出(我不确切地知道该节省了多少内存)

      但似乎你需要捕捉群体并且它们是必要的。然后,为了提高性能,您应该重新编写RegEx。回溯几乎是RegEx中应该考虑的所有内容。

      更新#1

      <强>解决方案

      (?(DEFINE)
        (?<recurs>
          (?! {@|@} ) [^|] [^{@|\\]* ( \\.[^{@|\\]* )* | (?R)
        )
      )
      {@
      (?<If> \w+)-
      (?<Condition> (%?\w++ (:\w+)*)* )
      (?<True> [|] [^{@|]*+ (?&recurs)* )
      (?<False> [|] (?&recurs)* )?
      \s*@}
      

      <强> Live demo

      PHP代码(观看反斜杠转义):

      preg_match_all('/(?(DEFINE)
        (?<recurs>
          (?! {@|@} ) [^|] [^{@|\\\\]* ( \\\\.[^{@|\\\\]* )* | (?R)
        )
      )
      {@
      (?<If> \w+ )-
      (?<Condition> (%?\w++ (:\w+)*)* )
      (?<True> [|] [^{@|]*+ (?&recurs)* )
      (?<False> [|] (?&recurs)* )?
      \s*@}/x', $string, $matches);
      

      这是您自己的RegEx,它以最少的回溯步骤进行优化。因此,你自己应该匹配的任何内容都与此相匹配。

      RegEx没有遵循嵌套的if块:

      {@
      (?<If> \w+)-
      (?<Condition> (%?\w++ (:\w+)*)* )
      (?<True> [|] [^|\\]* (?: \\.[^|\\]* )* )
      (?<False> [|] \X*)?
      @}
      

      Live demo

      大多数量词都是通过向+附加{{1}}而占有地写入(避免回溯)。

答案 1 :(得分:3)

您可以看到的问题是您的模式效率低下。主要原因是:

顺便说一句,有太多无用的捕获组会消耗内存。如果您不需要捕获组,请不要编写它。如果您确实需要对元素进行分组,请使用非捕获组,但不要使用非捕获组来制作模式,并且“更具可读性”。 (还有其他方法可以像命名组,自由空格和评论一样)

如果我理解得很好,那么您正在尝试为preg_replace_callback构建一个正则表达式来处理模板系统的控制语句。 由于这些控制语句可以嵌套,并且正则表达式引擎无法匹配相同子字符串的几次,因此您必须在以下几种策略中进行选择:

  1. 您可以编写递归模式来描述最终包含其他条件语句的条件语句。

  2. 您可以编写仅匹配最内层条件语句的模式。 (换句话说,它禁止嵌套的条件语句。)

  3. 在这两种情况下,您需要多次解析字符串,直到无法替换。 (请注意,您也可以使用第一个策略的递归函数,但它会使事情变得更复杂。)

    让我们看看第二种方式:

    $pattern = '~
    {@ (?<cond> \w+ ) - (?<stat> \w+ (?: % \w+ )* ) (?: : (?<sub> \w+ ) )? \|
    
    # a "THEN" part that doesn\'t have nested conditional statements
    (?<then> [^{|@]*+ (?: { (?!@) [^{|@]* | @ (?!}) [^{|@]* )*+ )
    
    # optional "ELSE" part (the content is similar to the "THEN" part)
    (?: \| (?<else> \g<then> ) )? (*SKIP) @}~x';
    
    $parsed_view = $string;
    $count = 0;
    
    do {
        $parsed_view = preg_replace_callback($pattern, function ($m) {
            // do what you need here. The different captures can be
            // easily accessed with their names: $m['cond'], $m['stat']...
            // as defined in the pattern.
            return $result;
        }, $parsed_view, -1, $count);
    } while ($count);
    

    catastrophic backtracking

    正如您所看到的,嵌套语句的问题通过do..while循环和count的{​​{1}}参数来解决,以查看是否有内容被替换。

    此代码未经过测试,但我确信您可以完成并最终根据您的需求进行调整。

    顺便说一下,已经存在很多模板引擎(PHP已经是模板引擎)。您可以使用它们并避免创建自己的语法。您还可以查看他们的代码。