Paul King created GROOVY-12708:
----------------------------------

             Summary: RegexChecker ignores the flags argument of Pattern.compile
                 Key: GROOVY-12708
                 URL: https://issues.apache.org/jira/browse/GROOVY-12708
             Project: Groovy
          Issue Type: Bug
    Affects Versions: 6.0.0, 5.1.3
            Reporter: Paul King


h2. Problem

{{groovy.typecheckers.RegexChecker}} checks {{Pattern.compile(regex, flags)}} 
by compiling {{regex}} without {{flags}}. Most flags don't affect whether a 
regex is valid or how many groups it has, but {{Pattern.COMMENTS}} and 
{{Pattern.LITERAL}} change how the regex text is parsed, so the checker checks 
a different regex from the one that runs:

* *Valid code is rejected* with "Bad regex", when the regex is only valid with 
the flag.
* *Errors are missed*, when the group count without the flag is higher than the 
real one (text in a COMMENTS-mode comment, or a LITERAL regex, which has no 
groups).

Reproducer, run with {{groovy --configscript config.groovy comments.groovy}}:

{code:groovy|title=config.groovy}
import groovy.transform.TypeChecked
withConfig(configuration) {
    ast(TypeChecked, extensions: 'groovy.typecheckers.RegexChecker')
}
{code}

{code:groovy|title=comments.groovy}
import java.util.regex.Pattern
assert Pattern.compile('(a) # (b', Pattern.COMMENTS).matcher('a').matches()
{code}

Actual:
{noformat}
[Static type checking] - Bad regex: Unclosed group near index 8
{noformat}

Expected: the script compiles and the assertion passes, as it does without the 
extension.

||Script||Actual (4.0.32, 5.1.3, 6.0.0)||Expected||
|{{Pattern.compile('(a) # (b', Pattern.COMMENTS)}}|"Bad regex: Unclosed group 
near index 8"|compiles|
|{{Pattern.compile('(?< first >a)', Pattern.COMMENTS)}}|"Bad regex: capturing 
group name does not start with a Latin letter near index 3"|compiles|
|{{Pattern.compile('a(b', Pattern.LITERAL)}}|"Bad regex: Unclosed group near 
index 3"|compiles|
|{{Matcher m = Pattern.compile('(a) # (b)', Pattern.COMMENTS).matcher('a')}}, 
then {{m.group(2)}}|compiles, then {{IndexOutOfBoundsException: No group 2}} at 
runtime (6.0.0)|"Invalid group count 2 for regex with 1 group"|
|{{Matcher m = Pattern.compile('(a)', Pattern.LITERAL).matcher('(a)')}}, then 
{{m.group(1)}}|compiles, then {{IndexOutOfBoundsException: No group 1}} at 
runtime (6.0.0)|"Invalid group count 1 for regex with 0 groups"|

The same flags written inline, such as {{(?x)}}, are handled correctly, because 
the JDK applies them when the checker compiles the regex.

On 4.0.32 and 5.1.3 the last two scripts instead fail with an unrelated general 
error in {{findTargetVariable}}, from a crash on 
{{Pattern.compile(...).matcher(...)}} that was fixed before 6.0.0.

h2. Proposed fix

Work out the flags argument at compile time and compile the regex with it. Then 
"Bad regex", the group count, the named-group check and the 
{{matcher[...][...]}} type inference all apply to the regex that actually runs. 
The flags can be written as:
* an {{int}} constant;
* a {{Pattern}} constant such as {{Pattern.COMMENTS}}, read from the 
{{Pattern}} class by name;
* constants combined with {{|}};
* a local variable or field holding one of these, as already allowed for the 
regex string.

When the flags can't be worked out (a parameter, a method call), skip the 
checks for that call and record no group count, because the result depends on 
flags that are only known at runtime.

A LITERAL regex has no groups at all. The named-group check (GROOVY-12707) 
works by compiling the regex with a named backreference added to the end, but 
under LITERAL that text is literal, so every name would look defined. Record a 
LITERAL regex as having no named groups instead.

h2. Tests

* Each row of the table above.
* Flags written as {{Pattern.COMMENTS}}, {{Pattern.COMMENTS | 
Pattern.CASE_INSENSITIVE}}, an {{int}} constant and a local variable, with the 
same results.
* Flags that can't be worked out (a method parameter): no "Bad regex" error and 
no group-count error.
* A named group under COMMENTS passed as a flag ({{Pattern.compile('(?< first 
>a)', Pattern.COMMENTS)}}, then {{m.group('first')}}) compiles, and an 
undefined name is reported.
* Flags that don't change parsing ({{CASE_INSENSITIVE}}) give the same results 
as no flags.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to