PHP PREG_JIT_STACKLIMIT_ERROR – неэффективное регулярное выражение

Я получаю ошибку preg_replace_callback() функции preg_replace_callback() при работе с более длинной строкой. Над 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@} @} {@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 

Что такое PCRE JIT ?

Компиляция «точно в срок» – это супертяжелая оптимизация, которая может значительно ускорить сопоставление шаблонов. Тем не менее, это связано с дополнительной обработкой перед выполнением матча. Таким образом, это очень полезно, если один и тот же шаблон будет сопоставляться много раз.

и как он работает в основном?

PCRE (и JIT) – это рекурсивный, глубинный движок, поэтому ему нужен стек, где локальные данные текущего узла проталкиваются перед проверкой его дочерних узлов … Когда выполняется скомпилированный JIT-код, ему нужен блок памяти для использования в качестве стека. По умолчанию он использует 32K в стеке машины. Однако некоторые крупные или сложные шаблоны нуждаются в большем, чем это. Ошибка PCRE_ERROR_JIT_STACKLIMIT задается, когда недостаточно стека.

По первой цитате вы поймете, что JIT – это дополнительная функция, которая по умолчанию включена в PHP [v7. *] PCRE. Поэтому вы можете легко отключить его: pcre.jit = 0 (это не рекомендуется)

Однако, получая код ошибки # 6 функций preg_* это означает, что JIT достигает предела размера стека.

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

  1. Группа захвата OP_CBRA ( pcre_jit_compile.c: # 1138 ) – (реальная память больше, чем эта):
 case OP_CBRA: case OP_SCBRA: bracketlen = 1 + LINK_SIZE + IMM2_SIZE; break; 
  1. OP_BRA группа OP_BRA ( pcre_jit_compile.c: # 1134 ) – (реальная память больше, чем эта):
 case OP_BRA: bracketlen = 1 + LINK_SIZE; break; 

Поэтому изменение групп захвата в группы, не связанные с захватом, в вашем собственном RegEx заставляет его давать правильный результат (который я точно не знаю, сколько памяти сохранено этим)

Но, похоже, вам нужно захватить группы, и они необходимы. Затем вы должны переписать свой RegEx ради производительности. Backtracking – это почти все в RegEx, которые следует учитывать.

Обновление # 1

Решение :

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

Демо-версия

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*)? @} 

Демо-версия

Большинство квантификаторов записываются собственнически (избегает обратного хода), добавляя + к ним.

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

  • Вы используете такие подшаблоны: (a+)+b что является лучшим способом для катастрофического возврата
  • Вы также используете такие подшаблоны: (a|b)+ который может быть хорошим дизайном, за исключением механизма повторного выражения, такого как pcre
  • Вы используете модификатор U по неизвестной причине, из-за чего все ваши кванторы не жадные и генерируют много бесполезных тестов

В стороне есть слишком много бесполезных групп захвата, которые потребляют память ни для чего. Когда вам не нужна группа захвата, не пишите. Если вам действительно нужно группировать элементы, используйте группу, не связанную с захватом, но не используйте группы, не захватывающие захват, чтобы сделать шаблон «более читаемым» (есть другие способы сделать это, например, именованные группы, свободные интервалы и комментарии) ,


Если я хорошо понимаю, вы пытаетесь создать регулярное выражение для preg_replace_callback для обработки инструкции управления вашей системой шаблонов. Поскольку эти управляющие операторы могут быть вложенными, а механизм регулярных выражений не может совпадать с одной и той же подстрокой, вам нужно выбирать между несколькими стратегиями:

  1. Вы можете написать рекурсивный шаблон для описания условного оператора, который в конечном итоге содержит другие условные операторы.

  2. Вы можете написать шаблон, который соответствует только самым внутренним условным операторам. (Другими словами, он запрещает вложенные условные утверждения.)

В двух случаях вам нужно разбирать строку несколько раз, пока нечего заменить. (Обратите внимание, что вы также можете использовать рекурсивную функцию с первой стратегией, но это усложняет ситуацию.)

Давайте посмотрим на второй способ:

 $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); 

шаблонная демонстрация

Как вы видите, проблема вложенных операторов решается с помощью цикла do..while и параметра count preg_replace_callback чтобы увидеть, что что-то заменено.

Этот код не проверен, но я уверен, что вы можете его завершить и в конечном итоге адаптировать к вашим потребностям.


В стороне есть много шаблонов, которые уже существуют (и PHP уже является механизмом шаблонов). Вы можете использовать их и избегать создания собственного синтаксиса. Вы также можете посмотреть их коды.