Scott Murphy Heiberg created GROOVY-12692:
---------------------------------------------

             Summary: FormatStringChecker counts %% and %n as taking an 
argument when the arguments are not constants
                 Key: GROOVY-12692
                 URL: https://issues.apache.org/jira/browse/GROOVY-12692
             Project: Groovy
          Issue Type: Bug
    Affects Versions: 6.0.0
            Reporter: Scott Murphy Heiberg


With {{groovy.typecheckers.FormatStringChecker}} enabled, a format call whose 
arguments are not all constants is checked as if {{%%}} and {{%n}} each took an 
argument. A valid call is rejected.

{{Percent.groovy}}:

{code:groovy}
import groovy.transform.TypeChecked

@TypeChecked(extensions = 'groovy.typecheckers.FormatStringChecker')
String show(String s) {
    String.format('100%% %s', s)
}
assert show('done') == '100% done'
{code}

{code:sh}
groovyc Percent.groovy
{code}

Actual: exit 1 and

{code:text}
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
Percent.groovy: 5: [Static type checking] - MissingFormatArgument: Format 
specifier '%s'
 @ line 5, column 5.
       String.format('100%% %s', s)
       ^

1 error
{code}

Expected: the source compiles. {{java.util.Formatter}} gives {{%%}} and {{%n}} 
no argument, so the call returns {{100% done}}. The checker accepts the same 
format string when the argument is a constant ({{String.format('100%% %s', 
'done')}}).

More calls, each in a method with a {{String s}} parameter, with {{s = 'done'}} 
at run time:

||call||java.util.Formatter||FormatStringChecker||
|{{String.format('%s%n', s)}}|returns {{done}} and a line 
separator|MissingFormatArgument: Format specifier '%n'|
|{{String.format('%n%s', s)}}|returns a line separator and 
{{done}}|MissingFormatArgument: Format specifier '%s'|
|{{String.format('%s %%', s)}}|returns {{done %}}|MissingFormatArgument: Format 
specifier '%%'|
|{{String.format('%s %% %s', s, s)}}|returns {{done % 
done}}|MissingFormatArgument: Format specifier '%s'|
|{{String.format('%s %% %<s', s)}}|returns {{done % 
done}}|MissingFormatArgument: Format specifier '%%'|
|{{String.format('100%% %d', s)}}|IllegalFormatConversionException: d != 
java.lang.String|MissingFormatArgument: Format specifier '%d'|
|{{String.format('%% %<s', s)}}|MissingFormatArgumentException: Format 
specifier '%<s'|no error|
|{{sprintf('%s%n', s)}}|returns {{done}} and a line 
separator|MissingFormatArgument: Format specifier '%n'|

The rows for {{'100%% %d'}} and {{'%% %<s'}} show the other side of the same 
mistake: because the {{%%}} is given an argument, a real error is reported as a 
different error, or not at all.

Affected versions: 6.0.0, 6.0.0-beta-1, 5.0.8 and 5.0.0 reject 
{{Percent.groovy}} the same way, and so does current master (7.0.0-SNAPSHOT at 
5e9212ce1e).

Cause: when not all arguments are constants, {{checkFormatStringTypes}} handles 
every specifier the format pattern matches as one that takes an argument: it 
checks the index against the number of arguments, reads that argument's type, 
and advances both the next index and the previous argument that {{%<}} refers 
to. The {{switch}} that follows skips {{%}} and {{n}}, but only after this 
bookkeeping. With constant arguments the checker runs {{java.util.Formatter}} 
itself, which is why that path is right.

Fix outline: handle {{%%}} and {{%n}} before the argument bookkeeping, keeping 
the duplicate-flag check they already get.

A pull request with regression tests follows.

Environment: Groovy 6.0.0 on BellSoft JDK 21.0.12, macOS 26.5 (arm64); 
reproduced on master at 5e9212ce1e with the same JDK.



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

Reply via email to