Skip to content

SONARJAVA-5683 S2077 Fix FN on strings built with String.format()/formatted(). - #5246

Merged
alban-auzeill merged 6 commits into
masterfrom
tt/S2077
Jul 17, 2025
Merged

SONARJAVA-5683 S2077 Fix FN on strings built with String.format()/formatted().#5246
alban-auzeill merged 6 commits into
masterfrom
tt/S2077

Conversation

@tomasz-tylenda-sonarsource

@tomasz-tylenda-sonarsource tomasz-tylenda-sonarsource commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

@sonarqube-next

sonarqube-next Bot commented Jul 8, 2025

Copy link
Copy Markdown
Contributor

Comment on lines +222 to +229
private static boolean hasDynamicStringParameters(MethodInvocationTree mit) {
for (ExpressionTree arg: mit.arguments()) {
if (arg.symbolType().is(JAVA_LANG_STRING) && arg.asConstant().isEmpty()) {
return true;
}
}
return false;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional not to support String.format(String format, Object... args) when args is not a String?

For example, if the argument is a int. Why does the rule not support the following case?

  public void formatIntVar(int input) throws SQLException {
    String query = String.format("SELECT %s", input);
    this.stmt.execute(query); // Noncompliant
  }

  public void formatIntConst() throws SQLException {
    String query = String.format("SELECT %s", 1);
    this.stmt.execute(query);
  }

Could be done with this kind of logic:

Suggested change
private static boolean hasDynamicStringParameters(MethodInvocationTree mit) {
for (ExpressionTree arg: mit.arguments()) {
if (arg.symbolType().is(JAVA_LANG_STRING) && arg.asConstant().isEmpty()) {
return true;
}
}
return false;
}
private static boolean hasDynamicStringParameters(MethodInvocationTree mit) {
boolean firstArg = true;
for (ExpressionTree arg: mit.arguments()) {
Type type = arg.symbolType();
boolean notTheFirstLocaleArgument = !firstArg || !type.isUnknown() || !type.is("java.util.Locale");
if (notTheFirstLocaleArgument && arg.asConstant().isEmpty()) {
return true;
}
firstArg = false;
}
return false;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to change it due to a failing test and then expanded it to exclude primitive arguments as well. Please see code comments for details.

// `format` has a variant with Locale as the first argument - we do not need to check that parameter.
boolean isFirstLocaleArgument = firstArg && !type.isUnknown() && type.is("java.util.Locale");
// Primitives will not lead to SQL injection, so the code is compliant.
if (!isFirstLocaleArgument && !type.isPrimitive() && arg.asConstant().isEmpty()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also use isPrimitiveWrapper

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

for (ExpressionTree arg: mit.arguments()) {
Type type = arg.symbolType();
// `format` has a variant with Locale as the first argument - we do not need to check that parameter.
boolean isFirstLocaleArgument = firstArg && !type.isUnknown() && type.is("java.util.Locale");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&& !type.isUnknown() seems useless, remove or invert the logic

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved and tested.

@sonarqube-next

Copy link
Copy Markdown
Contributor

@alban-auzeill
alban-auzeill merged commit 2214434 into master Jul 17, 2025
16 of 17 checks passed
@alban-auzeill
alban-auzeill deleted the tt/S2077 branch July 17, 2025 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants