Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAX_RECURSE_EVAL_NOCHANGE_DEPTH #17490

Open
hvds opened this issue Jan 26, 2020 · 7 comments
Open

MAX_RECURSE_EVAL_NOCHANGE_DEPTH #17490

hvds opened this issue Jan 26, 2020 · 7 comments

Comments

@hvds
Copy link
Contributor

hvds commented Jan 26, 2020

This was introduced as 50 by 6bda09f (Yves, 2006-10-04), made compile-time overridable by d56b301 (Rafael, 2007-02-14), bumped to 1000 by 4b196cd (Yves, 2007-02-15), and then dropped to 10 by ba6840f (Yves, 2016-03-06 as part of #14935). There are brief mentions in perlre under (?PARNO) and (??{ expr }):

Recursing deeply without consuming any input string will
also result in a fatal error.  The depth at which that happens is
compiled into perl, so it can be changed with a custom build.
[...]
Executing a postponed regular expression too many times without
consuming any input string will also result in a fatal error.

.. but once we hit a GOSUB it looks like every additional GOSUB or EVAL ("postponed" or not) bumps the count, so as of now we get:

% perl -wle '"foo" =~ m{(?&FOO)(?(DEFINE)(?<FOO>
    (?{1}) (?{1}) (?{1}) (?{1}) (?{1})
    (?{1}) (?{1}) (?{1}) (?{1}) (?{1})
    (?{1})
    foo
))}x'
EVAL without pos change exceeded limit in regex at -e line 1.
% 

This behaviour breaks Regexp::Debugger for quite trivial cases, which is a shame.

I consider it wrong that this recursion check is any more than a warning (as for sub recursion); if there's a good reason for it to be fatal, I think it really must be controllable without having to recompile perl. But the first question is whether it is actually checking the right thing - should it be treating normal (?{...}) evals as a candidate for bumping the recursion depth at all?

@demerphq can you clarify if this check was intended to kill patterns like the above?

Hugo

@demerphq
Copy link
Collaborator

demerphq commented Jan 26, 2020 via email

@hvds
Copy link
Contributor Author

hvds commented Jan 26, 2020

But the first question is whether it is actually checking the right thing -
should it be treating normal (?{...}) evals as a candidate for bumping the recursion depth at all?

(?{...}) no. (??{...}) yes.

I think that may be as simple as:

--- a/regexec.c
+++ b/regexec.c
@@ -7411,3 +7411,3 @@ S_regmatch(pTHX_ regmatch_info *reginfo, char *startpos, regnode *prog)
         case EVAL:  /*   /(?{...})B/   /(??{A})B/  and  /(?(?{...})X|Y)B/   */
-            if (cur_eval && cur_eval->locinput==locinput) {
+            if (logical == 2 && cur_eval && cur_eval->locinput==locinput) {
                if ( ++nochange_depth > max_nochange_depth )

.. since the 3 places it is decremented all have comments saying they are specifically for (??{...}) constructs (EVAL_postponed_AB, EVAL_postponed_AB_fail and END/fake_end). I'll try running some tests.

Hugo

@hvds
Copy link
Contributor Author

hvds commented Jan 27, 2020

Created a PR for this change as a first step.

@demerphq
Copy link
Collaborator

demerphq commented Jan 27, 2020 via email

@hvds
Copy link
Contributor Author

hvds commented Jan 27, 2020

Ok, merged that.

For the remaining questions, I'm confused now precisely what constructs should trigger "Pattern subroutine nesting/EVAL without pos change exceeded limit in regex" that would not first be caught by the "infinite recursion" tests. I tried crafting a cascade of (?<A>(?&B))(?<B>(?&C))..., but that didn't trigger it.

I think it'd be useful to have a couple of tests for these errors. I'm happy to write those if I can understand what's supposed to trigger them.

@demerphq
Copy link
Collaborator

demerphq commented Jan 27, 2020 via email

@hvds
Copy link
Contributor Author

hvds commented Jan 27, 2020

Right, the infinite recursion check is really aggressive now:

% perl -le '
  print +("foo" =~ m{(?&F)
    (?(DEFINE)(?<F>
      (??{ $x++ ? "(?!)" : "(?=)" })
      (?&F)
    |
      foo
    ))
  }x) ? "ok" : "not ok"
'
Infinite recursion in regex at -e line 1.
% 

I think that's a case that would be more ideally served by a (preferably overridable) "X without pos change exceeded limit" check.

wip-sync pushed a commit to NetBSD/pkgsrc-wip that referenced this issue Jun 28, 2020
Changes:
0.002005  Sat May 23 03:27:31 2020
    - Improved inference of /x flag (or absence thereof)
      for some (but not all) cases where an unescaped # is found
      (thanks Deven!)
    - Added proper dynamic tracking of /x flag status within regex,
      so that (?x:...) and (?-x:...) blocks are handled correctly.

0.002004  Sun Feb 16 23:58:03 2020
    - Added detection of (&subpat) and (<name> ... ) errors
      (Thanks, Hugo!)

0.002003  Fri Jan 31 21:30:10 2020
    - Fixed 'd' command under rxrx REPL
    - Added 'g' command under rxrx REPL
      (thanks, Richard!)

0.002002  Mon Jan 27 21:58:28 2020
    - Worked around spurious "EVAL without pos change exceeded limit"
      under Perl 5.24 to 5.30. See: Perl/perl5#17490
      (Thanks, Hugo!)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants