From 102fc8d3eb4c860b80ee5afebc6d182799745bb6 Mon Sep 17 00:00:00 2001 From: Cody Lerum Date: Mon, 3 Jan 2022 10:08:39 -0700 Subject: [PATCH] implement FormatString for Java 15 String#formatted --- .../formatstring/FormatString.java | 34 +++++++++++++-- .../formatstring/FormatStringTest.java | 43 +++++++++++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/FormatString.java b/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/FormatString.java index 449dc6298e8..53fb00fcc63 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/FormatString.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/FormatString.java @@ -17,6 +17,7 @@ package com.google.errorprone.bugpatterns.formatstring; import static com.google.errorprone.BugPattern.SeverityLevel.ERROR; +import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod; import com.google.common.collect.ImmutableList; import com.google.errorprone.BugPattern; @@ -24,6 +25,7 @@ import com.google.errorprone.bugpatterns.BugChecker; import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; @@ -33,16 +35,40 @@ @BugPattern(name = "FormatString", summary = "Invalid printf-style format string", severity = ERROR) public class FormatString extends BugChecker implements MethodInvocationTreeMatcher { + private static final Matcher FORMATTED_METHOD = instanceMethod().onExactClass("java.lang.String").named("formatted"); + @Override public Description matchMethodInvocation(MethodInvocationTree tree, final VisitorState state) { - ImmutableList args = FormatStringUtils.formatMethodArguments(tree, state); - if (args.isEmpty()) { - return Description.NO_MATCH; - } + ImmutableList args; MethodSymbol sym = ASTHelpers.getSymbol(tree); if (sym == null) { return Description.NO_MATCH; } + + if (FORMATTED_METHOD.matches(tree, state)) { + /* + Java 15 and greater supports the formatted method on an instance of string. If found + then use the string value as the pattern and all-of-the arguments and send directly to + the validate method. + */ + ExpressionTree receiver = ASTHelpers.getReceiver(tree); + if (receiver == null) { + // an unqualified call to 'formatted', possibly inside the definition + // of java.lang.String + return Description.NO_MATCH; + } + args = + ImmutableList.builder() + .add(receiver) + .addAll(tree.getArguments()) + .build(); + + } else { + args = FormatStringUtils.formatMethodArguments(tree, state); + } + if (args.isEmpty()) { + return Description.NO_MATCH; + } FormatStringValidation.ValidationResult result = FormatStringValidation.validate(sym, args, state); if (result == null) { diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/formatstring/FormatStringTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/formatstring/FormatStringTest.java index 2bcea229f06..f24e6146fcf 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/formatstring/FormatStringTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/formatstring/FormatStringTest.java @@ -365,4 +365,47 @@ public void invalidIndex() { "}") .doTest(); } + + @Test + public void stringFormattedNegativeCase() { + assumeTrue(RuntimeVersion.isAtLeast15()); + compilationHelper + .addSourceLines( + "Test.java", + "class Test {", + " void f() {", + " \"%s %s\".formatted(\"foo\", \"baz\");", + " }", + "}") + .doTest(); + } + + @Test + public void stringFormattedPositiveCase() { + assumeTrue(RuntimeVersion.isAtLeast15()); + compilationHelper + .addSourceLines( + "Test.java", + "class Test {", + " void f() {", + " // BUG: Diagnostic contains: missing argument for format specifier", + " \"%s %s %s\".formatted(\"foo\", \"baz\");", + " }", + "}") + .doTest(); + } + + @Test + public void nonConstantStringFormattedNegativeCase() { + assumeTrue(RuntimeVersion.isAtLeast15()); + compilationHelper + .addSourceLines( + "Test.java", + "class Test {", + " void f(String f) {", + " f.formatted(\"foo\", \"baz\");", + " }", + "}") + .doTest(); + } }