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 * [#3701](https://github.com/pmd/pmd/issues/3701): \[java] MissingStaticMethodInNonInstantiatableClass false positive with method inner classes
* java-performance * java-performance
* [#3492](https://github.com/pmd/pmd/issues/3492): \[java] UselessStringValueOf: False positive when there is no initial String to append to * [#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) * [#3712](https://github.com/pmd/pmd/issues/3712): \[java] InsufficientStringBufferDeclaration false positive with StringBuilder.setLength(0)
* javascript * javascript
* [#3703](https://github.com/pmd/pmd/issues/3703): \[javascript] Error - no Node adapter class registered for XmlPropRef * [#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; package net.sourceforge.pmd.lang.java.rule.performance;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; 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.ASTLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTName; 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.rule.AbstractJavaRule;
import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
@ -56,15 +60,15 @@ public class UseStringBufferLengthRule extends AbstractJavaRule {
@Override @Override
public Object visit(ASTName decl, Object data) { public Object visit(ASTName decl, Object data) {
if (!decl.getImage().endsWith("toString")) { if (!decl.getImage().endsWith("toString")) {
return data; return super.visit(decl, data);
} }
NameDeclaration nd = decl.getNameDeclaration(); NameDeclaration nd = decl.getNameDeclaration();
if (nd == null) { if (nd == null) {
return data; return super.visit(decl, data);
} }
if (alreadySeen.contains(nd) || !(nd instanceof VariableNameDeclaration) if (alreadySeen.contains(nd) || !(nd instanceof VariableNameDeclaration)
|| !ConsecutiveLiteralAppendsRule.isStringBuilderOrBuffer(((VariableNameDeclaration) nd).getDeclaratorId())) { || !ConsecutiveLiteralAppendsRule.isStringBuilderOrBuffer(((VariableNameDeclaration) nd).getDeclaratorId())) {
return data; return super.visit(decl, data);
} }
alreadySeen.add(nd); alreadySeen.add(nd);
@ -72,7 +76,7 @@ public class UseStringBufferLengthRule extends AbstractJavaRule {
addViolation(data, decl); addViolation(data, decl);
} }
return data; return super.visit(decl, data);
} }
/** /**
@ -112,16 +116,39 @@ public class UseStringBufferLengthRule extends AbstractJavaRule {
// 3. child: equals // 3. child: equals
if (parent.getChild(2).hasImageEqualTo("equals")) { if (parent.getChild(2).hasImageEqualTo("equals")) {
// 4. child: the arguments of equals, there must be exactly one and // 4. child: the arguments of equals, there must be exactly one and
// it must be "" // it must be "" or a final variable initialized with ""
List<ASTArgumentList> argList = parent.getChild(3).findDescendantsOfType(ASTArgumentList.class); Node primarySuffix = parent.getChild(3);
if (argList.size() == 1) { List<ASTArgumentList> methodCalls = primarySuffix.findDescendantsOfType(ASTArgumentList.class);
List<ASTLiteral> literals = argList.get(0).findDescendantsOfType(ASTLiteral.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 literals.size() == 1 && literals.get(0).hasImageEqualTo("\"\"");
} }
} }
return false; 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) { private boolean isLengthViolation(Node parent) {
// 3. child: length // 3. child: length
return parent.getChild(2).hasImageEqualTo("length"); return parent.getChild(2).hasImageEqualTo("length");

View File

@ -188,6 +188,28 @@ public class Ineffecient
{ {
return "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> ]]></code>
</test-code> </test-code>