Merge pull request #3674 from adangel:issue-3639-usestringbufferlength

[java] UseStringBufferLength: false negative with empty string variable
#3674

* pr-3674:
  [java] UseStringBufferLength: false negative with empty string
variable
This commit is contained in:
Andreas Dangel
2022-01-20 15:25:16 +01:00
3 changed files with 58 additions and 8 deletions

View File

@ -54,6 +54,7 @@ not support all features of the latest EcmaScript standard.
* [#3701](https://github.com/pmd/pmd/issues/3701): \[java] MissingStaticMethodInNonInstantiatableClass false positive with method inner classes
* java-performance
* [#3492](https://github.com/pmd/pmd/issues/3492): \[java] UselessStringValueOf: False positive when there is no initial String to append to
* [#3639](https://github.com/pmd/pmd/issues/3639): \[java] UseStringBufferLength: false negative with empty string variable
* [#3712](https://github.com/pmd/pmd/issues/3712): \[java] InsufficientStringBufferDeclaration false positive with StringBuilder.setLength(0)
* javascript
* [#3703](https://github.com/pmd/pmd/issues/3703): \[javascript] Error - no Node adapter class registered for XmlPropRef

View File

@ -4,15 +4,19 @@
package net.sourceforge.pmd.lang.java.rule.performance;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
@ -56,15 +60,15 @@ public class UseStringBufferLengthRule extends AbstractJavaRule {
@Override
public Object visit(ASTName decl, Object data) {
if (!decl.getImage().endsWith("toString")) {
return data;
return super.visit(decl, data);
}
NameDeclaration nd = decl.getNameDeclaration();
if (nd == null) {
return data;
return super.visit(decl, data);
}
if (alreadySeen.contains(nd) || !(nd instanceof VariableNameDeclaration)
|| !ConsecutiveLiteralAppendsRule.isStringBuilderOrBuffer(((VariableNameDeclaration) nd).getDeclaratorId())) {
return data;
return super.visit(decl, data);
}
alreadySeen.add(nd);
@ -72,7 +76,7 @@ public class UseStringBufferLengthRule extends AbstractJavaRule {
addViolation(data, decl);
}
return data;
return super.visit(decl, data);
}
/**
@ -112,16 +116,39 @@ public class UseStringBufferLengthRule extends AbstractJavaRule {
// 3. child: equals
if (parent.getChild(2).hasImageEqualTo("equals")) {
// 4. child: the arguments of equals, there must be exactly one and
// it must be ""
List<ASTArgumentList> argList = parent.getChild(3).findDescendantsOfType(ASTArgumentList.class);
if (argList.size() == 1) {
List<ASTLiteral> literals = argList.get(0).findDescendantsOfType(ASTLiteral.class);
// it must be "" or a final variable initialized with ""
Node primarySuffix = parent.getChild(3);
List<ASTArgumentList> methodCalls = primarySuffix.findDescendantsOfType(ASTArgumentList.class);
if (methodCalls.size() == 1 && methodCalls.get(0).size() == 1) {
ASTExpression firstArgument = primarySuffix.getChild(0).getFirstChildOfType(ASTArgumentList.class)
.findChildrenOfType(ASTExpression.class).get(0);
List<ASTLiteral> literals = firstArgument.findDescendantsOfType(ASTLiteral.class);
if (literals.isEmpty()) {
literals = findLiteralsInVariableInitializer(firstArgument);
}
return literals.size() == 1 && literals.get(0).hasImageEqualTo("\"\"");
}
}
return false;
}
private List<ASTLiteral> findLiteralsInVariableInitializer(ASTExpression firstArgument) {
List<ASTName> varAccess = firstArgument.findDescendantsOfType(ASTName.class);
if (varAccess.size() == 1) {
NameDeclaration nameDeclaration = varAccess.get(0).getNameDeclaration();
if (nameDeclaration != null && nameDeclaration.getNode() instanceof ASTVariableDeclaratorId) {
ASTVariableDeclaratorId varId = (ASTVariableDeclaratorId) nameDeclaration.getNode();
if (varId.isFinal() && varId.getParent() instanceof ASTVariableDeclarator) {
ASTVariableDeclarator declarator = (ASTVariableDeclarator) varId.getParent();
if (declarator.getInitializer() != null) {
return declarator.getInitializer().findDescendantsOfType(ASTLiteral.class);
}
}
}
}
return Collections.emptyList();
}
private boolean isLengthViolation(Node parent) {
// 3. child: length
return parent.getChild(2).hasImageEqualTo("length");

View File

@ -188,6 +188,28 @@ public class Ineffecient
{
return "Ineffecient";
}
}
]]></code>
</test-code>
<test-code>
<description>[java] UseStringBufferLength: false negative with empty string variable #3639</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>5</expected-linenumbers>
<code><![CDATA[
public class Foo {
public void foo() {
StringBuffer sb = new StringBuffer("any_string");
final String nullStr = "";
if (sb.toString().equals(nullStr)) { // PMD should report a warning here
System.out.println("Buffer is empty");
}
// the preferred way
if (sb.length() == 0) {
System.out.println("Buffer is empty");
}
}
}
]]></code>
</test-code>