Paul King created GROOVY-12707:
----------------------------------
Summary: RegexChecker misreports named groups for regexes in
COMMENTS mode
Key: GROOVY-12707
URL: https://issues.apache.org/jira/browse/GROOVY-12707
Project: Groovy
Issue Type: Bug
Reporter: Paul King
h2. Problem
GROOVY-12697 added a compile-time check that {{Matcher.group(String)}} names a
group the regex defines. The checker finds the names by scanning the regex text
for {{(?<name>}} openers. That scan doesn't follow the JDK's parsing rules,
which goes wrong both ways:
* *Valid code is rejected.* In COMMENTS mode ({{(?x)}}) the JDK allows
whitespace between {{(}} and {{?}}, and allows a {{#}} comment inside a group
name. The scan misses those names and reports a correctly named group as
missing.
* *Errors are missed.* Text that only looks like a group opener, such as
{{(?<a>)}} inside {{\Q...\E}}, is counted as a group name, so a lookup of that
name is not reported and fails at runtime instead.
Reproducer, with {{@TypeChecked(extensions =
'groovy.typecheckers.RegexChecker')}}:
{code:groovy}
Matcher m = 'foobar' =~ /(?x)( ?<first>...)/
assert m.find()
assert m.group('first') == 'foo'
{code}
Actual:
{noformat}
[Static type checking] - Invalid group name 'first' for regex with no named
groups
{noformat}
Expected: the script compiles and the assertions pass, as they do without the
extension.
||Regex||Lookup||Actual||Expected||
|{{/(?x)( ?<first>...)/}}|{{group('first')}}|"Invalid group name 'first' for
regex with no named groups"|compiles|
|{{"(?x)(?<fi#c\nrst>...)"}}|{{group('first')}}|"Invalid group name 'first' for
regex with no named groups"|compiles|
|{{/\Q(?<a>)\E(?<b>...)/}}|{{group('a')}}|compiles, then
{{IllegalArgumentException: No group with name <a>}} at runtime|"Invalid group
name 'a' for regex with named group b"|
GROOVY-12697 is on master but not yet released, so no release is affected. If
GROOVY-12697 is backported to GROOVY_6_0_X, this fix should go with it.
h2. Proposed fix
Ask the JDK whether the regex defines the name, rather than scanning the text.
A named backreference to an undefined group is a compile error ("named
capturing group <x> does not exist"), so compiling the regex with {{"\n\\k<" +
name + ">"}} added to the end answers the question exactly. The {{\n}} ends a
trailing COMMENTS-mode comment. This works on JDK 17, so
{{Pattern.namedGroups()}} (JDK 20) is not needed.
Checked on JDK 17 against 15 regexes, with exact results for each:
* names after whitespace or comments in COMMENTS mode ({{( ?<first>}},
{{(?<fi#c\nrst>}});
* a trailing {{# comment}};
* openers inside {{\Q...\E}}, inside a character class, or after an escaped
{{\(}}, which correctly don't count;
* scoped {{(?x:...)}} and {{(?-x)}};
* the COMMENTS flag passed to {{Pattern.compile}}.
Two cases can only report a name as defined, never as missing. Both only miss
an error and never reject valid code:
* an unterminated {{\Q}} quotes the added backreference;
* with {{Pattern.LITERAL}} the added text is literal. A LITERAL regex has no
groups at all.
The error message keeps listing the defined names ("for regex with named groups
first, second"). The scan stays only to find the candidate names for that list,
and each one is confirmed with the same JDK test before it is listed, so names
from quoted text or character classes no longer appear in the message.
{{Matcher.start(String)}} and {{Matcher.end(String)}} take a group name in the
same way and should get the same check.
h2. Tests
* Each row of the table above.
* The existing named-group tests from GROOVY-12697 keep passing, including
whitespace inside the angle brackets in COMMENTS mode.
* The message lists only the names the regex defines; for example, a regex with
{{(?<a>)}} inside {{\Q...\E}} lists only the real groups.
* {{start('name')}} and {{end('name')}}: a defined name compiles, and an
undefined one is reported.
* The unterminated {{\Q}} and {{Pattern.LITERAL}} cases compile, and are left
to the runtime.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)