From 700d029932d22fcb4889ff3072d30c5b5c7a70dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 7 Aug 2020 04:18:33 +0200 Subject: [PATCH 01/45] Make usage resolver --- .../java/ast/ASTVariableDeclaratorId.java | 30 ++++++- .../pmd/lang/java/ast/InternalApiBridge.java | 15 ++++ .../pmd/lang/java/ast/JavaVisitorBase.java | 16 ++-- .../lang/java/internal/JavaAstProcessor.java | 1 + .../rule/AbstractInefficientZeroCheck.java | 2 +- .../java/rule/AbstractPoorMethodCall.java | 2 +- .../AvoidReassigningCatchVariablesRule.java | 2 +- .../UnusedLocalVariableRule.java | 2 +- .../rule/codestyle/UnnecessaryCastRule.java | 2 +- .../java/rule/design/SingularFieldRule.java | 2 +- .../rule/errorprone/CheckSkipResultRule.java | 2 +- .../UselessOperationOnImmutableRule.java | 2 +- .../UnsynchronizedStaticFormatterRule.java | 2 +- ...sufficientStringBufferDeclarationRule.java | 2 +- .../UseStringBufferForStringAppendsRule.java | 2 +- .../internal/ImplicitMemberSymbols.java | 15 +++- .../lang/java/symboltable/AcceptanceTest.java | 8 +- .../pmd/lang/java/ast/UsageResolutionTest.kt | 79 +++++++++++++++++++ 18 files changed, 161 insertions(+), 25 deletions(-) create mode 100644 pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/UsageResolutionTest.kt diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java index 3da8b10f39..35aa495fb6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java @@ -4,6 +4,8 @@ package net.sourceforge.pmd.lang.java.ast; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; @@ -12,6 +14,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.annotation.Experimental; import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; import net.sourceforge.pmd.lang.rule.xpath.DeprecatedAttribute; @@ -33,7 +36,7 @@ import net.sourceforge.pmd.lang.symboltable.NameOccurrence; * *

Since this node conventionally represents the declared variable in PMD, our symbol table * populates it with a {@link VariableNameDeclaration}, and its usages can be accessed through - * the method {@link #getUsages()}. + * the method {@link #oldGetUsages ()}. * *

Type resolution assigns the type of the variable to this node. See {@link #getType()}'s * documentation for the contract of this method. @@ -51,6 +54,8 @@ public final class ASTVariableDeclaratorId extends AbstractTypedSymbolDeclarator private VariableNameDeclaration nameDeclaration; + private List usages = Collections.emptyList(); + ASTVariableDeclaratorId(int id) { super(id); } @@ -73,10 +78,30 @@ public final class ASTVariableDeclaratorId extends AbstractTypedSymbolDeclarator nameDeclaration = decl; } - public List getUsages() { + /** + * @deprecated transitional, use {@link #getUsages()} + */ + @Deprecated + public List oldGetUsages() { return getScope().getDeclarations(VariableNameDeclaration.class).get(nameDeclaration); } + /** + * Returns an unmodifiable list of the usages of this variable that + * are made in this file. Note that for a record component, this returns + * usages both for the formal parameter symbol and its field counterpart. + */ + public List getUsages() { + return usages; + } + + void addUsage(ASTNamedReferenceExpr usage) { + if (usages.isEmpty()) { + usages = new ArrayList<>(4); //make modifiable + } + usages.add(usage); + } + /** * Returns the extra array dimensions associated with this variable. * For example in the declaration {@code int a[]}, {@link #getTypeNode()} @@ -113,7 +138,6 @@ public final class ASTVariableDeclaratorId extends AbstractTypedSymbolDeclarator /** * @deprecated Use {@link #getName()} - * @return */ @Override @DeprecatedAttribute(replaceWith = "@Name") diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java index 756f81a3cf..3f50e561bc 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java @@ -10,6 +10,7 @@ import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.NodeStream; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccTokenDocument; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; import net.sourceforge.pmd.lang.java.internal.JavaAstProcessor; import net.sourceforge.pmd.lang.java.symbols.JClassSymbol; import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol; @@ -122,6 +123,20 @@ public final class InternalApiBridge { AstDisambiguationPass.disambigWithCtx(nodes, ctx); } + public static void usageResolution(JavaAstProcessor processor, ASTCompilationUnit root) { + root.descendants(ASTNamedReferenceExpr.class) + .crossFindBoundaries() + .forEach(node -> { + JVariableSymbol sym = node.getReferencedSym(); + if (sym != null) { + ASTVariableDeclaratorId reffed = sym.tryGetNode(); + if (reffed != null) { // declared in this file + reffed.addUsage(node); + } + } + }); + } + public static @Nullable JTypeMirror getTypeMirrorInternal(TypeNode node) { return ((AbstractJavaTypeNode) node).getTypeMirrorInternal(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaVisitorBase.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaVisitorBase.java index f984b710b0..ef18296cef 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaVisitorBase.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaVisitorBase.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.lang.java.ast; import net.sourceforge.pmd.lang.ast.AstVisitorBase; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; /** * Base implementation of {@link JavaVisitor}. This adds delegation logic @@ -185,11 +186,6 @@ public class JavaVisitorBase extends AstVisitorBase implements JavaV return visitPrimaryExpr(node, data); } - @Override - public R visit(ASTFieldAccess node, P data) { - return visitPrimaryExpr(node, data); - } - @Override public R visit(ASTConstructorCall node, P data) { return visitPrimaryExpr(node, data); @@ -207,10 +203,18 @@ public class JavaVisitorBase extends AstVisitorBase implements JavaV return visitPrimaryExpr(node, data); } + public R visitNamedExpr(ASTNamedReferenceExpr node, P data) { + return visitPrimaryExpr(node, data); + } @Override public R visit(ASTVariableAccess node, P data) { - return visitPrimaryExpr(node, data); + return visitNamedExpr(node, data); + } + + @Override + public R visit(ASTFieldAccess node, P data) { + return visitNamedExpr(node, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaAstProcessor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaAstProcessor.java index 7598a3ccbf..d974baa460 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaAstProcessor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaAstProcessor.java @@ -143,6 +143,7 @@ public final class JavaAstProcessor { bench("2. Symbol table resolution", () -> SymbolTableResolver.traverse(this, acu)); bench("3. AST disambiguation", () -> InternalApiBridge.disambigWithCtx(NodeStream.of(acu), ReferenceCtx.root(this, acu))); bench("4. Comment assignment", () -> InternalApiBridge.assignComments(acu)); + bench("5. Usage resolution", () -> InternalApiBridge.usageResolution(this, acu)); } public TypeSystem getTypeSystem() { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractInefficientZeroCheck.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractInefficientZeroCheck.java index 499e5efc9f..45a0cd3ab7 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractInefficientZeroCheck.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractInefficientZeroCheck.java @@ -69,7 +69,7 @@ public abstract class AbstractInefficientZeroCheck extends AbstractJavaRule { return data; } - List declars = node.getUsages(); + List declars = node.oldGetUsages(); for (NameOccurrence occ : declars) { JavaNameOccurrence jocc = (JavaNameOccurrence) occ; if (!isTargetMethod(jocc)) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractPoorMethodCall.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractPoorMethodCall.java index b869265b19..a002bb54ce 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractPoorMethodCall.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractPoorMethodCall.java @@ -99,7 +99,7 @@ public abstract class AbstractPoorMethodCall extends AbstractJavaRule { return data; } - for (NameOccurrence occ : node.getUsages()) { + for (NameOccurrence occ : node.oldGetUsages()) { JavaNameOccurrence jocc = (JavaNameOccurrence) occ; if (isNotedMethod(jocc.getNameForWhichThisIsAQualifier())) { Node parent = jocc.getLocation().getParent().getParent(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesRule.java index 108f038fac..6c36c60109 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesRule.java @@ -23,7 +23,7 @@ public class AvoidReassigningCatchVariablesRule extends AbstractJavaRule { public Object visit(ASTCatchClause catchStatement, Object data) { ASTVariableDeclaratorId caughtExceptionId = catchStatement.getParameter().getVarId(); String caughtExceptionVar = caughtExceptionId.getName(); - for (NameOccurrence usage : caughtExceptionId.getUsages()) { + for (NameOccurrence usage : caughtExceptionId.oldGetUsages()) { JavaNode operation = getOperationOfUsage(usage); if (isAssignment(operation)) { String assignedVar = getAssignedVariableName(operation); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java index fea5d5696f..bca15429ec 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java @@ -29,7 +29,7 @@ public class UnusedLocalVariableRule extends AbstractJavaRule { // TODO this isArray() check misses some cases // need to add DFAish code to determine if an array // is initialized locally or gotten from somewhere else - if (!node.getNameDeclaration().isArray() && !actuallyUsed(node.getUsages())) { + if (!node.getNameDeclaration().isArray() && !actuallyUsed(node.oldGetUsages())) { addViolation(data, node, node.getNameDeclaration().getImage()); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryCastRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryCastRule.java index 82fd6bdedb..a726cb489c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryCastRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryCastRule.java @@ -90,7 +90,7 @@ public class UnnecessaryCastRule extends AbstractJavaRule { return; } ASTVariableDeclaratorId decl = node.getFirstDescendantOfType(ASTVariableDeclaratorId.class); - List usages = decl.getUsages(); + List usages = decl.oldGetUsages(); for (NameOccurrence no : usages) { ASTCastExpression castExpression = findCastExpression(no.getLocation()); if (castExpression != null) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java index ce1777ce48..00c43a4a13 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java @@ -88,7 +88,7 @@ public class SingularFieldRule extends AbstractLombokAwareRule { for (ASTVariableDeclarator declarator : node.findChildrenOfType(ASTVariableDeclarator.class)) { ASTVariableDeclaratorId declaration = (ASTVariableDeclaratorId) declarator.getChild(0); - List usages = declaration.getUsages(); + List usages = declaration.oldGetUsages(); Node decl = null; boolean violation = true; for (int ix = 0; ix < usages.size(); ix++) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultRule.java index 3b5710f780..1a0a4484cf 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultRule.java @@ -24,7 +24,7 @@ public class CheckSkipResultRule extends AbstractJavaRule { if (!TypeTestUtil.isA(InputStream.class, node.getTypeNode())) { return data; } - for (NameOccurrence occ : node.getUsages()) { + for (NameOccurrence occ : node.oldGetUsages()) { JavaNameOccurrence jocc = (JavaNameOccurrence) occ; NameOccurrence qualifier = jocc.getNameForWhichThisIsAQualifier(); if (qualifier != null && "skip".equals(qualifier.getImage())) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UselessOperationOnImmutableRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UselessOperationOnImmutableRule.java index aae47c5a6e..6b44f359fa 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UselessOperationOnImmutableRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UselessOperationOnImmutableRule.java @@ -70,7 +70,7 @@ public class UselessOperationOnImmutableRule extends AbstractJavaRule { return super.visit(node, data); } String variableName = var.getImage(); - for (NameOccurrence no : var.getUsages()) { + for (NameOccurrence no : var.oldGetUsages()) { // FIXME - getUsages will return everything with the same name as // the variable, // see JUnit test, case 6. Changing to Node below, revisit when diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticFormatterRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticFormatterRule.java index 414d547b90..1e1fccfeb6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticFormatterRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticFormatterRule.java @@ -70,7 +70,7 @@ public class UnsynchronizedStaticFormatterRule extends AbstractJavaRule { return data; } } - for (NameOccurrence occ : var.getUsages()) { + for (NameOccurrence occ : var.oldGetUsages()) { Node n = occ.getLocation(); // ignore usages, that don't call a method. if (!n.getImage().contains(".")) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java index 3aeeffa35a..35a59e8534 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java @@ -67,7 +67,7 @@ public class InsufficientStringBufferDeclarationRule extends AbstractJavaRule { anticipatedLength += getConstructorAppendsLength(node); - List usage = node.getUsages(); + List usage = node.oldGetUsages(); Map> blocks = new HashMap<>(); for (NameOccurrence no : usage) { JavaNameOccurrence jno = (JavaNameOccurrence) no; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsRule.java index ac91f98e46..46a27b763c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsRule.java @@ -46,7 +46,7 @@ public class UseStringBufferForStringAppendsRule extends AbstractJavaRule { // Remember how often we the variable has been used int usageCounter = 0; - for (NameOccurrence no : node.getUsages()) { + for (NameOccurrence no : node.oldGetUsages()) { Node name = no.getLocation(); ASTStatementExpression statement = name.getFirstParentOfType(ASTStatementExpression.class); if (statement == null) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ImplicitMemberSymbols.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ImplicitMemberSymbols.java index 24584575b8..9cc2c0caae 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ImplicitMemberSymbols.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ImplicitMemberSymbols.java @@ -13,7 +13,9 @@ import java.util.function.BiFunction; import java.util.function.Function; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.symbols.JClassSymbol; import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol; import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol; @@ -115,7 +117,7 @@ public final class ImplicitMemberSymbols { modifiers, CollectionUtil.map( recordComponents, - f -> c -> new FakeFormalParamSym(c, f.getSimpleName(), (ts, sym) -> f.getTypeMirror(Substitution.EMPTY)) + f -> c -> new FakeFormalParamSym(c, f.getSimpleName(), f.tryGetNode(), (ts, sym) -> f.getTypeMirror(Substitution.EMPTY)) ) ); } @@ -279,14 +281,25 @@ public final class ImplicitMemberSymbols { private final JExecutableSymbol owner; private final String name; + private final ASTVariableDeclaratorId node; private final BiFunction type; private FakeFormalParamSym(JExecutableSymbol owner, String name, BiFunction type) { + this(owner, name, null, type); + } + + private FakeFormalParamSym(JExecutableSymbol owner, String name, @Nullable ASTVariableDeclaratorId node, BiFunction type) { this.owner = owner; this.name = name; + this.node = node; this.type = type; } + @Override + public @Nullable ASTVariableDeclaratorId tryGetNode() { + return node; + } + @Override public TypeSystem getTypeSystem() { return owner.getTypeSystem(); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java index b229cee8d8..d60e2869a8 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java @@ -88,8 +88,8 @@ public class AcceptanceTest extends BaseNonParserTest { ASTVariableDeclaratorId declaration = acu.findDescendantsOfType(ASTVariableDeclaratorId.class).get(1); assertEquals(3, declaration.getBeginLine()); assertEquals("bbbbbbbbbb", declaration.getImage()); - assertEquals(1, declaration.getUsages().size()); - NameOccurrence no = declaration.getUsages().get(0); + assertEquals(1, declaration.oldGetUsages().size()); + NameOccurrence no = declaration.oldGetUsages().get(0); Node location = no.getLocation(); assertEquals(6, location.getBeginLine()); // System.out.println("variable " + declaration.getImage() + " is used @@ -124,7 +124,7 @@ public class AcceptanceTest extends BaseNonParserTest { ASTCompilationUnit acu = parseCode(NameOccurrencesTest.TEST_ENUM); ASTVariableDeclaratorId vdi = acu.findDescendantsOfType(ASTVariableDeclaratorId.class).get(0); - List usages = vdi.getUsages(); + List usages = vdi.oldGetUsages(); assertEquals(2, usages.size()); assertEquals(5, usages.get(0).getLocation().getBeginLine()); assertEquals(9, usages.get(1).getLocation().getBeginLine()); @@ -135,7 +135,7 @@ public class AcceptanceTest extends BaseNonParserTest { ASTCompilationUnit acu = parseCode(TEST_INNER_CLASS); ASTVariableDeclaratorId vdi = acu.findDescendantsOfType(ASTClassOrInterfaceDeclaration.class).get(1) // get inner class .getFirstDescendantOfType(ASTVariableDeclaratorId.class); // get first declaration - List usages = vdi.getUsages(); + List usages = vdi.oldGetUsages(); assertEquals(2, usages.size()); assertEquals(5, usages.get(0).getLocation().getBeginLine()); assertEquals(10, usages.get(1).getLocation().getBeginLine()); diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/UsageResolutionTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/UsageResolutionTest.kt new file mode 100644 index 0000000000..f7951cf9ba --- /dev/null +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/UsageResolutionTest.kt @@ -0,0 +1,79 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.java.ast + +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.collections.shouldBeSingleton +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.shouldBe +import net.sourceforge.pmd.lang.ast.test.shouldBe +import net.sourceforge.pmd.lang.ast.test.shouldBeA +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.AccessType.WRITE +import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol +import net.sourceforge.pmd.lang.java.symbols.JFormalParamSymbol + +class UsageResolutionTest : ProcessorTestSpec({ + + parserTest("Test usage resolution") { + val acu = parser.parse(""" + class Bar { + int f1; + { + this.f1 = 4; + } + } + class Foo extends Bar { + int f1; // hides Bar#f1 + int f2; + { + int f2 = 0; + super.f1 = 0; + f1 = 0; + this.f1 = 0; + } + { + int f2 = 0; + f2 = this.f2; + } + } + """) + val (barF1, fooF1, fooF2, localF2, localF22) = acu.descendants(ASTVariableDeclaratorId::class.java).toList() + barF1.usages.map { it.text.toString() }.shouldContainExactly("this.f1", "super.f1") + fooF1.usages.map { it.text.toString() }.shouldContainExactly("f1", "this.f1") + fooF2.usages.map { it.text.toString() }.shouldContainExactly("this.f2") + localF2.usages.shouldBeEmpty() + localF22.usages.shouldBeSingleton { + it.accessType shouldBe WRITE + } + } + + parserTest("Test record components") { + val acu = parser.parse(""" + record Foo(int p) { + Foo { + p = 10; + } + + void pPlus1() { return p + 1; } + } + """) + + val (p) = acu.descendants(ASTVariableDeclaratorId::class.java).toList() + + p::isRecordComponent shouldBe true + p.usages.shouldHaveSize(2) + p.usages[0].shouldBeA { + it.referencedSym!!.shouldBeA { + it.tryGetNode() shouldBe p + } + } + p.usages[1].shouldBeA { + it.referencedSym!!.shouldBeA { + it.tryGetNode() shouldBe p + } + } + } + +}) From bbc71da28926ca6f0242eb7fe31f2298a031ba8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 30 Oct 2020 02:05:38 +0100 Subject: [PATCH 02/45] Update UnusedLocalVariable --- .travis/all-java.xml | 2 +- .../UnusedLocalVariableRule.java | 44 +++++++++---------- .../UnusedLocalVariableTest.java | 1 - .../bestpractices/xml/UnusedLocalVariable.xml | 4 +- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/.travis/all-java.xml b/.travis/all-java.xml index 72ed667e22..9b76f4b53c 100644 --- a/.travis/all-java.xml +++ b/.travis/all-java.xml @@ -51,7 +51,7 @@ - + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java index bca15429ec..2c96b0f5cc 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java @@ -4,46 +4,42 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; -import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; +import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.AccessType; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; +import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence; -import net.sourceforge.pmd.lang.symboltable.NameOccurrence; +import net.sourceforge.pmd.lang.rule.RuleTargetSelector; +import net.sourceforge.pmd.util.CollectionUtil; public class UnusedLocalVariableRule extends AbstractJavaRule { - public UnusedLocalVariableRule() { - addRuleChainVisit(ASTLocalVariableDeclaration.class); + @Override + protected @NonNull RuleTargetSelector buildTargetSelector() { + return RuleTargetSelector.forTypes(ASTLocalVariableDeclaration.class); } @Override public Object visit(ASTLocalVariableDeclaration decl, Object data) { - for (int i = 0; i < decl.getNumChildren(); i++) { - if (!(decl.getChild(i) instanceof ASTVariableDeclarator)) { - continue; - } - ASTVariableDeclaratorId node = (ASTVariableDeclaratorId) decl.getChild(i).getChild(0); - // TODO this isArray() check misses some cases - // need to add DFAish code to determine if an array - // is initialized locally or gotten from somewhere else - if (!node.getNameDeclaration().isArray() && !actuallyUsed(node.oldGetUsages())) { - addViolation(data, node, node.getNameDeclaration().getImage()); + for (ASTVariableDeclaratorId varId : decl.getVarIds()) { + if (hasReadUsage(varId)) { + addViolation(data, varId, varId.getName()); } } return data; } - private boolean actuallyUsed(List usages) { - for (NameOccurrence occ : usages) { - JavaNameOccurrence jocc = (JavaNameOccurrence) occ; - if (!jocc.isOnLeftHandSide()) { - return true; - } - } - return false; + static boolean hasReadUsage(ASTVariableDeclaratorId varId) { + return CollectionUtil.none(varId.getUsages(), UnusedLocalVariableRule::isReadUsage); } + private static boolean isReadUsage(ASTNamedReferenceExpr expr) { + return expr.getAccessType() == AccessType.READ + // foo(x++) + || expr.getParent() instanceof ASTUnaryExpression && expr.getParent().getParent() instanceof ASTArgumentList; + } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableTest.java index 3f3885a95a..aad6d106c4 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class UnusedLocalVariableTest extends PmdRuleTst { // no additional unit tests } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedLocalVariable.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedLocalVariable.xml index 4c7aadbb11..75afe6e196 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedLocalVariable.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedLocalVariable.xml @@ -153,8 +153,8 @@ public class Foo { - a compound assignment operator doth a usage make - 0 + a compound assignment operator does not a usage make + 1 Date: Fri, 30 Oct 2020 02:14:25 +0100 Subject: [PATCH 03/45] Update UnusedPrivateField --- .travis/all-java.xml | 2 +- .../UnusedLocalVariableRule.java | 4 +- .../bestpractices/UnusedPrivateFieldRule.java | 121 +++++------------- .../bestpractices/UnusedPrivateFieldTest.java | 19 --- .../bestpractices/xml/UnusedPrivateField.xml | 6 +- 5 files changed, 36 insertions(+), 116 deletions(-) diff --git a/.travis/all-java.xml b/.travis/all-java.xml index 9b76f4b53c..2b6b3372a3 100644 --- a/.travis/all-java.xml +++ b/.travis/all-java.xml @@ -52,7 +52,7 @@ - + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java index 2c96b0f5cc..30d3aa2a32 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java @@ -26,14 +26,14 @@ public class UnusedLocalVariableRule extends AbstractJavaRule { @Override public Object visit(ASTLocalVariableDeclaration decl, Object data) { for (ASTVariableDeclaratorId varId : decl.getVarIds()) { - if (hasReadUsage(varId)) { + if (isNeverUsed(varId)) { addViolation(data, varId, varId.getName()); } } return data; } - static boolean hasReadUsage(ASTVariableDeclaratorId varId) { + static boolean isNeverUsed(ASTVariableDeclaratorId varId) { return CollectionUtil.none(varId.getUsages(), UnusedLocalVariableRule::isReadUsage); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java index fe270853a6..b4f448308d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java @@ -6,28 +6,24 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.ArrayList; import java.util.Collection; -import java.util.List; -import java.util.Map; -import net.sourceforge.pmd.lang.java.ast.ASTBodyDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTEnumBody; -import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; -import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; -import net.sourceforge.pmd.lang.java.ast.ASTTypeBody; -import net.sourceforge.pmd.lang.java.ast.AccessNode; -import net.sourceforge.pmd.lang.java.ast.Annotatable; +import org.checkerframework.checker.nullness.qual.NonNull; + +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.ast.AccessNode.Visibility; +import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.rule.AbstractLombokAwareRule; -import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence; -import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; -import net.sourceforge.pmd.lang.symboltable.NameDeclaration; -import net.sourceforge.pmd.lang.symboltable.NameOccurrence; +import net.sourceforge.pmd.lang.rule.RuleTargetSelector; public class UnusedPrivateFieldRule extends AbstractLombokAwareRule { + @Override + protected @NonNull RuleTargetSelector buildTargetSelector() { + return RuleTargetSelector.forTypes(ASTAnyTypeDeclaration.class); + } + @Override protected Collection defaultSuppressionAnnotations() { Collection defaultValues = new ArrayList<>(super.defaultSuppressionAnnotations()); @@ -39,89 +35,32 @@ public class UnusedPrivateFieldRule extends AbstractLombokAwareRule { } @Override - public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - boolean classHasLombok = hasLombokAnnotation(node); - - Map> vars = node.getScope() - .getDeclarations(VariableNameDeclaration.class); - for (Map.Entry> entry : vars.entrySet()) { - VariableNameDeclaration decl = entry.getKey(); - AccessNode accessNodeParent = decl.getAccessNodeParent(); - if (!accessNodeParent.isPrivate() || isOK(decl.getImage()) || classHasLombok - || hasIgnoredAnnotation((Annotatable) accessNodeParent) - || hasIgnoredAnnotation(node)) { - continue; - } - if (!actuallyUsed(entry.getValue())) { - if (!usedInOuterClass(node, decl) && !usedInOuterEnum(node, decl)) { - addViolation(data, decl.getNode(), decl.getImage()); - } - } - } - return super.visit(node, data); - } - - private boolean usedInOuterEnum(ASTClassOrInterfaceDeclaration node, NameDeclaration decl) { - List outerEnums = node.getParentsOfType(ASTEnumDeclaration.class); - for (ASTEnumDeclaration outerEnum : outerEnums) { - ASTEnumBody enumBody = outerEnum.getFirstChildOfType(ASTEnumBody.class); - if (usedInOuter(decl, enumBody)) { - return true; - } - } - return false; - } - - /** - * Find out whether the variable is used in an outer class - */ - private boolean usedInOuterClass(ASTClassOrInterfaceDeclaration node, NameDeclaration decl) { - List outerClasses = node.getParentsOfType(ASTClassOrInterfaceDeclaration.class); - for (ASTClassOrInterfaceDeclaration outerClass : outerClasses) { - ASTClassOrInterfaceBody classOrInterfaceBody = outerClass - .getFirstChildOfType(ASTClassOrInterfaceBody.class); - if (usedInOuter(decl, classOrInterfaceBody)) { - return true; - } - } - return false; - } - - private boolean usedInOuter(NameDeclaration decl, ASTTypeBody body) { - for (ASTBodyDeclaration node : body.toStream()) { - for (ASTPrimarySuffix primarySuffix : node.findDescendantsOfType(ASTPrimarySuffix.class, true)) { - if (decl.getImage().equals(primarySuffix.getImage())) { - return true; // No violation - } + public Object visitJavaNode(JavaNode node, Object data) { + if (node instanceof ASTAnyTypeDeclaration) { + ASTAnyTypeDeclaration type = (ASTAnyTypeDeclaration) node; + if (hasIgnoredAnnotation(type) || hasLombokAnnotation(type)) { + return null; } - for (ASTPrimaryPrefix primaryPrefix : node.findDescendantsOfType(ASTPrimaryPrefix.class, true)) { - ASTName name = primaryPrefix.getFirstDescendantOfType(ASTName.class); - - if (name != null) { - for (String id : name.getImage().split("\\.")) { - if (id.equals(decl.getImage())) { - return true; // No violation + for (ASTFieldDeclaration field : type.getDeclarations() + .filterIs(ASTFieldDeclaration.class)) { + if (field.getVisibility() == Visibility.V_PRIVATE + && !hasIgnoredAnnotation(field)) { + for (ASTVariableDeclaratorId varId : field.getVarIds()) { + if (!isOK(varId) && UnusedLocalVariableRule.isNeverUsed(varId)) { + addViolation(data, varId, varId.getName()); } } } } } - return false; + return null; } - private boolean actuallyUsed(List usages) { - for (NameOccurrence nameOccurrence : usages) { - JavaNameOccurrence jNameOccurrence = (JavaNameOccurrence) nameOccurrence; - if (!jNameOccurrence.isOnLeftHandSide()) { - return true; - } - } - return false; - } - - private boolean isOK(String image) { - return "serialVersionUID".equals(image) || "serialPersistentFields".equals(image) || "IDENT".equals(image); + private boolean isOK(ASTVariableDeclaratorId node) { + return "serialVersionUID".equals(node.getName()) + || "serialPersistentFields".equals(node.getName()) + || "IDENT".equals(node.getName()); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldTest.java index 16da41a239..09e8be52e3 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldTest.java @@ -4,27 +4,8 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; -import org.junit.Assert; -import org.junit.Test; - import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class UnusedPrivateFieldTest extends PmdRuleTst { - /** - * This test will fail, as soon Lombok classes are on the test classpath. - * The test classpath is used as auxclasspath during unit tests. - * If lombok is present, then the test case for #1952 will never fail - * and won't reproduce the false-negative case anymore. - */ - @Test - public void makeSureLombokIsNotOnClasspath() { - try { - Class.forName("lombok.Value"); - Assert.fail(); - } catch (ClassNotFoundException e) { - // this is ok - } - } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateField.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateField.xml index efd39eb122..e9e45ad143 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateField.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateField.xml @@ -466,7 +466,7 @@ public class Foo { #1420 UnusedPrivateField: Ignore fields if using lombok - 3 0 #1420 UnusedPrivateField: Ignore fields if using lombok - 7 0 1 6 Date: Fri, 30 Oct 2020 02:30:14 +0100 Subject: [PATCH 04/45] Proto for method usages --- .../pmd/lang/java/ast/ASTMethodCall.java | 3 +- .../lang/java/ast/ASTMethodDeclaration.java | 20 ++++++++++ .../pmd/lang/java/ast/ASTMethodReference.java | 6 ++- .../pmd/lang/java/ast/InternalApiBridge.java | 38 ++++++++++++++++--- .../pmd/lang/java/ast/MethodUsage.java | 16 ++++++++ 5 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodUsage.java diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodCall.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodCall.java index e27673e5ff..bd06c74068 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodCall.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodCall.java @@ -22,7 +22,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; public final class ASTMethodCall extends AbstractInvocationExpr implements ASTPrimaryExpression, QualifiableExpression, - InvocationNode { + InvocationNode, + MethodUsage { ASTMethodCall(int id) { super(id); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java index 7dcccf0756..721e027b3d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java @@ -4,6 +4,10 @@ package net.sourceforge.pmd.lang.java.ast; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -40,6 +44,8 @@ import net.sourceforge.pmd.lang.rule.xpath.DeprecatedAttribute; */ public final class ASTMethodDeclaration extends AbstractMethodOrConstructorDeclaration { + private List usages = Collections.emptyList(); + ASTMethodDeclaration(int id) { super(id); } @@ -50,6 +56,20 @@ public final class ASTMethodDeclaration extends AbstractMethodOrConstructorDecla return visitor.visit(this, data); } + /** + * Returns the usages of the method made in this file. Because of this, + * this list will be incomplete if the method is not effectively private. + */ + public List getUsages() { + return usages; + } + + void addUsage(MethodUsage u) { + if (usages.isEmpty()) { + usages = new ArrayList<>(2); + } + usages.add(u); + } /** * Returns true if this method is overridden. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java index 79d97e486a..a68b5db08a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java @@ -22,7 +22,11 @@ import net.sourceforge.pmd.lang.java.types.TypeSystem; * * */ -public final class ASTMethodReference extends AbstractJavaExpr implements ASTPrimaryExpression, QualifiableExpression, LeftRecursiveNode { +public final class ASTMethodReference extends AbstractJavaExpr + implements ASTPrimaryExpression, + QualifiableExpression, + LeftRecursiveNode, + MethodUsage { private JMethodSig functionalMethod; private JMethodSig compileTimeDecl; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java index 3f50e561bc..593b6c075e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java @@ -15,6 +15,7 @@ import net.sourceforge.pmd.lang.java.internal.JavaAstProcessor; import net.sourceforge.pmd.lang.java.symbols.JClassSymbol; import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol; import net.sourceforge.pmd.lang.java.symbols.JElementSymbol; +import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol; import net.sourceforge.pmd.lang.java.symbols.JMethodSymbol; import net.sourceforge.pmd.lang.java.symbols.JTypeParameterSymbol; import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol; @@ -124,15 +125,40 @@ public final class InternalApiBridge { } public static void usageResolution(JavaAstProcessor processor, ASTCompilationUnit root) { - root.descendants(ASTNamedReferenceExpr.class) + root.descendants() .crossFindBoundaries() .forEach(node -> { - JVariableSymbol sym = node.getReferencedSym(); - if (sym != null) { - ASTVariableDeclaratorId reffed = sym.tryGetNode(); - if (reffed != null) { // declared in this file - reffed.addUsage(node); + if (node instanceof ASTNamedReferenceExpr) { + // variable/field usages + ASTNamedReferenceExpr ref = (ASTNamedReferenceExpr) node; + JVariableSymbol sym = ref.getReferencedSym(); + if (sym != null) { + ASTVariableDeclaratorId reffed = sym.tryGetNode(); + if (reffed != null) { // declared in this file + reffed.addUsage(ref); + } } + } else if (node instanceof ASTMethodCall) { + // method usages + OverloadSelectionResult overload = ((ASTMethodCall) node).getOverloadSelectionInfo(); + if (!overload.isFailed()) { + JExecutableSymbol symbol = overload.getMethodType().getSymbol(); + JavaNode reffed = symbol.tryGetNode(); + if (reffed instanceof ASTMethodDeclaration) { + ((ASTMethodDeclaration) reffed).addUsage((ASTMethodCall) node); + } + } + } else if (node instanceof ASTMethodReference) { + // method usages + OverloadSelectionResult overload = ((ASTMethodCall) node).getOverloadSelectionInfo(); + if (!overload.isFailed()) { + JExecutableSymbol symbol = overload.getMethodType().getSymbol(); + JavaNode reffed = symbol.tryGetNode(); + if (reffed instanceof ASTMethodDeclaration) { + ((ASTMethodDeclaration) reffed).addUsage((ASTMethodCall) node); + } + } + } }); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodUsage.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodUsage.java new file mode 100644 index 0000000000..060da507d7 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodUsage.java @@ -0,0 +1,16 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +/** + * A node that uses another method. Those are {@link ASTMethodCall#getMethodType() MethodCall} + * and {@link ASTMethodReference#getReferencedMethod() MethodReference}. + * + * TODO should these method be named the same, and added to this interface? + */ +public interface MethodUsage { + + +} From 5c902a47f943ff7152fe3b8a967462cc7fbd3528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 30 Oct 2020 02:31:05 +0100 Subject: [PATCH 05/45] Revert "Proto for method usages" This reverts commit 0cf084db5befb78579b05468014b3a26509d494a. --- .../pmd/lang/java/ast/ASTMethodCall.java | 3 +- .../lang/java/ast/ASTMethodDeclaration.java | 20 ---------- .../pmd/lang/java/ast/ASTMethodReference.java | 6 +-- .../pmd/lang/java/ast/InternalApiBridge.java | 38 +++---------------- .../pmd/lang/java/ast/MethodUsage.java | 16 -------- 5 files changed, 8 insertions(+), 75 deletions(-) delete mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodUsage.java diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodCall.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodCall.java index bd06c74068..e27673e5ff 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodCall.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodCall.java @@ -22,8 +22,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public final class ASTMethodCall extends AbstractInvocationExpr implements ASTPrimaryExpression, QualifiableExpression, - InvocationNode, - MethodUsage { + InvocationNode { ASTMethodCall(int id) { super(id); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java index 721e027b3d..7dcccf0756 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java @@ -4,10 +4,6 @@ package net.sourceforge.pmd.lang.java.ast; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -44,8 +40,6 @@ import net.sourceforge.pmd.lang.rule.xpath.DeprecatedAttribute; */ public final class ASTMethodDeclaration extends AbstractMethodOrConstructorDeclaration { - private List usages = Collections.emptyList(); - ASTMethodDeclaration(int id) { super(id); } @@ -56,20 +50,6 @@ public final class ASTMethodDeclaration extends AbstractMethodOrConstructorDecla return visitor.visit(this, data); } - /** - * Returns the usages of the method made in this file. Because of this, - * this list will be incomplete if the method is not effectively private. - */ - public List getUsages() { - return usages; - } - - void addUsage(MethodUsage u) { - if (usages.isEmpty()) { - usages = new ArrayList<>(2); - } - usages.add(u); - } /** * Returns true if this method is overridden. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java index a68b5db08a..79d97e486a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java @@ -22,11 +22,7 @@ import net.sourceforge.pmd.lang.java.types.TypeSystem; * * */ -public final class ASTMethodReference extends AbstractJavaExpr - implements ASTPrimaryExpression, - QualifiableExpression, - LeftRecursiveNode, - MethodUsage { +public final class ASTMethodReference extends AbstractJavaExpr implements ASTPrimaryExpression, QualifiableExpression, LeftRecursiveNode { private JMethodSig functionalMethod; private JMethodSig compileTimeDecl; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java index 593b6c075e..3f50e561bc 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java @@ -15,7 +15,6 @@ import net.sourceforge.pmd.lang.java.internal.JavaAstProcessor; import net.sourceforge.pmd.lang.java.symbols.JClassSymbol; import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol; import net.sourceforge.pmd.lang.java.symbols.JElementSymbol; -import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol; import net.sourceforge.pmd.lang.java.symbols.JMethodSymbol; import net.sourceforge.pmd.lang.java.symbols.JTypeParameterSymbol; import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol; @@ -125,40 +124,15 @@ public final class InternalApiBridge { } public static void usageResolution(JavaAstProcessor processor, ASTCompilationUnit root) { - root.descendants() + root.descendants(ASTNamedReferenceExpr.class) .crossFindBoundaries() .forEach(node -> { - if (node instanceof ASTNamedReferenceExpr) { - // variable/field usages - ASTNamedReferenceExpr ref = (ASTNamedReferenceExpr) node; - JVariableSymbol sym = ref.getReferencedSym(); - if (sym != null) { - ASTVariableDeclaratorId reffed = sym.tryGetNode(); - if (reffed != null) { // declared in this file - reffed.addUsage(ref); - } + JVariableSymbol sym = node.getReferencedSym(); + if (sym != null) { + ASTVariableDeclaratorId reffed = sym.tryGetNode(); + if (reffed != null) { // declared in this file + reffed.addUsage(node); } - } else if (node instanceof ASTMethodCall) { - // method usages - OverloadSelectionResult overload = ((ASTMethodCall) node).getOverloadSelectionInfo(); - if (!overload.isFailed()) { - JExecutableSymbol symbol = overload.getMethodType().getSymbol(); - JavaNode reffed = symbol.tryGetNode(); - if (reffed instanceof ASTMethodDeclaration) { - ((ASTMethodDeclaration) reffed).addUsage((ASTMethodCall) node); - } - } - } else if (node instanceof ASTMethodReference) { - // method usages - OverloadSelectionResult overload = ((ASTMethodCall) node).getOverloadSelectionInfo(); - if (!overload.isFailed()) { - JExecutableSymbol symbol = overload.getMethodType().getSymbol(); - JavaNode reffed = symbol.tryGetNode(); - if (reffed instanceof ASTMethodDeclaration) { - ((ASTMethodDeclaration) reffed).addUsage((ASTMethodCall) node); - } - } - } }); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodUsage.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodUsage.java deleted file mode 100644 index 060da507d7..0000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodUsage.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.ast; - -/** - * A node that uses another method. Those are {@link ASTMethodCall#getMethodType() MethodCall} - * and {@link ASTMethodReference#getReferencedMethod() MethodReference}. - * - * TODO should these method be named the same, and added to this interface? - */ -public interface MethodUsage { - - -} From 3c8283792fad0c16844fb20136590197cc33b5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 30 Oct 2020 03:01:58 +0100 Subject: [PATCH 06/45] Update UnusedPrivateMethod --- .travis/all-java.xml | 2 +- .../sourceforge/pmd/lang/ast/NodeStream.java | 2 +- .../sourceforge/pmd/util/CollectionUtil.java | 22 +-- .../pmd/lang/java/ast/ASTBreakStatement.java | 2 +- .../lang/java/ast/ASTContinueStatement.java | 2 +- .../pmd/lang/java/ast/ASTMethodCall.java | 3 +- .../pmd/lang/java/ast/ASTMethodReference.java | 7 +- .../pmd/lang/java/ast/InvocationNode.java | 11 +- .../pmd/lang/java/ast/MethodUsage.java | 27 +++ .../UnusedPrivateMethodRule.java | 158 ++++++++---------- .../UnusedPrivateMethodTest.java | 1 - .../bestpractices/xml/UnusedPrivateMethod.xml | 11 +- 12 files changed, 130 insertions(+), 118 deletions(-) create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodUsage.java diff --git a/.travis/all-java.xml b/.travis/all-java.xml index 2b6b3372a3..db6d3d925f 100644 --- a/.travis/all-java.xml +++ b/.travis/all-java.xml @@ -53,7 +53,7 @@ - + diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/NodeStream.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/NodeStream.java index 4f6c80d386..a40bff02ed 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/NodeStream.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/NodeStream.java @@ -1086,7 +1086,7 @@ public interface NodeStream extends Iterable<@NonNull T> { */ @SafeVarargs // this method is static because of the generic varargs @SuppressWarnings("unchecked") - static Function<@Nullable I, @Nullable O> asInstanceOf(Class c1, Class... rest) { + static Function<@Nullable Object, @Nullable O> asInstanceOf(Class c1, Class... rest) { if (rest.length == 0) { return obj -> c1.isInstance(obj) ? (O) obj : null; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/CollectionUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/CollectionUtil.java index 366e8fff84..238080335f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/CollectionUtil.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/CollectionUtil.java @@ -459,16 +459,18 @@ public final class CollectionUtil { * @param Type of accumulated values */ public static Collector> toMutableList() { - return Collector., List>of( - ArrayList::new, - ArrayList::add, - (left, right) -> { - left.addAll(right); - return left; - }, - a -> a, - Characteristics.IDENTITY_FINISH - ); + return Collectors.toCollection(ArrayList::new); + } + + /** + * A collector that returns a mutable set. This contrasts with + * {@link Collectors#toSet()}, which makes no guarantee about the + * mutability of the set. + * + * @param Type of accumulated values + */ + public static Collector> toMutableSet() { + return Collectors.toCollection(HashSet::new); } /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBreakStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBreakStatement.java index 65615ef883..9d5a99c60f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBreakStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBreakStatement.java @@ -21,7 +21,7 @@ import net.sourceforge.pmd.lang.ast.NodeStream; */ public final class ASTBreakStatement extends AbstractStatement { - private static final Function BREAK_TARGET_MAPPER = + private static final Function BREAK_TARGET_MAPPER = NodeStream.asInstanceOf(ASTLoopStatement.class, ASTSwitchStatement.class); ASTBreakStatement(int id) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTContinueStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTContinueStatement.java index 376c80e6cf..9c1175af09 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTContinueStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTContinueStatement.java @@ -22,7 +22,7 @@ import net.sourceforge.pmd.lang.ast.NodeStream; */ public final class ASTContinueStatement extends AbstractStatement { - private static final Function CONTINUE_TARGET_MAPPER = + private static final Function CONTINUE_TARGET_MAPPER = NodeStream.asInstanceOf(ASTLoopStatement.class); ASTContinueStatement(int id) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodCall.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodCall.java index e27673e5ff..bd06c74068 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodCall.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodCall.java @@ -22,7 +22,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; public final class ASTMethodCall extends AbstractInvocationExpr implements ASTPrimaryExpression, QualifiableExpression, - InvocationNode { + InvocationNode, + MethodUsage { ASTMethodCall(int id) { super(id); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java index 79d97e486a..01bc091769 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java @@ -22,7 +22,11 @@ import net.sourceforge.pmd.lang.java.types.TypeSystem; * * */ -public final class ASTMethodReference extends AbstractJavaExpr implements ASTPrimaryExpression, QualifiableExpression, LeftRecursiveNode { +public final class ASTMethodReference extends AbstractJavaExpr + implements ASTPrimaryExpression, + QualifiableExpression, + LeftRecursiveNode, + MethodUsage { private JMethodSig functionalMethod; private JMethodSig compileTimeDecl; @@ -90,6 +94,7 @@ public final class ASTMethodReference extends AbstractJavaExpr implements ASTPri * Returns the method name, or an {@link JConstructorSymbol#CTOR_NAME} * if this is a {@linkplain #isConstructorReference() constructor reference}. */ + @Override public @NonNull String getMethodName() { return super.getImage(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InvocationNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InvocationNode.java index 1c8e94f9ab..80563b665b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InvocationNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InvocationNode.java @@ -4,10 +4,8 @@ package net.sourceforge.pmd.lang.java.ast; -import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol; import net.sourceforge.pmd.lang.java.types.JMethodSig; import net.sourceforge.pmd.lang.java.types.OverloadSelectionResult; @@ -24,7 +22,7 @@ import net.sourceforge.pmd.lang.java.types.OverloadSelectionResult; * of the {@linkplain #getMethodType() compile-time declaration} * of this node. */ -public interface InvocationNode extends TypeNode { +public interface InvocationNode extends TypeNode, MethodUsage { /** * Returns the node representing the list of arguments @@ -57,12 +55,5 @@ public interface InvocationNode extends TypeNode { */ OverloadSelectionResult getOverloadSelectionInfo(); - /** - * Returns the name of the called method. If this is a constructor - * call, returns {@link JConstructorSymbol#CTOR_NAME}. - */ - default @NonNull String getMethodName() { - return JConstructorSymbol.CTOR_NAME; - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodUsage.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodUsage.java new file mode 100644 index 0000000000..d85602411c --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodUsage.java @@ -0,0 +1,27 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol; + +/** + * A node that uses another method or constructor. Those are + * {@link InvocationNode#getMethodType() InvocationNode}s and + * {@link ASTMethodReference#getReferencedMethod() MethodReference}. + * + * TODO should these method be named the same, and added to this interface? + */ +public interface MethodUsage extends JavaNode { + + /** + * Returns the name of the called method. If this is a constructor + * call, returns {@link JConstructorSymbol#CTOR_NAME}. + */ + default @NonNull String getMethodName() { + return JConstructorSymbol.CTOR_NAME; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java index f44819c8d1..8880a0d00c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java @@ -7,120 +7,100 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTInitializer; +import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTMethodCall; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.Annotatable; +import net.sourceforge.pmd.lang.java.ast.ASTMethodReference; +import net.sourceforge.pmd.lang.java.ast.AccessNode.Visibility; +import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.ast.MethodUsage; +import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil; import net.sourceforge.pmd.lang.java.rule.AbstractIgnoredAnnotationRule; -import net.sourceforge.pmd.lang.java.symboltable.ClassScope; -import net.sourceforge.pmd.lang.java.symboltable.MethodNameDeclaration; -import net.sourceforge.pmd.lang.symboltable.NameOccurrence; +import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol; +import net.sourceforge.pmd.util.CollectionUtil; /** * This rule detects private methods, that are not used and can therefore be * deleted. */ public class UnusedPrivateMethodRule extends AbstractIgnoredAnnotationRule { + private static final Set SERIALIZATION_METHODS = new HashSet<>(Arrays.asList( - "readObject", "writeObject", "readResolve", "writeReplace")); + "readObject", "writeObject", "readResolve", "writeReplace")); @Override protected Collection defaultSuppressionAnnotations() { return Collections.singletonList("java.lang.Deprecated"); } - /** - * Visit each method declaration. - * - * @param node - * the method declaration - * @param data - * data - rule context - * @return data - */ @Override - public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - if (node.isInterface()) { - return data; - } + public Object visit(ASTCompilationUnit file, Object param) { + // We do just two traversals: + // - one to find the "interesting methods", ie those that may be violations + // - another to find the possible usages. We only try to resolve + // method calls/method refs that may refer to a method in the + // first set, ie, not every call in the file. - Map> methods = node.getScope().getEnclosingScope(ClassScope.class) - .getMethodDeclarations(); - for (MethodNameDeclaration mnd : findUnique(methods)) { - List occs = methods.get(mnd); - if (!privateAndNotExcluded(mnd) || hasIgnoredAnnotation((Annotatable) mnd.getNode().getParent())) { - continue; - } - if (occs.isEmpty()) { - addViolation(data, mnd.getNode(), mnd.getImage() + mnd.getParameterDisplaySignature()); - } else { - if (isMethodNotCalledFromOtherMethods(mnd, occs)) { - addViolation(data, mnd.getNode(), mnd.getImage() + mnd.getParameterDisplaySignature()); + Map> consideredNames = + file.descendants(ASTMethodDeclaration.class) + .crossFindBoundaries() + // get methods whose usages are all in this file + // TODO we could use getEffectiveVisibility here, but we need to consider overrides then. + .filter(it -> it.getVisibility() == Visibility.V_PRIVATE) + .filter(it -> !hasIgnoredAnnotation(it) && !hasExcludedName(it) && !it.isAnnotationPresent(Override.class)) + .toStream() + .collect(Collectors.groupingBy(ASTMethodDeclaration::getName, HashMap::new, CollectionUtil.toMutableSet())); + + + file.descendants() + .crossFindBoundaries() + .map(NodeStream.asInstanceOf(ASTMethodCall.class, ASTMethodReference.class)) + .forEach(ref -> { + String methodName = ref.getMethodName(); + // the considered names might be mutated during the traversal + if (!consideredNames.containsKey(methodName)) { + return; + } + JExecutableSymbol sym; + if (ref instanceof ASTMethodCall) { + sym = ((ASTMethodCall) ref).getMethodType().getSymbol(); + } else if (ref instanceof ASTMethodReference) { + sym = ((ASTMethodReference) ref).getReferencedMethod().getSymbol(); + } else { + return; } + JavaNode reffed = sym.tryGetNode(); + if (reffed instanceof ASTMethodDeclaration + && ref.ancestors(ASTMethodDeclaration.class).first() != reffed) { + // remove from set, but only if it is called outside of itself + Set remainingUnused = consideredNames.get(methodName); + if (remainingUnused != null + && remainingUnused.remove(reffed) // note: side-effect + && remainingUnused.isEmpty()) { + consideredNames.remove(methodName); // clear this name + } + } + }); + + // those that remain are unused + consideredNames.forEach((name, unused) -> { + for (ASTMethodDeclaration m : unused) { + addViolation(param, m, PrettyPrintingUtil.displaySignature(m)); } - } - return data; + }); + + return null; } - private Set findUnique(Map> methods) { - // some rather hideous hackery here - // to work around the fact that PMD does not yet do full type analysis - // when it does, delete this - Set unique = new HashSet<>(); - Set sigs = new HashSet<>(); - for (MethodNameDeclaration mnd : methods.keySet()) { - String sig = mnd.getImage() + mnd.getParameterCount() + mnd.isVarargs(); - if (!sigs.contains(sig)) { - unique.add(mnd); - } - sigs.add(sig); - } - return unique; - } - - /** - * Checks, whether the given method {@code mnd} is called from other methods or constructors. - * - * @param mnd the private method, that is checked - * @param occs the usages of the private method - * @return true if the method is not used (except maybe from itself), false - * if the method is called by other methods. - */ - private boolean isMethodNotCalledFromOtherMethods(MethodNameDeclaration mnd, List occs) { - int callsFromOutsideMethod = 0; - for (NameOccurrence occ : occs) { - Node occNode = occ.getLocation(); - ASTConstructorDeclaration enclosingConstructor = occNode - .getFirstParentOfType(ASTConstructorDeclaration.class); - if (enclosingConstructor != null) { - callsFromOutsideMethod++; - break; // Do we miss unused private constructors here? - } - ASTInitializer enclosingInitializer = occNode.getFirstParentOfType(ASTInitializer.class); - if (enclosingInitializer != null) { - callsFromOutsideMethod++; - break; - } - - ASTMethodDeclaration enclosingMethod = occNode.getFirstParentOfType(ASTMethodDeclaration.class); - if (enclosingMethod == null || !mnd.getNode().getParent().equals(enclosingMethod)) { - callsFromOutsideMethod++; - break; - } - } - return callsFromOutsideMethod == 0; - } - - private boolean privateAndNotExcluded(MethodNameDeclaration mnd) { - ASTMethodDeclaration node = mnd.getDeclarator(); - return node.isPrivate() && !SERIALIZATION_METHODS.contains(node.getName()); + private boolean hasExcludedName(ASTMethodDeclaration node) { + return SERIALIZATION_METHODS.contains(node.getName()); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodTest.java index 85c62ecfca..5cc101a04f 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class UnusedPrivateMethodTest extends PmdRuleTst { // no additional unit tests } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml index 2c3e325c11..1f82a91999 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml @@ -227,13 +227,18 @@ public class Foo { - two private methods, both used, same name, same arg count, diff types - 0 + two private methods, only one used, same name, same arg count, diff types + 1 + 7 + + Avoid unused private methods such as 'foo(List)'. + #1403 False positive UnusedPrivateMethod with JAVA8 0 actual From df59506bc7fbaecf2d4b506ad67bfb48640fd02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 30 Oct 2020 03:33:42 +0100 Subject: [PATCH 07/45] Update UnusedFormalParameter --- .travis/all-java.xml | 2 +- .../pmd/lang/java/ast/ASTList.java | 13 +++ .../lang/java/ast/ASTMethodDeclaration.java | 10 ++ .../UnusedFormalParameterRule.java | 93 ++++--------------- .../UnusedPrivateMethodRule.java | 4 +- .../lang/java/rule/internal/RuleAstUtil.java | 79 ++++++++++++++++ .../UnusedFormalParameterTest.java | 1 - .../xml/UnusedFormalParameter.xml | 1 + 8 files changed, 122 insertions(+), 81 deletions(-) create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/RuleAstUtil.java diff --git a/.travis/all-java.xml b/.travis/all-java.xml index db6d3d925f..b526de74d0 100644 --- a/.travis/all-java.xml +++ b/.travis/all-java.xml @@ -49,7 +49,7 @@ - + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTList.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTList.java index 16f4a56272..2c3589697f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTList.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTList.java @@ -114,6 +114,19 @@ public abstract class ASTList extends AbstractJavaNode imple return list == null ? 0 : list.size(); } + + /** + * Returns the element if there is exactly one, otherwise returns null. + * + * @param list List node + * @param Type of elements + * + * @return An element, or null. + */ + public static @Nullable N singleOrNull(@Nullable ASTList list) { + return list == null || list.size() != 1 ? null : list.get(0); + } + /** * Super type for *nonempty* lists that *only* have nodes of type {@code } * as a child. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java index 7dcccf0756..76017e4045 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java @@ -50,6 +50,16 @@ public final class ASTMethodDeclaration extends AbstractMethodOrConstructorDecla return visitor.visit(this, data); } + /** + * Returns true if this method is overridden. + * TODO for now, this just checks for an @Override annotation, + * but this should definitely do what MissingOverride does. + * This could be useful in UnusedPrivateMethod (to check not only private methods), + * and also UselessOverridingMethod, and overall many many rules. + */ + public boolean isOverridden() { + return isAnnotationPresent(Override.class); + } /** * Returns true if this method is overridden. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java index a04cfbfbf9..2813ce814b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java @@ -8,26 +8,15 @@ import static net.sourceforge.pmd.properties.PropertyFactory.booleanProperty; import java.io.InvalidObjectException; import java.io.ObjectInputStream; -import java.util.List; -import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; -import net.sourceforge.pmd.lang.java.ast.ASTThrowsList; -import net.sourceforge.pmd.lang.java.ast.ASTType; +import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; -import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.ast.AccessNode.Visibility; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence; -import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; -import net.sourceforge.pmd.lang.symboltable.NameOccurrence; +import net.sourceforge.pmd.lang.java.rule.internal.RuleAstUtil; import net.sourceforge.pmd.properties.PropertyDescriptor; @@ -47,82 +36,32 @@ public class UnusedFormalParameterRule extends AbstractJavaRule { @Override public Object visit(ASTMethodDeclaration node, Object data) { - if (!node.isPrivate() && !getProperty(CHECKALL_DESCRIPTOR)) { + if (node.getVisibility() != Visibility.V_PRIVATE && !getProperty(CHECKALL_DESCRIPTOR)) { return data; } - if (!node.isNative() && !node.isAbstract() && !isSerializationMethod(node) && !hasOverrideAnnotation(node)) { + if (node.getBody() != null && !isSerializationMethod(node) && !node.isOverridden()) { check(node, data); } return data; } + // TODO consider moving to RuleAstUtil, see also UnusedPrivateMethod and other rules that deal with serialization private boolean isSerializationMethod(ASTMethodDeclaration node) { - ASTMethodDeclarator declarator = node.getFirstDescendantOfType(ASTMethodDeclarator.class); - List parameters = declarator.findDescendantsOfType(ASTFormalParameter.class); - if (node.isPrivate() && "readObject".equals(node.getName()) && parameters.size() == 1 - && throwsOneException(node, InvalidObjectException.class)) { - ASTType type = parameters.get(0).getTypeNode(); - if (type.getType() == ObjectInputStream.class - || ObjectInputStream.class.getSimpleName().equals(type.getTypeImage()) - || ObjectInputStream.class.getName().equals(type.getTypeImage())) { - return true; - } - } - return false; + return node.getVisibility() == Visibility.V_PRIVATE + && "readObject".equals(node.getName()) + && RuleAstUtil.hasExceptionList(node, InvalidObjectException.class) + && RuleAstUtil.hasParameters(node, ObjectInputStream.class); } - private boolean throwsOneException(ASTMethodDeclaration node, Class exception) { - @Nullable ASTThrowsList throwsList = node.getThrowsList(); - if (throwsList != null && throwsList.getNumChildren() == 1) { - ASTClassOrInterfaceType n = throwsList.getChild(0); - if (n.getType() == exception || exception.getSimpleName().equals(n.getImage()) - || exception.getName().equals(n.getImage())) { - return true; - } - } - return false; - } - - private void check(Node node, Object data) { - Node parent = node.getParent().getParent().getParent(); - if (parent instanceof ASTClassOrInterfaceDeclaration - && !((ASTClassOrInterfaceDeclaration) parent).isInterface()) { - Map> vars = ((JavaNode) node).getScope() - .getDeclarations(VariableNameDeclaration.class); - for (Map.Entry> entry : vars.entrySet()) { - VariableNameDeclaration nameDecl = entry.getKey(); - - ASTVariableDeclaratorId declNode = nameDecl.getDeclaratorId(); - if (!declNode.isFormalParameter()) { - continue; + private void check(ASTMethodOrConstructorDeclaration node, Object data) { + if (!node.getEnclosingType().isInterface()) { + for (ASTFormalParameter formal : node.getFormalParameters()) { + ASTVariableDeclaratorId varId = formal.getVarId(); + if (UnusedLocalVariableRule.isNeverUsed(varId)) { + addViolation(data, varId, new Object[] {node instanceof ASTMethodDeclaration ? "method" : "constructor", varId.getName(),}); } - - if (actuallyUsed(nameDecl, entry.getValue())) { - continue; - } - addViolation(data, nameDecl.getNode(), new Object[] { - node instanceof ASTMethodDeclaration ? "method" : "constructor", nameDecl.getImage(), }); } } } - private boolean actuallyUsed(VariableNameDeclaration nameDecl, List usages) { - for (NameOccurrence occ : usages) { - JavaNameOccurrence jocc = (JavaNameOccurrence) occ; - if (jocc.isOnLeftHandSide()) { - if (nameDecl.isArray() && jocc.getLocation().getParent().getParent().getNumChildren() > 1) { - // array element access - return true; - } - continue; - } else { - return true; - } - } - return false; - } - - private boolean hasOverrideAnnotation(ASTMethodDeclaration node) { - return node.isAnnotationPresent(Override.class); - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java index 8880a0d00c..efad927fbf 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java @@ -32,8 +32,8 @@ import net.sourceforge.pmd.util.CollectionUtil; */ public class UnusedPrivateMethodRule extends AbstractIgnoredAnnotationRule { - private static final Set SERIALIZATION_METHODS = new HashSet<>(Arrays.asList( - "readObject", "writeObject", "readResolve", "writeReplace")); + private static final Set SERIALIZATION_METHODS = + new HashSet<>(Arrays.asList("readObject", "writeObject", "readResolve", "writeReplace")); @Override protected Collection defaultSuppressionAnnotations() { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/RuleAstUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/RuleAstUtil.java new file mode 100644 index 0000000000..f5ca715cdc --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/RuleAstUtil.java @@ -0,0 +1,79 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.internal; + +import java.util.List; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; +import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; +import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; +import net.sourceforge.pmd.lang.java.ast.ASTList; +import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.TypeNode; +import net.sourceforge.pmd.lang.java.types.TypeTestUtil; + +/** + * Utilities for rules to query the AST. + */ +public final class RuleAstUtil { + + private RuleAstUtil() { + // utility class + } + + + /** + * Returns true if the formal parameters of the method or constructor + * match the given types exactly. Note that for varargs methods, the + * last param must have an array type (but it is not checked to be varargs). + * This will return false if we're not sure. + * + * @param node Method or ctor + * @param types List of types to match (may be empty) + * + * @throws NullPointerException If any of the classes is null, or the node is null + * @see TypeTestUtil#isExactlyA(Class, TypeNode) + */ + public static boolean hasParameters(ASTMethodOrConstructorDeclaration node, Class... types) { + ASTFormalParameters formals = node.getFormalParameters(); + if (formals.size() != types.length) { + return false; + } + for (int i = 0; i < formals.size(); i++) { + ASTFormalParameter fi = formals.get(i); + if (!TypeTestUtil.isExactlyA(types[i], fi)) { + return false; + } + } + return true; + } + + /** + * Returns true if the {@code throws} declaration of the method or constructor + * matches the given types exactly. + * + * @param node Method or ctor + * @param types List of exception types to match (may be empty) + * + * @throws NullPointerException If any of the classes is null, or the node is null + * @see TypeTestUtil#isExactlyA(Class, TypeNode) + */ + @SafeVarargs + public static boolean hasExceptionList(ASTMethodOrConstructorDeclaration node, Class... types) { + @NonNull List formals = ASTList.orEmpty(node.getThrowsList()); + if (formals.size() != types.length) { + return false; + } + for (int i = 0; i < formals.size(); i++) { + ASTClassOrInterfaceType fi = formals.get(i); + if (!TypeTestUtil.isExactlyA(types[i], fi)) { + return false; + } + } + return true; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterTest.java index f4e6c18889..154952c1f5 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class UnusedFormalParameterTest extends PmdRuleTst { // no additional unit tests } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedFormalParameter.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedFormalParameter.xml index f5a4b52e8e..0d74b345e1 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedFormalParameter.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedFormalParameter.xml @@ -232,6 +232,7 @@ abstract class Foo { #1159 false positive UnusedFormalParameter readObject(ObjectInputStream) if not used 0 Date: Fri, 30 Oct 2020 04:03:18 +0100 Subject: [PATCH 08/45] Add more stuff to JavaRuleUtil --- .../UnusedFormalParameterRule.java | 17 +- .../UnusedLocalVariableRule.java | 17 +- .../bestpractices/UnusedPrivateFieldRule.java | 12 +- .../documentation/CommentRequiredRule.java | 33 +--- .../lang/java/rule/internal/JavaRuleUtil.java | 146 ++++++++++++++++++ .../lang/java/rule/internal/RuleAstUtil.java | 79 ---------- 6 files changed, 158 insertions(+), 146 deletions(-) create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java delete mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/RuleAstUtil.java diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java index 2813ce814b..39a98a8de6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java @@ -6,9 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import static net.sourceforge.pmd.properties.PropertyFactory.booleanProperty; -import java.io.InvalidObjectException; -import java.io.ObjectInputStream; - import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; @@ -16,7 +13,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.AccessNode.Visibility; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.rule.internal.RuleAstUtil; +import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; import net.sourceforge.pmd.properties.PropertyDescriptor; @@ -39,25 +36,17 @@ public class UnusedFormalParameterRule extends AbstractJavaRule { if (node.getVisibility() != Visibility.V_PRIVATE && !getProperty(CHECKALL_DESCRIPTOR)) { return data; } - if (node.getBody() != null && !isSerializationMethod(node) && !node.isOverridden()) { + if (node.getBody() != null && !JavaRuleUtil.isSerializationReadObject(node) && !node.isOverridden()) { check(node, data); } return data; } - // TODO consider moving to RuleAstUtil, see also UnusedPrivateMethod and other rules that deal with serialization - private boolean isSerializationMethod(ASTMethodDeclaration node) { - return node.getVisibility() == Visibility.V_PRIVATE - && "readObject".equals(node.getName()) - && RuleAstUtil.hasExceptionList(node, InvalidObjectException.class) - && RuleAstUtil.hasParameters(node, ObjectInputStream.class); - } - private void check(ASTMethodOrConstructorDeclaration node, Object data) { if (!node.getEnclosingType().isInterface()) { for (ASTFormalParameter formal : node.getFormalParameters()) { ASTVariableDeclaratorId varId = formal.getVarId(); - if (UnusedLocalVariableRule.isNeverUsed(varId)) { + if (JavaRuleUtil.isNeverUsed(varId)) { addViolation(data, varId, new Object[] {node instanceof ASTMethodDeclaration ? "method" : "constructor", varId.getName(),}); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java index 30d3aa2a32..61b362c810 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java @@ -6,15 +6,11 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import org.checkerframework.checker.nullness.qual.NonNull; -import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; -import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; -import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.AccessType; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; import net.sourceforge.pmd.lang.rule.RuleTargetSelector; -import net.sourceforge.pmd.util.CollectionUtil; public class UnusedLocalVariableRule extends AbstractJavaRule { @@ -26,20 +22,11 @@ public class UnusedLocalVariableRule extends AbstractJavaRule { @Override public Object visit(ASTLocalVariableDeclaration decl, Object data) { for (ASTVariableDeclaratorId varId : decl.getVarIds()) { - if (isNeverUsed(varId)) { + if (JavaRuleUtil.isNeverUsed(varId)) { addViolation(data, varId, varId.getName()); } } return data; } - static boolean isNeverUsed(ASTVariableDeclaratorId varId) { - return CollectionUtil.none(varId.getUsages(), UnusedLocalVariableRule::isReadUsage); - } - - private static boolean isReadUsage(ASTNamedReferenceExpr expr) { - return expr.getAccessType() == AccessType.READ - // foo(x++) - || expr.getParent() instanceof ASTUnaryExpression && expr.getParent().getParent() instanceof ASTArgumentList; - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java index b4f448308d..14f2d28cec 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java @@ -15,6 +15,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.AccessNode.Visibility; import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.rule.AbstractLombokAwareRule; +import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; import net.sourceforge.pmd.lang.rule.RuleTargetSelector; public class UnusedPrivateFieldRule extends AbstractLombokAwareRule { @@ -45,9 +46,11 @@ public class UnusedPrivateFieldRule extends AbstractLombokAwareRule { for (ASTFieldDeclaration field : type.getDeclarations() .filterIs(ASTFieldDeclaration.class)) { if (field.getVisibility() == Visibility.V_PRIVATE + && !JavaRuleUtil.isSerialPersistentFields(field) + && !JavaRuleUtil.isSerialVersionUID(field) && !hasIgnoredAnnotation(field)) { for (ASTVariableDeclaratorId varId : field.getVarIds()) { - if (!isOK(varId) && UnusedLocalVariableRule.isNeverUsed(varId)) { + if (JavaRuleUtil.isNeverUsed(varId)) { addViolation(data, varId, varId.getName()); } } @@ -56,11 +59,4 @@ public class UnusedPrivateFieldRule extends AbstractLombokAwareRule { } return null; } - - - private boolean isOK(ASTVariableDeclaratorId node) { - return "serialVersionUID".equals(node.getName()) - || "serialPersistentFields".equals(node.getName()) - || "IDENT".equals(node.getName()); - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredRule.java index 78cabc82e2..fcef9c76a5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredRule.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.java.rule.documentation; -import java.io.ObjectStreamField; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -21,13 +20,11 @@ import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.JModifier; import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.ast.JavadocCommentOwner; import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind; -import net.sourceforge.pmd.lang.java.types.TypeTestUtil; +import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; import net.sourceforge.pmd.properties.PropertyBuilder.GenericPropertyBuilder; import net.sourceforge.pmd.properties.PropertyDescriptor; import net.sourceforge.pmd.properties.PropertyFactory; @@ -187,9 +184,9 @@ public class CommentRequiredRule extends AbstractJavaRule { @Override public Object visit(ASTFieldDeclaration decl, Object data) { - if (isSerialVersionUID(decl)) { + if (JavaRuleUtil.isSerialVersionUID(decl)) { checkCommentMeetsRequirement(data, decl, SERIAL_VERSION_UID_CMT_REQUIREMENT_DESCRIPTOR); - } else if (isSerialPersistentFields(decl)) { + } else if (JavaRuleUtil.isSerialPersistentFields(decl)) { checkCommentMeetsRequirement(data, decl, SERIAL_PERSISTENT_FIELDS_CMT_REQUIREMENT_DESCRIPTOR); } else { checkCommentMeetsRequirement(data, decl, FIELD_CMT_REQUIREMENT_DESCRIPTOR); @@ -199,30 +196,6 @@ public class CommentRequiredRule extends AbstractJavaRule { } - @SuppressWarnings("PMD.UnusedFormalParameter") - private boolean isSerialVersionUID(ASTFieldDeclaration field) { - return field.getVarIds().any(it -> "serialVersionUID".equals(it.getName())) - && field.hasModifiers(JModifier.FINAL, JModifier.STATIC) - && field.getTypeNode().getTypeMirror().isPrimitive(PrimitiveTypeKind.LONG); - } - - /** - * Whether the given field is a serialPersistentFields variable. - *

- * This field must be initialized with an array of ObjectStreamField objects. - * The modifiers for the field are required to be private, static, and final. - * - * @param field the field, must not be null - * @return true if the field is a serialPersistentFields variable, otherwise false - * @see Oracle docs - */ - @SuppressWarnings("PMD.UnusedFormalParameter") - private boolean isSerialPersistentFields(final ASTFieldDeclaration field) { - return field.getVarIds().any(it -> "serialPersistentFields".equals(it.getName())) - && field.hasModifiers(JModifier.FINAL, JModifier.STATIC, JModifier.PRIVATE) - && TypeTestUtil.isA(ObjectStreamField[].class, field.getTypeNode()); - } - @Override public Object visit(ASTEnumDeclaration decl, Object data) { checkCommentMeetsRequirement(data, decl, ENUM_CMT_REQUIREMENT_DESCRIPTOR); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java new file mode 100644 index 0000000000..59ffa07d74 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -0,0 +1,146 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.internal; + +import static net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind.LONG; + +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectStreamField; +import java.util.List; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.AccessType; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; +import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; +import net.sourceforge.pmd.lang.java.ast.ASTList; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.ast.AccessNode.Visibility; +import net.sourceforge.pmd.lang.java.ast.JModifier; +import net.sourceforge.pmd.lang.java.ast.TypeNode; +import net.sourceforge.pmd.lang.java.types.TypeTestUtil; +import net.sourceforge.pmd.util.CollectionUtil; + +/** + * Utilities for rules to query the AST. + */ +public final class JavaRuleUtil { + + private JavaRuleUtil() { + // utility class + } + + + /** + * Returns true if the formal parameters of the method or constructor + * match the given types exactly. Note that for varargs methods, the + * last param must have an array type (but it is not checked to be varargs). + * This will return false if we're not sure. + * + * @param node Method or ctor + * @param types List of types to match (may be empty) + * + * @throws NullPointerException If any of the classes is null, or the node is null + * @see TypeTestUtil#isExactlyA(Class, TypeNode) + */ + public static boolean hasParameters(ASTMethodOrConstructorDeclaration node, Class... types) { + ASTFormalParameters formals = node.getFormalParameters(); + if (formals.size() != types.length) { + return false; + } + for (int i = 0; i < formals.size(); i++) { + ASTFormalParameter fi = formals.get(i); + if (!TypeTestUtil.isExactlyA(types[i], fi)) { + return false; + } + } + return true; + } + + /** + * Returns true if the {@code throws} declaration of the method or constructor + * matches the given types exactly. + * + * @param node Method or ctor + * @param types List of exception types to match (may be empty) + * + * @throws NullPointerException If any of the classes is null, or the node is null + * @see TypeTestUtil#isExactlyA(Class, TypeNode) + */ + @SafeVarargs + public static boolean hasExceptionList(ASTMethodOrConstructorDeclaration node, Class... types) { + @NonNull List formals = ASTList.orEmpty(node.getThrowsList()); + if (formals.size() != types.length) { + return false; + } + for (int i = 0; i < formals.size(); i++) { + ASTClassOrInterfaceType fi = formals.get(i); + if (!TypeTestUtil.isExactlyA(types[i], fi)) { + return false; + } + } + return true; + } + + /** + * True if the variable is never used. Note that the visibility of + * the variable must be less than {@link Visibility#V_PRIVATE} for + * us to be sure of it. + */ + public static boolean isNeverUsed(ASTVariableDeclaratorId varId) { + return CollectionUtil.none(varId.getUsages(), JavaRuleUtil::isReadUsage); + } + + private static boolean isReadUsage(ASTNamedReferenceExpr expr) { + return expr.getAccessType() == AccessType.READ + // foo(x++) + || expr.getParent() instanceof ASTUnaryExpression + && expr.getParent().getParent() instanceof ASTArgumentList; + } + + // TODO at least UnusedPrivateMethod has some serialization-related logic. + + /** + * Whether some variable declared by the given node is a serialPersistentFields + * (serialization-specific field). + */ + public static boolean isSerialPersistentFields(final ASTFieldDeclaration field) { + return field.hasModifiers(JModifier.FINAL, JModifier.STATIC, JModifier.PRIVATE) + && field.getVarIds().any( + it -> "serialPersistentFields".equals(it.getName()) + && TypeTestUtil.isA(ObjectStreamField[].class, it) + ); + } + + /** + * Whether some variable declared by the given node is a serialVersionUID + * (serialization-specific field). + */ + public static boolean isSerialVersionUID(ASTFieldDeclaration field) { + return field.hasModifiers(JModifier.FINAL, JModifier.STATIC) + && field.getVarIds().any( + it -> "serialVersionUID".equals(it.getName()) + && it.getTypeMirror().isPrimitive(LONG) + ); + } + + /** + * True if the method is a {@code readObject} method defined for serialization. + */ + public static boolean isSerializationReadObject(ASTMethodDeclaration node) { + return node.getVisibility() == Visibility.V_PRIVATE + && "readObject".equals(node.getName()) + && hasExceptionList(node, InvalidObjectException.class) + && hasParameters(node, ObjectInputStream.class); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/RuleAstUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/RuleAstUtil.java deleted file mode 100644 index f5ca715cdc..0000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/RuleAstUtil.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.internal; - -import java.util.List; - -import org.checkerframework.checker.nullness.qual.NonNull; - -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; -import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; -import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; -import net.sourceforge.pmd.lang.java.ast.ASTList; -import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.TypeNode; -import net.sourceforge.pmd.lang.java.types.TypeTestUtil; - -/** - * Utilities for rules to query the AST. - */ -public final class RuleAstUtil { - - private RuleAstUtil() { - // utility class - } - - - /** - * Returns true if the formal parameters of the method or constructor - * match the given types exactly. Note that for varargs methods, the - * last param must have an array type (but it is not checked to be varargs). - * This will return false if we're not sure. - * - * @param node Method or ctor - * @param types List of types to match (may be empty) - * - * @throws NullPointerException If any of the classes is null, or the node is null - * @see TypeTestUtil#isExactlyA(Class, TypeNode) - */ - public static boolean hasParameters(ASTMethodOrConstructorDeclaration node, Class... types) { - ASTFormalParameters formals = node.getFormalParameters(); - if (formals.size() != types.length) { - return false; - } - for (int i = 0; i < formals.size(); i++) { - ASTFormalParameter fi = formals.get(i); - if (!TypeTestUtil.isExactlyA(types[i], fi)) { - return false; - } - } - return true; - } - - /** - * Returns true if the {@code throws} declaration of the method or constructor - * matches the given types exactly. - * - * @param node Method or ctor - * @param types List of exception types to match (may be empty) - * - * @throws NullPointerException If any of the classes is null, or the node is null - * @see TypeTestUtil#isExactlyA(Class, TypeNode) - */ - @SafeVarargs - public static boolean hasExceptionList(ASTMethodOrConstructorDeclaration node, Class... types) { - @NonNull List formals = ASTList.orEmpty(node.getThrowsList()); - if (formals.size() != types.length) { - return false; - } - for (int i = 0; i < formals.size(); i++) { - ASTClassOrInterfaceType fi = formals.get(i); - if (!TypeTestUtil.isExactlyA(types[i], fi)) { - return false; - } - } - return true; - } -} From 8d26aed8111b64fdd8c6bb0efe31ccd3818030e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 30 Oct 2020 04:21:40 +0100 Subject: [PATCH 09/45] Update AvoidReassigningParametersRule --- .travis/all-java.xml | 2 +- .../AvoidReassigningParametersRule.java | 66 ++++++++----------- .../AvoidReassigningParametersTest.java | 1 - .../xml/AvoidReassigningParameters.xml | 5 ++ 4 files changed, 33 insertions(+), 41 deletions(-) diff --git a/.travis/all-java.xml b/.travis/all-java.xml index b526de74d0..a5b71bc4d3 100644 --- a/.travis/all-java.xml +++ b/.travis/all-java.xml @@ -17,7 +17,7 @@ - + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersRule.java index ec06c65580..69a97a24ef 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersRule.java @@ -4,55 +4,43 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; -import java.util.List; -import java.util.Map; - +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.AccessType; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence; -import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; -import net.sourceforge.pmd.lang.symboltable.NameOccurrence; +import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; -public class AvoidReassigningParametersRule extends AbstractJavaRule { +public class AvoidReassigningParametersRule extends AbstractJavaRulechainRule { - @Override - public Object visit(ASTMethodDeclarator node, Object data) { - Map> params = node.getScope() - .getDeclarations(VariableNameDeclaration.class); - this.lookForViolation(params, data); - return super.visit(node, data); + public AvoidReassigningParametersRule() { + super(ASTMethodDeclaration.class, ASTConstructorDeclaration.class); } - private void lookForViolation(Map> params, Object data) { - for (Map.Entry> entry : params.entrySet()) { - VariableNameDeclaration decl = entry.getKey(); - List usages = entry.getValue(); + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + lookForViolations(node, data); + return data; + } - // Only look for formal parameters - if (!decl.getDeclaratorId().isFormalParameter()) { - continue; - } - for (NameOccurrence occ : usages) { - JavaNameOccurrence jocc = (JavaNameOccurrence) occ; - if ((jocc.isOnLeftHandSide() || jocc.isSelfAssignment()) - && jocc.getNameForWhichThisIsAQualifier() == null && !jocc.useThisOrSuper() && !decl.isVarargs() - && (!decl.isArray() - || jocc.getLocation().getParent().getParent().getNumChildren() == 1)) { - // not an array or no primary suffix to access the array - // values - addViolation(data, decl.getNode(), decl.getImage()); + @Override + public Object visit(ASTConstructorDeclaration node, Object data) { + lookForViolations(node, data); + return data; + } + + private void lookForViolations(ASTMethodOrConstructorDeclaration node, Object data) { + for (ASTFormalParameter formal : node.getFormalParameters()) { + ASTVariableDeclaratorId varId = formal.getVarId(); + for (ASTNamedReferenceExpr usage : varId.getUsages()) { + if (usage.getAccessType() == AccessType.WRITE) { + addViolation(data, usage, varId.getName()); } } } } - @Override - public Object visit(ASTConstructorDeclaration node, Object data) { - Map> params = node.getScope() - .getDeclarations(VariableNameDeclaration.class); - this.lookForViolation(params, data); - return super.visit(node, data); - } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersTest.java index 35143dabda..906b82d966 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class AvoidReassigningParametersTest extends PmdRuleTst { // no additional unit tests } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningParameters.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningParameters.xml index c639198e7e..37f7407b24 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningParameters.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningParameters.xml @@ -162,7 +162,12 @@ public class Foo { The rule should take into account uses of field names, inherited or not, matching the method parameter name. 0 Date: Fri, 30 Oct 2020 05:40:33 +0100 Subject: [PATCH 10/45] Try updating AvoidReassigningLoopVariablesRule --- .../pmd/lang/java/ast/ASTAssignableExpr.java | 6 +- .../pmd/lang/java/ast/ASTForStatement.java | 4 - .../lang/java/ast/ASTForeachStatement.java | 7 - .../pmd/lang/java/ast/ASTLoopStatement.java | 7 + .../pmd/lang/java/ast/ASTWhileStatement.java | 8 - .../AvoidReassigningLoopVariablesRule.java | 320 ++++++------------ .../lang/java/rule/internal/JavaRuleUtil.java | 55 +++ .../AvoidReassigningLoopVariablesTest.java | 1 - 8 files changed, 168 insertions(+), 240 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAssignableExpr.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAssignableExpr.java index 1590e422ef..2d763fb192 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAssignableExpr.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAssignableExpr.java @@ -8,6 +8,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol; import net.sourceforge.pmd.lang.java.types.JVariableSig; @@ -95,7 +96,10 @@ public interface ASTAssignableExpr extends ASTPrimaryExpression { /** The value of the variable is read. */ READ, - /** The value is written to, possibly being read before or after. */ + /** + * The value is written to, possibly being read before or after. + * Also see {@link JavaRuleUtil#isVarAccessReadAndWrite(ASTNamedReferenceExpr)}. + */ WRITE } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForStatement.java index cc76696e40..117ccb54c7 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForStatement.java @@ -58,9 +58,5 @@ public final class ASTForStatement extends AbstractStatement implements ASTLoopS return update == null ? null : update.getExprList(); } - /** Returns the statement that represents the body of this loop. */ - public ASTStatement getBody() { - return (ASTStatement) getChild(getNumChildren() - 1); - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForeachStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForeachStatement.java index 9adb51dbb9..3455ba3c78 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForeachStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForeachStatement.java @@ -43,12 +43,5 @@ public final class ASTForeachStatement extends AbstractStatement implements Inte return getFirstChildOfType(ASTExpression.class); } - /** - * Returns the statement that represents the body of this - * loop. - */ - public ASTStatement getBody() { - return (ASTStatement) getChild(getNumChildren() - 1); - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLoopStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLoopStatement.java index bab6252aae..8ad32273ce 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLoopStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLoopStatement.java @@ -19,4 +19,11 @@ package net.sourceforge.pmd.lang.java.ast; * */ public interface ASTLoopStatement extends ASTStatement { + + + /** Returns the statement that represents the body of this loop. */ + default ASTStatement getBody() { + return (ASTStatement) getLastChild(); + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWhileStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWhileStatement.java index 28ab3950fa..683b4ad39d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWhileStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWhileStatement.java @@ -29,14 +29,6 @@ public final class ASTWhileStatement extends AbstractStatement implements ASTLoo } - /** - * Returns the statement that will be run while the guard - * evaluates to true. - */ - public ASTStatement getBody() { - return (ASTStatement) getChild(1); - } - @Override protected R acceptVisitor(JavaVisitor visitor, P data) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java index 939f4c73b4..52d5f0916a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java @@ -8,34 +8,25 @@ import static java.util.Arrays.asList; import static net.sourceforge.pmd.properties.PropertyFactory.enumProperty; import static net.sourceforge.pmd.util.CollectionUtil.associateBy; -import java.util.HashSet; -import java.util.List; import java.util.Map; -import java.util.Set; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; -import net.sourceforge.pmd.lang.java.ast.ASTBlock; -import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; -import net.sourceforge.pmd.lang.java.ast.ASTContinueStatement; -import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; +import org.checkerframework.checker.nullness.qual.NonNull; + +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.AccessType; import net.sourceforge.pmd.lang.java.ast.ASTExpression; -import net.sourceforge.pmd.lang.java.ast.ASTForInit; import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTForUpdate; +import net.sourceforge.pmd.lang.java.ast.ASTForeachStatement; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; -import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; +import net.sourceforge.pmd.lang.java.ast.ASTLoopStatement; import net.sourceforge.pmd.lang.java.ast.ASTStatement; import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; -import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; -import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; import net.sourceforge.pmd.lang.java.rule.performance.AbstractOptimizationRule; +import net.sourceforge.pmd.lang.rule.RuleTargetSelector; import net.sourceforge.pmd.properties.PropertyDescriptor; import net.sourceforge.pmd.util.StringUtil.CaseConvention; @@ -45,241 +36,139 @@ public class AvoidReassigningLoopVariablesRule extends AbstractOptimizationRule associateBy(asList(ForeachReassignOption.values()), ForeachReassignOption::getDisplayName); private static final PropertyDescriptor FOREACH_REASSIGN - = enumProperty("foreachReassign", FOREACH_REASSIGN_VALUES) - .defaultValue(ForeachReassignOption.DENY) - .desc("how/if foreach control variables may be reassigned") - .build(); + = enumProperty("foreachReassign", FOREACH_REASSIGN_VALUES) + .defaultValue(ForeachReassignOption.DENY) + .desc("how/if foreach control variables may be reassigned") + .build(); private static final Map FOR_REASSIGN_VALUES = associateBy(asList(ForReassignOption.values()), ForReassignOption::getDisplayName); private static final PropertyDescriptor FOR_REASSIGN - = enumProperty("forReassign", FOR_REASSIGN_VALUES) - .defaultValue(ForReassignOption.DENY) - .desc("how/if for control variables may be reassigned") - .build(); + = enumProperty("forReassign", FOR_REASSIGN_VALUES) + .defaultValue(ForReassignOption.DENY) + .desc("how/if for control variables may be reassigned") + .build(); public AvoidReassigningLoopVariablesRule() { definePropertyDescriptor(FOREACH_REASSIGN); definePropertyDescriptor(FOR_REASSIGN); - addRuleChainVisit(ASTLocalVariableDeclaration.class); } @Override - public Object visit(ASTLocalVariableDeclaration node, Object data) { - final Set loopVariables = new HashSet<>(); - for (ASTVariableDeclaratorId declaratorId : node.findDescendantsOfType(ASTVariableDeclaratorId.class)) { - loopVariables.add(declaratorId.getImage()); + protected @NonNull RuleTargetSelector buildTargetSelector() { + return RuleTargetSelector.forTypes(ASTForStatement.class, ASTForeachStatement.class); + } + + @Override + public Object visit(ASTForeachStatement loopStmt, Object data) { + ForeachReassignOption behavior = getProperty(FOREACH_REASSIGN); + if (behavior == ForeachReassignOption.ALLOW) { + return data; } - - if (node.getParent() instanceof ASTForInit) { - // regular for loop: LocalVariableDeclaration -> ForInit -> ForStatement - final ASTStatement loopBody = node.getParent().getParent().getFirstChildOfType(ASTStatement.class); - final ForReassignOption forReassign = getProperty(FOR_REASSIGN); - - if (forReassign != ForReassignOption.ALLOW) { - // check assignments - checkAssignExceptIncrement(data, loopVariables, loopBody); - - if (forReassign == ForReassignOption.SKIP) { - // skipping allowed -> only check non-conditional increments - checkIncrementAndDecrement(data, loopVariables, loopBody, IgnoreFlags.IGNORE_CONDITIONAL); - } else { - // skipping not allowed -> check all increments - checkIncrementAndDecrement(data, loopVariables, loopBody); + ASTVariableDeclaratorId loopVar = loopStmt.getVarId(); + boolean ignoreNext = behavior == ForeachReassignOption.FIRST_ONLY; + for (ASTNamedReferenceExpr usage : loopVar.getUsages()) { + if (usage.getAccessType() == AccessType.WRITE) { + if (ignoreNext) { + ignoreNext = false; + continue; } - } - - } else if (node.getParent() instanceof ASTForStatement) { - // for-each loop: LocalVariableDeclaration -> ForStatement - final ASTStatement loopBody = node.getParent().getFirstChildOfType(ASTStatement.class); - final ForeachReassignOption foreachReassign = getProperty(FOREACH_REASSIGN); - - if (foreachReassign == ForeachReassignOption.FIRST_ONLY) { - checkAssignExceptIncrement(data, loopVariables, loopBody, IgnoreFlags.IGNORE_FIRST); - checkIncrementAndDecrement(data, loopVariables, loopBody, IgnoreFlags.IGNORE_FIRST); - - } else if (foreachReassign == ForeachReassignOption.DENY) { - checkAssignExceptIncrement(data, loopVariables, loopBody); - checkIncrementAndDecrement(data, loopVariables, loopBody); + addViolation(data, usage, loopVar.getName()); + } else { + ignoreNext = false; } } - - return data; + return null; } - /** - * Report usages of assignments except '+=' and '-='. - * - * @param ignoreFlags which statements should be ignored - */ - private void checkAssignExceptIncrement(Object data, Set loopVariables, ASTStatement loopBody, IgnoreFlags... ignoreFlags) { - checkAssignments(data, loopVariables, loopBody, false, ignoreFlags); - } + @Override + public Object visit(ASTForStatement loopStmt, Object data) { + ForReassignOption behavior = getProperty(FOR_REASSIGN); + if (behavior == ForReassignOption.ALLOW) { + return data; + } + ASTForUpdate update = loopStmt.getFirstChildOfType(ASTForUpdate.class); + ASTStatement body = loopStmt.getBody(); + for (ASTVariableDeclaratorId loopVar : JavaRuleUtil.getLoopVariables(loopStmt)) { + for (ASTNamedReferenceExpr usage : loopVar.getUsages()) { + if (usage.getAccessType() == AccessType.WRITE) { + if (update != null && usage.ancestors(ASTForUpdate.class).first() == update) { + continue; + } - /** - * Report usages of increments ('++', '--', '+=', '-='). - * - * @param ignoreFlags which statements should be ignored - */ - private void checkIncrementAndDecrement(Object data, Set loopVariables, ASTStatement loopBody, IgnoreFlags... ignoreFlags) { + if (behavior == ForReassignOption.SKIP + && JavaRuleUtil.isVarAccessReadAndWrite(usage) + && isConditionallyGuarded(usage, loopStmt)) { + continue; + } + addViolation(data, usage, loopVar.getName()); + + } - for (ASTUnaryExpression expression : loopBody.findDescendantsOfType(ASTUnaryExpression.class)) { - if (expression.getOperator().isPure() || ignoreNode(expression, loopBody, ignoreFlags)) { - continue; } - - checkVariable(data, loopVariables, singleVariableName(expression.getFirstDescendantOfType(ASTPrimaryExpression.class))); } - - // foo += x and foo -= x - checkAssignments(data, loopVariables, loopBody, true, ignoreFlags); + return null; } - /** - * Report usages of assignments. - * - * @param checkIncrement true: check only '+=' and '-=', - * false: check all other assignments - * @param ignoreFlags which statements should be ignored - */ - private void checkAssignments(Object data, Set loopVariables, ASTStatement loopBody, boolean checkIncrement, IgnoreFlags... ignoreFlags) { - for (ASTAssignmentOperator operator : loopBody.findDescendantsOfType(ASTAssignmentOperator.class)) { - // check if the current operator is an assign-increment or assign-decrement operator - final String operatorImage = operator.getImage(); - final boolean isIncrement = "+=".equals(operatorImage) || "-=".equals(operatorImage); + private static boolean isConditionallyGuarded(JavaNode node, ASTLoopStatement enclosingLoop) { + JavaNode parent = node.getParent(); - if (isIncrement != checkIncrement) { - // wrong type of operator - continue; - } - - if (ignoreNode(operator, loopBody, ignoreFlags)) { - continue; - } - - final ASTPrimaryExpression primaryExpression = operator.getParent().getFirstChildOfType(ASTPrimaryExpression.class); - checkVariable(data, loopVariables, singleVariableName(primaryExpression)); - } - } - - /** - * Check if the node should be ignored, depending on the given flags and the context. - */ - private boolean ignoreNode(Node node, ASTStatement loopBody, IgnoreFlags... ignoreFlags) { - if (ignoreFlags.length == 0) { - return false; - } - final List ignoreFlagsList = asList(ignoreFlags); - - // ignore the first statement - final boolean ignoredFirstStatement = ignoreFlagsList.contains(IgnoreFlags.IGNORE_FIRST) && isFirstStatementInBlock(node, loopBody); - - // ignore conditionally executed statement - final boolean ignoredConditional = ignoreFlagsList.contains(IgnoreFlags.IGNORE_CONDITIONAL) && isConditionallyExecuted(node, loopBody); - - return ignoredFirstStatement || ignoredConditional; - } - - /** - * Extracts the variable name by traversing PrimaryExpression -> PrimaryPrefix -> Name. - * Also check if there is a PrimaryExpression -> PrimarySuffix which indicates a field or array access. - * - * @returns the Name or null if the PrimaryPrefix is "this" or "super" - */ - private ASTName singleVariableName(ASTPrimaryExpression primaryExpression) { - final ASTPrimaryPrefix primaryPrefix = primaryExpression.getFirstChildOfType(ASTPrimaryPrefix.class); - final ASTPrimarySuffix primarySuffix = primaryExpression.getFirstChildOfType(ASTPrimarySuffix.class); - - if (primarySuffix != null || primaryPrefix == null) { - return null; - } - - return primaryPrefix.getFirstChildOfType(ASTName.class); - } - - /** - * Check if the given node is the first statement in the block. - */ - private boolean isFirstStatementInBlock(Node node, ASTStatement loopBody) { - // find the statement of the operation and the loop body block statement - final ASTBlockStatement statement = node.getFirstParentOfType(ASTBlockStatement.class); - final ASTBlock block = loopBody.getFirstDescendantOfType(ASTBlock.class); - - if (statement == null || block == null) { + if (parent == enclosingLoop) { return false; } - // is the first statement in the loop body? - return block.equals(statement.getParent()) && statement.getIndexInParent() == 0; - } - - /** - * Check if the node will only be executed conditionally by checking, - * if the node is inside any kind of control flow statement or - * if any prior statement contains a {@code continue} statement. - *
- * This doesn't check - */ - private boolean isConditionallyExecuted(Node node, ASTStatement loopBody) { - // starting at the assignment/increment node, traverse the tree up to - // check if we're inside the conditionally executed block of a control flow statement - - Node checkNode = node; - while (checkNode.getParent() != null && !checkNode.getParent().equals(loopBody)) { - final Node parent = checkNode.getParent(); - - // if/switch/while-statement, excluding the expression - if (parent instanceof ASTIfStatement || parent instanceof ASTSwitchStatement || parent instanceof ASTWhileStatement || parent instanceof ASTDoStatement) { - return !(checkNode instanceof ASTExpression); - } - - // for-statement, excluding the initializer, expression and update - if (parent instanceof ASTForStatement) { - return !(checkNode instanceof ASTForInit || checkNode instanceof ASTExpression || checkNode instanceof ASTForUpdate); - } - checkNode = parent; + if (parent instanceof ASTLoopStatement) { + return node == ((ASTLoopStatement) parent).getBody() || isConditionallyGuarded(parent, enclosingLoop); } - // iterating the statements of the loop body, check if there is a - // continue statement before the increment statement - final ASTBlock block = loopBody.getFirstDescendantOfType(ASTBlock.class); - if (block != null) { - for (int i = 0; i < block.getNumChildren(); i++) { - final Node statement = block.getChild(i); + if (parent instanceof ASTSwitchStatement) { + return node.getIndexInParent() != 0 || isConditionallyGuarded(parent, enclosingLoop); + } - if (statement.hasDescendantOfType(ASTContinueStatement.class)) { + if (parent instanceof ASTIfStatement) { + return node.getIndexInParent() != 0 || isConditionallyGuarded(parent, enclosingLoop); + +// if (node.getIndexInParent() == 0) {// condition +// return isConditionallyGuarded(parent, enclosingLoop); +// } +// while (parent.getParent() instanceof ASTIfStatement) { +// parent = parent.getParent(); +// } +// return isConditionallyGuarded(parent, enclosingLoop); + } + + return isConditionallyGuarded(parent, enclosingLoop); + } + + private static boolean isConditionallyGuarded(ASTLoopStatement loop, ASTExpression expr) { + ASTIfStatement enclosingIf = JavaRuleUtil.getIfStmtIfExprInCondition(expr); + JavaNode previous = expr; + for (JavaNode parent : expr.ancestors()) { + if (parent == loop) { + break; + } + if (parent instanceof ASTIfStatement) { + if (previous instanceof ASTIfStatement && previous.getIndexInParent() == 1 + || !(previous instanceof ASTExpression)) { return true; } - if (isParent(statement, node)) { - return false; + } else if (parent instanceof ASTSwitchStatement) { + if (previous != parent.getFirstChild()) { + return true; + } + } else if (parent instanceof ASTLoopStatement) { + // we didn't come from the condition expr + if (previous == ((ASTLoopStatement) parent).getBody()) { + return true; } } - } - return false; - } - - private boolean isParent(Node possibleParent, Node node) { - Node checkNode = node; - while (checkNode.getParent() != null) { - if (checkNode.getParent().equals(possibleParent)) { - return true; - } - checkNode = checkNode.getParent(); + previous = parent; } return false; } - /** - * Add a violation, if the node image is one of the loop variables. - */ - private void checkVariable(Object data, Set loopVariables, JavaNode node) { - if (node != null && loopVariables.contains(node.getImage())) { - addViolation(data, node, node.getImage()); - } - } - private enum ForeachReassignOption { /** * Deny reassigning the 'foreach' control variable @@ -342,11 +231,4 @@ public class AvoidReassigningLoopVariablesRule extends AbstractOptimizationRule } } - private enum IgnoreFlags { - - IGNORE_FIRST, - - IGNORE_CONDITIONAL - - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java index 59ffa07d74..bde241946f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -12,17 +12,25 @@ import java.io.ObjectStreamField; import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import net.sourceforge.pmd.lang.ast.NodeStream; import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.AccessType; +import net.sourceforge.pmd.lang.java.ast.ASTAssignmentExpression; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; +import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; +import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTList; +import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTStatement; import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.AccessNode.Visibility; @@ -108,6 +116,53 @@ public final class JavaRuleUtil { && expr.getParent().getParent() instanceof ASTArgumentList; } + /** + * True if the variable is incremented or decremented via a compound + * assignment operator, or a unary increment/decrement expression. + */ + public static boolean isVarAccessReadAndWrite(ASTNamedReferenceExpr expr) { + return expr.getAccessType() == AccessType.WRITE + && (!(expr.getParent() instanceof ASTAssignmentExpression) + || ((ASTAssignmentExpression) expr.getParent()).getOperator().isCompound()); + } + + /** + * True if the variable is incremented or decremented via a compound + * assignment operator, or a unary increment/decrement expression. + */ + public static boolean isInIfCondition(ASTExpression expr) { + ASTExpression toplevel = getTopLevelExpr(expr); + return toplevel.getIndexInParent() == 0 && toplevel.getParent() instanceof ASTIfStatement; + } + + public static @Nullable ASTIfStatement getIfStmtIfExprInCondition(ASTExpression expr) { + ASTExpression toplevel = getTopLevelExpr(expr); + if (toplevel.getIndexInParent() == 0 && toplevel.getParent() instanceof ASTIfStatement) { + return (ASTIfStatement) toplevel.getParent(); + } + return null; + } + + /** + * Will cut through argument lists, except those of enum constants & explicit invocation nodes. + */ + private static @NonNull ASTExpression getTopLevelExpr(ASTExpression expr) { + return (ASTExpression) expr.ancestorsOrSelf() + .takeWhile(it -> it instanceof ASTExpression + || it instanceof ASTArgumentList && it.getParent() instanceof ASTExpression) + .last(); + } + + public static NodeStream getLoopVariables(ASTForStatement loop) { + @Nullable ASTStatement init = loop.getInit(); + + if (init instanceof ASTLocalVariableDeclaration) { + return ((ASTLocalVariableDeclaration) init).getVarIds(); + } + + return NodeStream.empty(); + } + // TODO at least UnusedPrivateMethod has some serialization-related logic. /** diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesTest.java index 322cc16603..3157601173 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class AvoidReassigningLoopVariablesTest extends PmdRuleTst { // no additional unit tests } From 8b1a743886648102284a994b0ef4d77b44ec4ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 30 Oct 2020 07:02:29 +0100 Subject: [PATCH 11/45] Finish it --- .../pmd/lang/java/ast/ASTDoStatement.java | 1 + .../pmd/lang/java/ast/ASTForStatement.java | 8 +- .../pmd/lang/java/ast/ASTLoopStatement.java | 14 ++ .../pmd/lang/java/ast/ASTWhileStatement.java | 1 + .../AvoidReassigningLoopVariablesRule.java | 185 ++++++++++++------ .../lang/java/rule/internal/JavaRuleUtil.java | 27 +++ .../xml/AvoidReassigningLoopVariables.xml | 3 +- 7 files changed, 167 insertions(+), 72 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDoStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDoStatement.java index 4837ee446e..4a4d32d917 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDoStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDoStatement.java @@ -25,6 +25,7 @@ public final class ASTDoStatement extends AbstractStatement implements ASTLoopSt * Returns the node that represents the guard of this loop. * This may be any expression of type boolean. */ + @Override public ASTExpression getCondition() { return (ASTExpression) getChild(1); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForStatement.java index 117ccb54c7..79a0c21c63 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForStatement.java @@ -29,13 +29,7 @@ public final class ASTForStatement extends AbstractStatement implements ASTLoopS } - /** - * Returns the node that represents the condition of this loop. - * This may be any expression of type boolean. - * - *

If there is no specified guard, then returns null. - */ - @Nullable + @Override public ASTExpression getCondition() { return getFirstChildOfType(ASTExpression.class); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLoopStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLoopStatement.java index 8ad32273ce..1097a4770b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLoopStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLoopStatement.java @@ -4,6 +4,8 @@ package net.sourceforge.pmd.lang.java.ast; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * A loop statement. * @@ -26,4 +28,16 @@ public interface ASTLoopStatement extends ASTStatement { return (ASTStatement) getLastChild(); } + + /** + * Returns the node that represents the condition of this loop. + * This may be any expression of type boolean. + * + *

If there is no specified guard, then returns null (in particular, + * returns null if this is a foreach loop). + */ + default @Nullable ASTExpression getCondition() { + return null; + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWhileStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWhileStatement.java index 683b4ad39d..ebc39d8d0a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWhileStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWhileStatement.java @@ -24,6 +24,7 @@ public final class ASTWhileStatement extends AbstractStatement implements ASTLoo * Returns the node that represents the guard of this loop. * This may be any expression of type boolean. */ + @Override public ASTExpression getCondition() { return (ASTExpression) getChild(0); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java index 52d5f0916a..deeec667f3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java @@ -9,19 +9,29 @@ import static net.sourceforge.pmd.properties.PropertyFactory.enumProperty; import static net.sourceforge.pmd.util.CollectionUtil.associateBy; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.NonNull; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.ast.NodeStream; import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.AccessType; +import net.sourceforge.pmd.lang.java.ast.ASTBlock; +import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement; +import net.sourceforge.pmd.lang.java.ast.ASTContinueStatement; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTForUpdate; import net.sourceforge.pmd.lang.java.ast.ASTForeachStatement; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; +import net.sourceforge.pmd.lang.java.ast.ASTLocalClassStatement; import net.sourceforge.pmd.lang.java.ast.ASTLoopStatement; +import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; import net.sourceforge.pmd.lang.java.ast.ASTStatement; import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; +import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; @@ -89,84 +99,131 @@ public class AvoidReassigningLoopVariablesRule extends AbstractOptimizationRule return data; } ASTForUpdate update = loopStmt.getFirstChildOfType(ASTForUpdate.class); - ASTStatement body = loopStmt.getBody(); - for (ASTVariableDeclaratorId loopVar : JavaRuleUtil.getLoopVariables(loopStmt)) { - for (ASTNamedReferenceExpr usage : loopVar.getUsages()) { - if (usage.getAccessType() == AccessType.WRITE) { - if (update != null && usage.ancestors(ASTForUpdate.class).first() == update) { - continue; + NodeStream loopVars = JavaRuleUtil.getLoopVariables(loopStmt); + if (behavior == ForReassignOption.DENY) { + for (ASTVariableDeclaratorId loopVar : loopVars) { + for (ASTNamedReferenceExpr usage : loopVar.getUsages()) { + if (usage.getAccessType() == AccessType.WRITE) { + if (update != null && usage.ancestors(ASTForUpdate.class).first() == update) { + continue; + } + addViolation(data, usage, loopVar.getName()); } - - if (behavior == ForReassignOption.SKIP - && JavaRuleUtil.isVarAccessReadAndWrite(usage) - && isConditionallyGuarded(usage, loopStmt)) { - continue; - } - addViolation(data, usage, loopVar.getName()); - } - } + } else { + Set loopVarNames = loopVars.collect(Collectors.mapping(ASTVariableDeclaratorId::getName, Collectors.toSet())); + Set labels = JavaRuleUtil.getStatementLabels(loopStmt); + new ControlFlowCtx(false, loopVarNames, (RuleContext) data, labels, false, false).roamStatementsForExit(loopStmt.getBody()); } return null; } - private static boolean isConditionallyGuarded(JavaNode node, ASTLoopStatement enclosingLoop) { - JavaNode parent = node.getParent(); + class ControlFlowCtx { - if (parent == enclosingLoop) { + private final boolean guarded; + private boolean mayExit; + private final Set loopVarNames; + private final RuleContext ruleCtx; + + private final Set outerLoopNames; + private final boolean breakHidden; + private final boolean continueHidden; + + ControlFlowCtx(boolean guarded, Set loopVarNames, RuleContext ctx, Set outerLoopNames, boolean breakHidden, boolean continueHidden) { + this.guarded = guarded; + this.loopVarNames = loopVarNames; + this.ruleCtx = ctx; + this.outerLoopNames = outerLoopNames; + this.breakHidden = breakHidden; + this.continueHidden = continueHidden; + } + + ControlFlowCtx guarded() { + return withGuard(true); + } + + ControlFlowCtx withGuard(boolean isGuarded) { + return new ControlFlowCtx(isGuarded, loopVarNames, ruleCtx, outerLoopNames, breakHidden, continueHidden); + } + + ControlFlowCtx copy(boolean isGuarded, boolean breakHidden, boolean continueHidden) { + return new ControlFlowCtx(isGuarded, loopVarNames, ruleCtx, outerLoopNames, breakHidden, continueHidden); + } + + // return true if may exit the outer loop abruptly via continue/break + private boolean roamStatementsForExit(NodeStream stmts) { + for (JavaNode stmt : stmts) { + if (stmt instanceof ASTThrowStatement + || stmt instanceof ASTReturnStatement) { + return true; + } else if (stmt instanceof ASTBreakStatement) { + String label = ((ASTBreakStatement) stmt).getLabel(); + return label != null && outerLoopNames.contains(label) || !breakHidden; + } else if (stmt instanceof ASTContinueStatement) { + String label = ((ASTContinueStatement) stmt).getLabel(); + return label != null && outerLoopNames.contains(label) || !continueHidden; + } + + // note that we mean to use |= and not shortcut evaluation + + if (stmt instanceof ASTLoopStatement) { + + ASTStatement body = ((ASTLoopStatement) stmt).getBody(); + for (JavaNode child : stmt.children()) { + if (child != body) { + checkVorViolations(child); + } + } + + mayExit |= copy(true, true, true).roamStatementsForExit(body); + + } else if (stmt instanceof ASTSwitchStatement) { + + checkVorViolations(((ASTSwitchStatement) stmt).getTestedExpression()); + + mayExit |= copy(true, true, false).roamStatementsForExit(stmt.children().drop(1)); + + } else if (stmt instanceof ASTIfStatement) { + + checkVorViolations(((ASTIfStatement) stmt).getCondition()); + mayExit |= guarded().roamStatementsForExit(((ASTIfStatement) stmt).getThenBranch()); + mayExit |= withGuard(this.guarded).roamStatementsForExit(((ASTIfStatement) stmt).getElseBranch()); + + } else if (stmt instanceof ASTExpression) { + + checkVorViolations(stmt); + + } else if (!(stmt instanceof ASTLocalClassStatement)) { + mayExit |= roamStatementsForExit(stmt.children()); + } + } return false; } - if (parent instanceof ASTLoopStatement) { - return node == ((ASTLoopStatement) parent).getBody() || isConditionallyGuarded(parent, enclosingLoop); - } - - if (parent instanceof ASTSwitchStatement) { - return node.getIndexInParent() != 0 || isConditionallyGuarded(parent, enclosingLoop); - } - - if (parent instanceof ASTIfStatement) { - return node.getIndexInParent() != 0 || isConditionallyGuarded(parent, enclosingLoop); - -// if (node.getIndexInParent() == 0) {// condition -// return isConditionallyGuarded(parent, enclosingLoop); -// } -// while (parent.getParent() instanceof ASTIfStatement) { -// parent = parent.getParent(); -// } -// return isConditionallyGuarded(parent, enclosingLoop); - } - - return isConditionallyGuarded(parent, enclosingLoop); - } - - private static boolean isConditionallyGuarded(ASTLoopStatement loop, ASTExpression expr) { - ASTIfStatement enclosingIf = JavaRuleUtil.getIfStmtIfExprInCondition(expr); - JavaNode previous = expr; - for (JavaNode parent : expr.ancestors()) { - if (parent == loop) { - break; - } - if (parent instanceof ASTIfStatement) { - if (previous instanceof ASTIfStatement && previous.getIndexInParent() == 1 - || !(previous instanceof ASTExpression)) { - return true; - } - } else if (parent instanceof ASTSwitchStatement) { - if (previous != parent.getFirstChild()) { - return true; - } - } else if (parent instanceof ASTLoopStatement) { - // we didn't come from the condition expr - if (previous == ((ASTLoopStatement) parent).getBody()) { - return true; - } + private boolean roamStatementsForExit(JavaNode node) { + if (node == null) { + return false; } - previous = parent; + NodeStream unwrappedBlock = + node instanceof ASTBlock + ? ((ASTBlock) node).toStream() + : NodeStream.of(node); + + return roamStatementsForExit(unwrappedBlock); + } + + private void checkVorViolations(JavaNode node) { + if (node == null) { + return; + } + node.descendants(ASTNamedReferenceExpr.class) + .filter(it -> loopVarNames.contains(it.getName())) + .filter(it -> (guarded || mayExit) ? JavaRuleUtil.isVarAccessStrictlyWrite(it) + : JavaRuleUtil.isVarAccessReadAndWrite(it)) + .forEach(it -> addViolation(ruleCtx, it, it.getName())); } - return false; } private enum ForeachReassignOption { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java index bde241946f..139eaceb25 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -9,7 +9,10 @@ import static net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKi import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectStreamField; +import java.util.Collections; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -26,6 +29,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; +import net.sourceforge.pmd.lang.java.ast.ASTLabeledStatement; import net.sourceforge.pmd.lang.java.ast.ASTList; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; @@ -126,6 +130,29 @@ public final class JavaRuleUtil { || ((ASTAssignmentExpression) expr.getParent()).getOperator().isCompound()); } + /** + * True if the variable access is a non-compound assignment. + */ + public static boolean isVarAccessStrictlyWrite(ASTNamedReferenceExpr expr) { + return expr.getParent() instanceof ASTAssignmentExpression + && expr.getIndexInParent() == 0 + && !((ASTAssignmentExpression) expr.getParent()).getOperator().isCompound(); + } + + /** + * Returns the set of labels on this statement. + */ + public static Set getStatementLabels(ASTStatement node) { + if (!(node.getParent() instanceof ASTLabeledStatement)) { + return Collections.emptySet(); + } + + return node.ancestors().takeWhile(it -> it instanceof ASTLabeledStatement) + .toStream() + .map(it -> ((ASTLabeledStatement) it).getLabel()) + .collect(Collectors.toSet()); + } + /** * True if the variable is incremented or decremented via a compound * assignment operator, or a unary increment/decrement expression. diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningLoopVariables.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningLoopVariables.xml index a75e19c559..25c6edbcc2 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningLoopVariables.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningLoopVariables.xml @@ -577,7 +577,8 @@ public class Foo { ]]> - + + violation: various conditional reassignments of 'for' loop variable, skip allowed skip 4 From 004e7792a0156ebe65dfcf23ebf3142a356e33fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 30 Oct 2020 07:26:27 +0100 Subject: [PATCH 12/45] Add more tests --- .../AvoidReassigningLoopVariablesRule.java | 46 +++++---- .../xml/AvoidReassigningLoopVariables.xml | 95 +++++++++++++++++++ 2 files changed, 117 insertions(+), 24 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java index deeec667f3..7d2ce987dd 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java @@ -139,19 +139,30 @@ public class AvoidReassigningLoopVariablesRule extends AbstractOptimizationRule this.continueHidden = continueHidden; } - ControlFlowCtx guarded() { - return withGuard(true); - } - ControlFlowCtx withGuard(boolean isGuarded) { - return new ControlFlowCtx(isGuarded, loopVarNames, ruleCtx, outerLoopNames, breakHidden, continueHidden); + return copy(isGuarded, breakHidden, continueHidden); } ControlFlowCtx copy(boolean isGuarded, boolean breakHidden, boolean continueHidden) { return new ControlFlowCtx(isGuarded, loopVarNames, ruleCtx, outerLoopNames, breakHidden, continueHidden); } - // return true if may exit the outer loop abruptly via continue/break + + private boolean roamStatementsForExit(JavaNode node) { + if (node == null) { + return false; + } + + NodeStream unwrappedBlock = + node instanceof ASTBlock + ? ((ASTBlock) node).toStream() + : NodeStream.of(node); + + return roamStatementsForExit(unwrappedBlock); + } + + // return true if any statement may exit the outer loop abruptly + // This way increments of variables are allowed if they are guarded by a conditional private boolean roamStatementsForExit(NodeStream stmts) { for (JavaNode stmt : stmts) { if (stmt instanceof ASTThrowStatement @@ -187,31 +198,18 @@ public class AvoidReassigningLoopVariablesRule extends AbstractOptimizationRule } else if (stmt instanceof ASTIfStatement) { checkVorViolations(((ASTIfStatement) stmt).getCondition()); - mayExit |= guarded().roamStatementsForExit(((ASTIfStatement) stmt).getThenBranch()); + mayExit |= withGuard(true).roamStatementsForExit(((ASTIfStatement) stmt).getThenBranch()); mayExit |= withGuard(this.guarded).roamStatementsForExit(((ASTIfStatement) stmt).getElseBranch()); - } else if (stmt instanceof ASTExpression) { - + } + // these two catch-all clauses implement other statements & eg switch branches + else if (stmt instanceof ASTExpression) { checkVorViolations(stmt); - } else if (!(stmt instanceof ASTLocalClassStatement)) { mayExit |= roamStatementsForExit(stmt.children()); } } - return false; - } - - private boolean roamStatementsForExit(JavaNode node) { - if (node == null) { - return false; - } - - NodeStream unwrappedBlock = - node instanceof ASTBlock - ? ((ASTBlock) node).toStream() - : NodeStream.of(node); - - return roamStatementsForExit(unwrappedBlock); + return mayExit; } private void checkVorViolations(JavaNode node) { diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningLoopVariables.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningLoopVariables.xml index 25c6edbcc2..c1088439ba 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningLoopVariables.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningLoopVariables.xml @@ -315,6 +315,44 @@ public class Foo { ]]> + + violation: break inside switch case + skip + 1 + + + + + no violation: continue inside switch case + skip + 0 + + + violation: incremented 'for' loop variable inside switch expression skip @@ -416,6 +454,63 @@ public class Foo { ]]> + + violation: incremented 'for' loop variable after nested for with break + skip + 1 + + + + + no violation: incremented 'for' loop variable after nested for with labeled break + skip + 0 + + + + + no violation: incremented 'for' loop variable inside nested for + skip + 0 + 4) { + break; + } + i++; + } + } + } + } + ]]> + + violation: incremented 'for' loop variable inside nested for declaration skip From 524cf33fc986d3409779d7e88d9b197cd44f71fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 30 Oct 2020 07:47:49 +0100 Subject: [PATCH 13/45] Update AvoidReassigningCatchVariablesRule --- .travis/all-java.xml | 4 +- .../lang/java/ast/ASTMethodDeclaration.java | 11 ----- .../AvoidReassigningCatchVariablesRule.java | 47 ++++++------------- .../AvoidReassigningCatchVariablesTest.java | 1 - 4 files changed, 17 insertions(+), 46 deletions(-) diff --git a/.travis/all-java.xml b/.travis/all-java.xml index a5b71bc4d3..c6fe7ed84d 100644 --- a/.travis/all-java.xml +++ b/.travis/all-java.xml @@ -15,8 +15,8 @@ - - + + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java index 76017e4045..7ccde2be00 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java @@ -61,17 +61,6 @@ public final class ASTMethodDeclaration extends AbstractMethodOrConstructorDecla return isAnnotationPresent(Override.class); } - /** - * Returns true if this method is overridden. - * TODO for now, this just checks for an @Override annotation, - * but this should definitely do what MissingOverride does. - * This could be useful in UnusedPrivateMethod (to check not only private methods), - * and also UselessOverridingMethod, and overall many many rules. - */ - public boolean isOverridden() { - return isAnnotationPresent(Override.class); - } - @Override protected @Nullable JavaccToken getPreferredReportLocation() { return TokenUtils.nthPrevious(getModifiers().getLastToken(), getFormalParameters().getFirstToken(), 1); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesRule.java index 6c36c60109..94d0fcbbd4 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesRule.java @@ -4,48 +4,31 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; -import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; -import net.sourceforge.pmd.lang.java.ast.ASTCatchClause; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; +import org.checkerframework.checker.nullness.qual.NonNull; + +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.AccessType; +import net.sourceforge.pmd.lang.java.ast.ASTCatchParameter; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; -import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.symboltable.NameOccurrence; +import net.sourceforge.pmd.lang.rule.RuleTargetSelector; public class AvoidReassigningCatchVariablesRule extends AbstractJavaRule { - public AvoidReassigningCatchVariablesRule() { - addRuleChainVisit(ASTCatchClause.class); + @Override + protected @NonNull RuleTargetSelector buildTargetSelector() { + return RuleTargetSelector.forTypes(ASTCatchParameter.class); } @Override - public Object visit(ASTCatchClause catchStatement, Object data) { - ASTVariableDeclaratorId caughtExceptionId = catchStatement.getParameter().getVarId(); - String caughtExceptionVar = caughtExceptionId.getName(); - for (NameOccurrence usage : caughtExceptionId.oldGetUsages()) { - JavaNode operation = getOperationOfUsage(usage); - if (isAssignment(operation)) { - String assignedVar = getAssignedVariableName(operation); - if (caughtExceptionVar.equals(assignedVar)) { - addViolation(data, operation, caughtExceptionVar); - } + public Object visit(ASTCatchParameter catchParam, Object data) { + ASTVariableDeclaratorId caughtExceptionId = catchParam.getVarId(); + for (ASTNamedReferenceExpr usage : caughtExceptionId.getUsages()) { + if (usage.getAccessType() == AccessType.WRITE) { + addViolation(data, usage, caughtExceptionId.getName()); } + } return data; } - - private JavaNode getOperationOfUsage(NameOccurrence usage) { - return usage.getLocation() - .getFirstParentOfType(ASTPrimaryExpression.class) - .getParent(); - } - - private boolean isAssignment(JavaNode operation) { - return operation.hasDescendantOfType(ASTAssignmentOperator.class); - } - - private String getAssignedVariableName(JavaNode operation) { - return operation.getFirstDescendantOfType(ASTName.class).getImage(); - } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesTest.java index 9088347cc0..5afdbca56b 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class AvoidReassigningCatchVariablesTest extends PmdRuleTst { // no additional unit tests } From 1ec5ca80131bd6ef56786ec088f47350e82d8d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 30 Oct 2020 10:09:56 +0100 Subject: [PATCH 14/45] Update VarCouldBeFinal (2 rules) --- .travis/all-java.xml | 4 +- .../java/ast/ASTVariableDeclaratorId.java | 8 +++ .../AvoidReassigningLoopVariablesRule.java | 13 +--- .../LocalVariableCouldBeFinalRule.java | 32 +++------ .../MethodArgumentCouldBeFinalRule.java | 66 ++++++++++++------- .../performance/AbstractOptimizationRule.java | 44 ------------- .../LocalVariableCouldBeFinalTest.java | 1 - .../MethodArgumentCouldBeFinalTest.java | 1 - .../xml/LocalVariableCouldBeFinal.xml | 28 ++++++-- .../xml/MethodArgumentCouldBeFinal.xml | 9 ++- 10 files changed, 91 insertions(+), 115 deletions(-) delete mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AbstractOptimizationRule.java diff --git a/.travis/all-java.xml b/.travis/all-java.xml index c6fe7ed84d..954be32745 100644 --- a/.travis/all-java.xml +++ b/.travis/all-java.xml @@ -90,11 +90,11 @@ - + - + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java index 35aa495fb6..d2917f9028 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java @@ -90,6 +90,9 @@ public final class ASTVariableDeclaratorId extends AbstractTypedSymbolDeclarator * Returns an unmodifiable list of the usages of this variable that * are made in this file. Note that for a record component, this returns * usages both for the formal parameter symbol and its field counterpart. + * + *

Note that a variable initializer is not part of the usages + * (though this should be evident from the return type). */ public List getUsages() { return usages; @@ -102,6 +105,11 @@ public final class ASTVariableDeclaratorId extends AbstractTypedSymbolDeclarator usages.add(usage); } + @Override + public Visibility getVisibility() { + return getModifierOwnerParent().getVisibility(); + } + /** * Returns the extra array dimensions associated with this variable. * For example in the declaration {@code int a[]}, {@link #getTypeNode()} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java index 7d2ce987dd..5de50b11c8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java @@ -12,8 +12,6 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import org.checkerframework.checker.nullness.qual.NonNull; - import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.NodeStream; import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; @@ -34,13 +32,12 @@ import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; -import net.sourceforge.pmd.lang.java.rule.performance.AbstractOptimizationRule; -import net.sourceforge.pmd.lang.rule.RuleTargetSelector; import net.sourceforge.pmd.properties.PropertyDescriptor; import net.sourceforge.pmd.util.StringUtil.CaseConvention; -public class AvoidReassigningLoopVariablesRule extends AbstractOptimizationRule { +public class AvoidReassigningLoopVariablesRule extends AbstractJavaRulechainRule { private static final Map FOREACH_REASSIGN_VALUES = associateBy(asList(ForeachReassignOption.values()), ForeachReassignOption::getDisplayName); @@ -61,15 +58,11 @@ public class AvoidReassigningLoopVariablesRule extends AbstractOptimizationRule .build(); public AvoidReassigningLoopVariablesRule() { + super(ASTForStatement.class, ASTForeachStatement.class); definePropertyDescriptor(FOREACH_REASSIGN); definePropertyDescriptor(FOR_REASSIGN); } - @Override - protected @NonNull RuleTargetSelector buildTargetSelector() { - return RuleTargetSelector.forTypes(ASTForStatement.class, ASTForeachStatement.class); - } - @Override public Object visit(ASTForeachStatement loopStmt, Object data) { ForeachReassignOption behavior = getProperty(FOREACH_REASSIGN); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalRule.java index b7ce0cab4b..efb2b384c9 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalRule.java @@ -6,45 +6,31 @@ package net.sourceforge.pmd.lang.java.rule.codestyle; import static net.sourceforge.pmd.properties.PropertyFactory.booleanProperty; -import java.util.List; -import java.util.Map; - -import net.sourceforge.pmd.lang.java.ast.ASTForStatement; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.java.ast.ASTForeachStatement; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; -import net.sourceforge.pmd.lang.java.rule.performance.AbstractOptimizationRule; -import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; -import net.sourceforge.pmd.lang.symboltable.NameOccurrence; -import net.sourceforge.pmd.lang.symboltable.Scope; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; import net.sourceforge.pmd.properties.PropertyDescriptor; -public class LocalVariableCouldBeFinalRule extends AbstractOptimizationRule { +public class LocalVariableCouldBeFinalRule extends AbstractJavaRulechainRule { private static final PropertyDescriptor IGNORE_FOR_EACH = - booleanProperty("ignoreForEachDecl").defaultValue(false).desc("Ignore non-final loop variables in a for-each statement.").build(); + booleanProperty("ignoreForEachDecl").defaultValue(false).desc("Ignore non-final loop variables in a for-each statement.").build(); public LocalVariableCouldBeFinalRule() { + super(ASTLocalVariableDeclaration.class); definePropertyDescriptor(IGNORE_FOR_EACH); } @Override public Object visit(ASTLocalVariableDeclaration node, Object data) { - if (node.isFinal()) { + if (node.isFinal()) { // also for implicit finals, like resources return data; } - if (getProperty(IGNORE_FOR_EACH) && node.getParent() instanceof ASTForStatement) { + if (getProperty(IGNORE_FOR_EACH) && node.getParent() instanceof ASTForeachStatement) { return data; } - Scope s = node.getScope(); - Map> decls = s.getDeclarations(VariableNameDeclaration.class); - for (Map.Entry> entry : decls.entrySet()) { - VariableNameDeclaration var = entry.getKey(); - if (var.getAccessNodeParent() != node) { - continue; - } - if (!assigned(entry.getValue())) { - addViolation(data, var.getAccessNodeParent(), var.getImage()); - } - } + MethodArgumentCouldBeFinalRule.checkForFinal((RuleContext) data, this, node.getVarIds()); return data; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalRule.java index 0d11dcf72d..4b3a6c3b95 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalRule.java @@ -4,44 +4,60 @@ package net.sourceforge.pmd.lang.java.rule.codestyle; -import java.util.List; -import java.util.Map; - +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.AccessType; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.AccessNode; -import net.sourceforge.pmd.lang.java.rule.performance.AbstractOptimizationRule; -import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; -import net.sourceforge.pmd.lang.symboltable.NameOccurrence; -import net.sourceforge.pmd.lang.symboltable.Scope; +import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; +import net.sourceforge.pmd.lang.rule.AbstractRule; -public class MethodArgumentCouldBeFinalRule extends AbstractOptimizationRule { +public class MethodArgumentCouldBeFinalRule extends AbstractJavaRulechainRule { + + public MethodArgumentCouldBeFinalRule() { + super(ASTMethodOrConstructorDeclaration.class); + } @Override public Object visit(ASTMethodDeclaration meth, Object data) { - if (meth.isNative() || meth.isAbstract()) { + if (meth.getBody() == null) { return data; } - this.lookForViolation(meth.getScope(), data); - return super.visit(meth, data); - } - - private void lookForViolation(Scope scope, Object data) { - Map> decls = scope.getDeclarations(VariableNameDeclaration.class); - for (Map.Entry> entry : decls.entrySet()) { - VariableNameDeclaration var = entry.getKey(); - AccessNode node = var.getAccessNodeParent(); - if (!node.isFinal() && node instanceof ASTFormalParameter && !assigned(entry.getValue())) { - addViolation(data, node, var.getImage()); - } - } + lookForViolation(meth, data); + return data; } @Override public Object visit(ASTConstructorDeclaration constructor, Object data) { - this.lookForViolation(constructor.getScope(), data); - return super.visit(constructor, data); + lookForViolation(constructor, data); + return data; + } + + private void lookForViolation(ASTMethodOrConstructorDeclaration node, Object data) { + checkForFinal((RuleContext) data, this, node.getFormalParameters().toStream().map(ASTFormalParameter::getVarId)); + } + + static void checkForFinal(RuleContext ruleContext, AbstractRule rule, NodeStream variables) { + outer: + for (ASTVariableDeclaratorId var : variables) { + if (var.isFinal()) { + continue; + } + boolean used = false; + for (ASTNamedReferenceExpr usage : var.getUsages()) { + used = true; + if (usage.getAccessType() == AccessType.WRITE) { + continue outer; + } + } + if (used) { + rule.addViolation(ruleContext, var, var.getName()); + } + } } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AbstractOptimizationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AbstractOptimizationRule.java deleted file mode 100644 index f882fc860d..0000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AbstractOptimizationRule.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.performance; - -import java.util.List; - -import net.sourceforge.pmd.annotation.InternalApi; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence; -import net.sourceforge.pmd.lang.symboltable.NameOccurrence; - -/** - * Base class with utility methods for optimization rules - * - * @author mgriffa - * @since Created on Jan 11, 2005 - * @deprecated Internal API - */ -@Deprecated -@InternalApi -public class AbstractOptimizationRule extends AbstractJavaRule { - - @Override - public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - if (node.isInterface()) { - return data; - } - return super.visit(node, data); - } - - protected boolean assigned(List usages) { - for (NameOccurrence occ : usages) { - JavaNameOccurrence jocc = (JavaNameOccurrence) occ; - if (jocc.isOnLeftHandSide() || jocc.isSelfAssignment()) { - return true; - } - } - return false; - } - -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalTest.java index a903c67f45..d342b03ba9 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.codestyle; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class LocalVariableCouldBeFinalTest extends PmdRuleTst { // no additional unit tests } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalTest.java index 7adb238896..66b362def9 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.codestyle; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class MethodArgumentCouldBeFinalTest extends PmdRuleTst { // no additional unit tests } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalVariableCouldBeFinal.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalVariableCouldBeFinal.xml index 3a7429e4c6..e296ff7cc2 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalVariableCouldBeFinal.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalVariableCouldBeFinal.xml @@ -5,19 +5,20 @@ xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - TEST1 + May be final 1 - TEST2 + Final local var 0 - TEST3 + Unused, no violation + 0 + + + + + Assigned 0 - TEST4 + Compound assignment 0 - TEST5 + + Blank local var 2 - TEST6 + Prefix increment 0 - TEST8 + Postfix increment 0 1 @@ -19,7 +21,10 @@ public class Foo { 2 From 1f66609fb1ed25bfa85d812a4427a2bf1e530d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 31 Oct 2020 11:55:21 +0100 Subject: [PATCH 15/45] Cleanup --- .../AvoidReassigningLoopVariablesRule.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java index 5de50b11c8..afa96196e8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java @@ -184,15 +184,17 @@ public class AvoidReassigningLoopVariablesRule extends AbstractJavaRulechainRule } else if (stmt instanceof ASTSwitchStatement) { - checkVorViolations(((ASTSwitchStatement) stmt).getTestedExpression()); + ASTSwitchStatement switchStmt = (ASTSwitchStatement) stmt; + checkVorViolations(switchStmt.getTestedExpression()); - mayExit |= copy(true, true, false).roamStatementsForExit(stmt.children().drop(1)); + mayExit |= copy(true, true, false).roamStatementsForExit(switchStmt.getBranches()); } else if (stmt instanceof ASTIfStatement) { - checkVorViolations(((ASTIfStatement) stmt).getCondition()); - mayExit |= withGuard(true).roamStatementsForExit(((ASTIfStatement) stmt).getThenBranch()); - mayExit |= withGuard(this.guarded).roamStatementsForExit(((ASTIfStatement) stmt).getElseBranch()); + ASTIfStatement ifStmt = (ASTIfStatement) stmt; + checkVorViolations(ifStmt.getCondition()); + mayExit |= withGuard(true).roamStatementsForExit(ifStmt.getThenBranch()); + mayExit |= withGuard(this.guarded).roamStatementsForExit(ifStmt.getElseBranch()); } // these two catch-all clauses implement other statements & eg switch branches From 880c51824b7f8539f7bd868515957002af83e6f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 31 Oct 2020 13:13:13 +0100 Subject: [PATCH 16/45] Fix compil --- .../pmd/lang/java/ast/ASTVariableDeclaratorId.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java index c91b524a13..aaf4d4642e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java @@ -135,12 +135,6 @@ public final class ASTVariableDeclaratorId extends AbstractTypedSymbolDeclarator } - @Override - public Visibility getVisibility() { - return isPatternBinding() ? Visibility.V_LOCAL - : getModifierOwnerParent().getVisibility(); - } - private AccessNode getModifierOwnerParent() { JavaNode parent = getParent(); if (parent instanceof ASTVariableDeclarator) { From fee83e567925425485ac27943e181c28a67fc3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 31 Oct 2020 13:59:10 +0100 Subject: [PATCH 17/45] Update SimplifyBooleanReturns --- .travis/all-java.xml | 2 +- .../pmd/lang/java/ast/ASTReturnStatement.java | 9 + .../pmd/lang/java/ast/BinaryOp.java | 22 ++ .../design/SimplifyBooleanReturnsRule.java | 330 +++++++----------- .../design/SimplifyBooleanReturnsTest.java | 1 - .../design/xml/SimplifyBooleanReturns.xml | 13 + 6 files changed, 165 insertions(+), 212 deletions(-) diff --git a/.travis/all-java.xml b/.travis/all-java.xml index 954be32745..1db8013567 100644 --- a/.travis/all-java.xml +++ b/.travis/all-java.xml @@ -153,7 +153,7 @@ - + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReturnStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReturnStatement.java index bd95e6a0d1..ea67c7f742 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReturnStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReturnStatement.java @@ -6,6 +6,8 @@ package net.sourceforge.pmd.lang.java.ast; import org.checkerframework.checker.nullness.qual.Nullable; +import net.sourceforge.pmd.lang.ast.NodeStream; + /** * A return statement in a method or constructor body. * @@ -28,6 +30,13 @@ public final class ASTReturnStatement extends AbstractStatement { return visitor.visit(this, data); } + /** + * Returns the method, ctor or lambda that this statement terminates. + */ + public JavaNode getTarget() { + return ancestors().map(NodeStream.asInstanceOf(ASTMethodOrConstructorDeclaration.class, ASTLambdaExpression.class)).first(); + } + /** * Returns the returned expression, or null if this is a simple return. */ diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/BinaryOp.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/BinaryOp.java index 025fc9c09f..8c2542a0bb 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/BinaryOp.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/BinaryOp.java @@ -166,6 +166,28 @@ public enum BinaryOp implements InternalInterfaces.OperatorLike { } } + /** + * Complement, for boolean operators. Eg for {@code ==}, return {@code !=}, + * for {@code <=}, returns {@code >}. Returns null if this is another kind + * of operator. + */ + public BinaryOp getComplement() { + switch (this) { + case CONDITIONAL_OR: return CONDITIONAL_AND; + case CONDITIONAL_AND: return CONDITIONAL_OR; + case OR: return AND; + case AND: return OR; + + case EQ: return NE; + case NE: return EQ; + case LE: return GT; + case GE: return LT; + case GT: return LE; + case LT: return GE; + } + return null; + } + @Override public String toString() { return this.code; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java index 624608b721..03b406b642 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java @@ -4,258 +4,168 @@ package net.sourceforge.pmd.lang.java.rule.design; -import net.sourceforge.pmd.lang.ast.Node; +import java.util.Iterator; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import net.sourceforge.pmd.lang.ast.GenericToken; +import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; import net.sourceforge.pmd.lang.java.ast.ASTBlock; import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression; import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; -import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpressionNotPlusMinus; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.ast.ASTStatement; +import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression; +import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.ast.UnaryOp; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind; -public class SimplifyBooleanReturnsRule extends AbstractJavaRule { +public class SimplifyBooleanReturnsRule extends AbstractJavaRulechainRule { + + public SimplifyBooleanReturnsRule() { + super(ASTReturnStatement.class); + } @Override - public Object visit(ASTMethodDeclaration node, Object data) { - // only boolean methods should be inspected - if (node.getResultType().getTypeMirror().isPrimitive(PrimitiveTypeKind.BOOLEAN)) { - return super.visit(node, data); + public Object visit(ASTReturnStatement node, Object data) { + ASTExpression expr = node.getExpr(); + if (expr == null + || !expr.getTypeMirror().isPrimitive(PrimitiveTypeKind.BOOLEAN) + || !isThenBranchOfSomeIf(node)) { + return null; } - // skip method - return data; + return visit(node.ancestors(ASTIfStatement.class).firstOrThrow(), data); + } + + // Only explore the then branch. If we explore the else, then we'll report twice. + // In the case if .. else, both branches need to be symmetric anyway. + private boolean isThenBranchOfSomeIf(ASTReturnStatement node) { + if (node.getParent() instanceof ASTIfStatement) { + return node.getIndexInParent() == 1; + } + if (node.getParent() instanceof ASTBlock + && ((ASTBlock) node.getParent()).size() == 1 + && node.getParent().getParent() instanceof ASTIfStatement) { + return node.getParent().getIndexInParent() == 1; + } + return false; + } + + private @Nullable ASTReturnStatement asReturnStatement(ASTStatement node) { + if (node instanceof ASTReturnStatement) { + return (ASTReturnStatement) node; + } else if (node instanceof ASTBlock && ((ASTBlock) node).size() == 1) { + return asReturnStatement(((ASTBlock) node).get(0)); + } + return null; } @Override public Object visit(ASTIfStatement node, Object data) { // that's the case: if..then..return; return; - if (!node.hasElse() && isIfJustReturnsBoolean(node) && isJustReturnsBooleanAfter(node)) { - addViolation(data, node); - return super.visit(node, data); - } - - // only deal with if..then..else stmts - if (node.getNumChildren() != 3) { - return super.visit(node, data); - } - - // don't bother if either the if or the else block is empty - if (node.getChild(1).getNumChildren() == 0 || node.getChild(2).getNumChildren() == 0) { - return super.visit(node, data); - } - - Node returnStatement1 = node.getChild(1).getChild(0); - Node returnStatement2 = node.getChild(2).getChild(0); - - if (returnStatement1 instanceof ASTReturnStatement && returnStatement2 instanceof ASTReturnStatement) { - Node expression1 = returnStatement1.getChild(0).getChild(0); - Node expression2 = returnStatement2.getChild(0).getChild(0); - if (terminatesInBooleanLiteral(returnStatement1) && terminatesInBooleanLiteral(returnStatement2)) { + if (!node.hasElse()) { + if (isFollowedByReturn(node)) { addViolation(data, node); - } else if (expression1 instanceof ASTUnaryExpressionNotPlusMinus - ^ expression2 instanceof ASTUnaryExpressionNotPlusMinus) { + } + return data; + } + + ASTReturnStatement returnStatement1 = asReturnStatement(node.getElseBranch()); + ASTReturnStatement returnStatement2 = asReturnStatement(node.getThenBranch()); + + if (returnStatement1 != null && returnStatement2 != null) { + ASTExpression e1 = returnStatement1.getExpr(); + ASTExpression e2 = returnStatement2.getExpr(); + + if (isBooleanLiteral(e1) && isBooleanLiteral(e2)) { + addViolation(data, node); + } else if (isBooleanNegation(e1) ^ isBooleanNegation(e2)) { // We get the nodes under the '!' operator // If they are the same => error - if (isNodesEqualWithUnaryExpression(expression1, expression2)) { - // second case: - // If - // Expr - // Statement - // ReturnStatement - // UnaryExpressionNotPlusMinus '!' - // Expression E - // Statement - // ReturnStatement - // Expression E - // i.e., - // if (foo) - // return !a; - // else - // return a; + if (areComplements(e1, e2)) { + // if (foo) return !a; + // else return a; addViolation(data, node); } } - } else if (hasOneBlockStmt(node.getChild(1)) && hasOneBlockStmt(node.getChild(2))) { - // We have blocks so we must go down three levels (BlockStatement, - // Statement, ReturnStatement) - returnStatement1 = returnStatement1.getChild(0).getChild(0).getChild(0); - returnStatement2 = returnStatement2.getChild(0).getChild(0).getChild(0); - - // if we have 2 return; - if (isSimpleReturn(returnStatement1) && isSimpleReturn(returnStatement2)) { - // third case - // If - // Expr - // Statement - // Block - // BlockStatement - // Statement - // ReturnStatement - // Statement - // Block - // BlockStatement - // Statement - // ReturnStatement - // i.e., - // if (foo) { - // return true; - // } else { - // return false; - // } - addViolation(data, node); - } else { - Node expression1 = getDescendant(returnStatement1, 4); - Node expression2 = getDescendant(returnStatement2, 4); - if (terminatesInBooleanLiteral(node.getChild(1).getChild(0)) - && terminatesInBooleanLiteral(node.getChild(2).getChild(0))) { - addViolation(data, node); - } else if (expression1 instanceof ASTUnaryExpressionNotPlusMinus - ^ expression2 instanceof ASTUnaryExpressionNotPlusMinus) { - // We get the nodes under the '!' operator - // If they are the same => error - if (isNodesEqualWithUnaryExpression(expression1, expression2)) { - // forth case - // If - // Expr - // Statement - // Block - // BlockStatement - // Statement - // ReturnStatement - // UnaryExpressionNotPlusMinus '!' - // Expression E - // Statement - // Block - // BlockStatement - // Statement - // ReturnStatement - // Expression E - // i.e., - // if (foo) { - // return !a; - // } else { - // return a; - // } - addViolation(data, node); - } - } - } } - return super.visit(node, data); + return data; } /** * Checks, whether there is a statement after the given if statement, and if * so, whether this is just a return boolean statement. * - * @param ifNode - * the if statement - * @return + * @param ifNode the if statement */ - private boolean isJustReturnsBooleanAfter(ASTIfStatement ifNode) { - Node blockStatement = ifNode.getParent().getParent(); - Node block = blockStatement.getParent(); - if (block.getNumChildren() != blockStatement.getIndexInParent() + 1 + 1) { - return false; - } - - Node nextBlockStatement = block.getChild(blockStatement.getIndexInParent() + 1); - return terminatesInBooleanLiteral(nextBlockStatement); + private boolean isFollowedByReturn(ASTIfStatement ifNode) { + return ifNode.asStream().followingSiblings() + .take(1) + .filter(it -> it instanceof ASTReturnStatement) + .nonEmpty(); } - /** - * Checks whether the given ifstatement just returns a boolean in the if - * clause. - * - * @param ifNode - * the if statement - * @return - */ - private boolean isIfJustReturnsBoolean(ASTIfStatement ifNode) { - Node node = ifNode.getChild(1); - return node.getNumChildren() == 1 - && (hasOneBlockStmt(node) || terminatesInBooleanLiteral(node.getChild(0))); - } - - private boolean hasOneBlockStmt(Node node) { - return node.getChild(0) instanceof ASTBlock && node.getChild(0).getNumChildren() == 1 - && terminatesInBooleanLiteral(node.getChild(0).getChild(0)); - } - - /** - * Returns the first child node going down 'level' levels or null if level - * is invalid - */ - private Node getDescendant(Node node, int level) { - Node n = node; - for (int i = 0; i < level; i++) { - if (n.getNumChildren() == 0) { - return null; + // this method must be symmetric + private static boolean areComplements(ASTExpression e1, ASTExpression e2) { + if (isBooleanNegation(e1)) { + return isEqual(unaryOperand(e1), e2); + } else if (isBooleanNegation(e2)) { + return isEqual(e1, unaryOperand(e2)); + } else if (e1 instanceof ASTInfixExpression && e2 instanceof ASTInfixExpression) { + ASTInfixExpression ifx1 = (ASTInfixExpression) e1; + ASTInfixExpression ifx2 = (ASTInfixExpression) e2; + if (ifx1.getOperator().getComplement() != ifx2.getOperator()) { + return false; } - n = n.getChild(0); + if (ifx1.getOperator().isEquality()) { + // NOT(a == b, a != b) + // NOT(a == b, b != a) + return isEqual(ifx1.getLeftOperand(), ifx2.getLeftOperand()) + && isEqual(ifx1.getRightOperand(), ifx2.getRightOperand()) + || isEqual(ifx2.getLeftOperand(), ifx1.getLeftOperand()) + && isEqual(ifx2.getRightOperand(), ifx1.getRightOperand()); + } + // todo we could continue with de Morgan and stuff, and move this into a library } - return n; + return false; } - private boolean terminatesInBooleanLiteral(Node node) { - return eachNodeHasOneChild(node) && getLastChild(node) instanceof ASTBooleanLiteral; + private static boolean isEqual(ASTExpression e1, ASTExpression e2) { + return tokenEquals(e1, e2); } - private boolean eachNodeHasOneChild(Node node) { - if (node.getNumChildren() > 1) { - return false; - } - if (node.getNumChildren() == 0) { - return true; - } - return eachNodeHasOneChild(node.getChild(0)); - } - private Node getLastChild(Node node) { - if (node.getNumChildren() == 0) { - return node; - } - return getLastChild(node.getChild(0)); - } - - private boolean isNodesEqualWithUnaryExpression(Node n1, Node n2) { - Node node1; - Node node2; - if (n1 instanceof ASTUnaryExpressionNotPlusMinus) { - node1 = n1.getChild(0); - } else { - node1 = n1; - } - if (n2 instanceof ASTUnaryExpressionNotPlusMinus) { - node2 = n2.getChild(0); - } else { - node2 = n2; - } - return isNodesEquals(node1, node2); - } - - private boolean isNodesEquals(Node n1, Node n2) { - int numberChild1 = n1.getNumChildren(); - int numberChild2 = n2.getNumChildren(); - if (numberChild1 != numberChild2) { - return false; - } - if (!n1.getClass().equals(n2.getClass())) { - return false; - } - if (!n1.toString().equals(n2.toString())) { - return false; - } - for (int i = 0; i < numberChild1; i++) { - if (!isNodesEquals(n1.getChild(i), n2.getChild(i))) { + private static boolean tokenEquals(JavaNode node, JavaNode that) { + Iterator thisIt = GenericToken.range(node.getFirstToken(), node.getLastToken()); + Iterator thatIt = GenericToken.range(that.getFirstToken(), that.getLastToken()); + while (thisIt.hasNext()) { + if (!thatIt.hasNext()) { + return false; + } + JavaccToken o1 = thisIt.next(); + JavaccToken o2 = thatIt.next(); + if (o1.kind != o2.kind + || !o2.getImage().equals(o2.getImage())) { return false; } } - return true; + return !thatIt.hasNext(); } - private boolean isSimpleReturn(Node node) { - return node instanceof ASTReturnStatement && node.getNumChildren() == 0; + + private static boolean isBooleanLiteral(ASTExpression e) { + return e instanceof ASTBooleanLiteral; + } + + private static boolean isBooleanNegation(ASTExpression e) { + return e instanceof ASTUnaryExpression && ((ASTUnaryExpression) e).getOperator() == UnaryOp.NEGATION; + } + + private static ASTExpression unaryOperand(ASTExpression e) { + return ((ASTUnaryExpression) e).getOperand(); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsTest.java index 17f5f12d74..487cc554e4 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.design; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class SimplifyBooleanReturnsTest extends PmdRuleTst { // no additional unit tests } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanReturns.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanReturns.xml index cc1f0664a4..b919c38763 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanReturns.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanReturns.xml @@ -94,6 +94,19 @@ public class SimplifyBooleanReturns { return true; return false; } +} + ]]> + + + Check for negated expr + 1 + From 8085d364b6bb229945353ef000b34e5b179ab88d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 31 Oct 2020 14:02:44 +0100 Subject: [PATCH 18/45] Move utils into JavaRuleUtil --- .../design/SimplifyBooleanReturnsRule.java | 81 +------------------ .../lang/java/rule/internal/JavaRuleUtil.java | 72 +++++++++++++++++ 2 files changed, 76 insertions(+), 77 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java index 03b406b642..aab907ad7c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java @@ -4,22 +4,17 @@ package net.sourceforge.pmd.lang.java.rule.design; -import java.util.Iterator; +import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.areComplements; +import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.isBooleanLiteral; +import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.isBooleanNegation; import org.checkerframework.checker.nullness.qual.Nullable; -import net.sourceforge.pmd.lang.ast.GenericToken; -import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; import net.sourceforge.pmd.lang.java.ast.ASTBlock; -import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression; import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; import net.sourceforge.pmd.lang.java.ast.ASTStatement; -import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression; -import net.sourceforge.pmd.lang.java.ast.JavaNode; -import net.sourceforge.pmd.lang.java.ast.UnaryOp; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind; @@ -95,77 +90,9 @@ public class SimplifyBooleanReturnsRule extends AbstractJavaRulechainRule { return data; } - /** - * Checks, whether there is a statement after the given if statement, and if - * so, whether this is just a return boolean statement. - * - * @param ifNode the if statement - */ private boolean isFollowedByReturn(ASTIfStatement ifNode) { - return ifNode.asStream().followingSiblings() - .take(1) - .filter(it -> it instanceof ASTReturnStatement) - .nonEmpty(); + return ifNode.asStream().followingSiblings().first() instanceof ASTReturnStatement; } - // this method must be symmetric - private static boolean areComplements(ASTExpression e1, ASTExpression e2) { - if (isBooleanNegation(e1)) { - return isEqual(unaryOperand(e1), e2); - } else if (isBooleanNegation(e2)) { - return isEqual(e1, unaryOperand(e2)); - } else if (e1 instanceof ASTInfixExpression && e2 instanceof ASTInfixExpression) { - ASTInfixExpression ifx1 = (ASTInfixExpression) e1; - ASTInfixExpression ifx2 = (ASTInfixExpression) e2; - if (ifx1.getOperator().getComplement() != ifx2.getOperator()) { - return false; - } - if (ifx1.getOperator().isEquality()) { - // NOT(a == b, a != b) - // NOT(a == b, b != a) - return isEqual(ifx1.getLeftOperand(), ifx2.getLeftOperand()) - && isEqual(ifx1.getRightOperand(), ifx2.getRightOperand()) - || isEqual(ifx2.getLeftOperand(), ifx1.getLeftOperand()) - && isEqual(ifx2.getRightOperand(), ifx1.getRightOperand()); - } - // todo we could continue with de Morgan and stuff, and move this into a library - } - return false; - } - - private static boolean isEqual(ASTExpression e1, ASTExpression e2) { - return tokenEquals(e1, e2); - } - - - private static boolean tokenEquals(JavaNode node, JavaNode that) { - Iterator thisIt = GenericToken.range(node.getFirstToken(), node.getLastToken()); - Iterator thatIt = GenericToken.range(that.getFirstToken(), that.getLastToken()); - while (thisIt.hasNext()) { - if (!thatIt.hasNext()) { - return false; - } - JavaccToken o1 = thisIt.next(); - JavaccToken o2 = thatIt.next(); - if (o1.kind != o2.kind - || !o2.getImage().equals(o2.getImage())) { - return false; - } - } - return !thatIt.hasNext(); - } - - - private static boolean isBooleanLiteral(ASTExpression e) { - return e instanceof ASTBooleanLiteral; - } - - private static boolean isBooleanNegation(ASTExpression e) { - return e instanceof ASTUnaryExpression && ((ASTUnaryExpression) e).getOperator() == UnaryOp.NEGATION; - } - - private static ASTExpression unaryOperand(ASTExpression e) { - return ((ASTUnaryExpression) e).getOperand(); - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java index 139eaceb25..20cf44a215 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -10,6 +10,7 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectStreamField; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -17,11 +18,14 @@ import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import net.sourceforge.pmd.lang.ast.GenericToken; import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.AccessType; import net.sourceforge.pmd.lang.java.ast.ASTAssignmentExpression; +import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; @@ -29,6 +33,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; +import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression; import net.sourceforge.pmd.lang.java.ast.ASTLabeledStatement; import net.sourceforge.pmd.lang.java.ast.ASTList; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; @@ -39,7 +44,9 @@ import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.AccessNode.Visibility; import net.sourceforge.pmd.lang.java.ast.JModifier; +import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.ast.TypeNode; +import net.sourceforge.pmd.lang.java.ast.UnaryOp; import net.sourceforge.pmd.lang.java.types.TypeTestUtil; import net.sourceforge.pmd.util.CollectionUtil; @@ -225,4 +232,69 @@ public final class JavaRuleUtil { && hasExceptionList(node, InvalidObjectException.class) && hasParameters(node, ObjectInputStream.class); } + + + private static boolean areEqual(ASTExpression e1, ASTExpression e2) { + return tokenEquals(e1, e2); + } + + /** + * Whether one expression is the boolean negation of the other. Many + * forms are not yet supported. This method is symmetric so only needs + * to be called once. + */ + public static boolean areComplements(ASTExpression e1, ASTExpression e2) { + if (isBooleanNegation(e1)) { + return areEqual(unaryOperand(e1), e2); + } else if (isBooleanNegation(e2)) { + return areEqual(e1, unaryOperand(e2)); + } else if (e1 instanceof ASTInfixExpression && e2 instanceof ASTInfixExpression) { + ASTInfixExpression ifx1 = (ASTInfixExpression) e1; + ASTInfixExpression ifx2 = (ASTInfixExpression) e2; + if (ifx1.getOperator().getComplement() != ifx2.getOperator()) { + return false; + } + if (ifx1.getOperator().isEquality()) { + // NOT(a == b, a != b) + // NOT(a == b, b != a) + return areEqual(ifx1.getLeftOperand(), ifx2.getLeftOperand()) + && areEqual(ifx1.getRightOperand(), ifx2.getRightOperand()) + || areEqual(ifx2.getLeftOperand(), ifx1.getLeftOperand()) + && areEqual(ifx2.getRightOperand(), ifx1.getRightOperand()); + } + // todo we could continue with de Morgan and such + } + return false; + } + + public static boolean tokenEquals(JavaNode node, JavaNode that) { + Iterator thisIt = GenericToken.range(node.getFirstToken(), node.getLastToken()); + Iterator thatIt = GenericToken.range(that.getFirstToken(), that.getLastToken()); + while (thisIt.hasNext()) { + if (!thatIt.hasNext()) { + return false; + } + JavaccToken o1 = thisIt.next(); + JavaccToken o2 = thatIt.next(); + if (o1.kind != o2.kind + || !o2.getImage().equals(o2.getImage())) { + return false; + } + } + return !thatIt.hasNext(); + } + + public static boolean isBooleanLiteral(ASTExpression e) { + return e instanceof ASTBooleanLiteral; + } + + public static boolean isBooleanNegation(ASTExpression e) { + return e instanceof ASTUnaryExpression && ((ASTUnaryExpression) e).getOperator() == UnaryOp.NEGATION; + } + + private static @Nullable ASTExpression unaryOperand(ASTExpression e) { + return e instanceof ASTUnaryExpression ? ((ASTUnaryExpression) e).getOperand() + : null; + } + } From a44fead9362d6aee133c25840a450caa8f9132f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 31 Oct 2020 14:18:13 +0100 Subject: [PATCH 19/45] Simplify further --- .../design/SimplifyBooleanReturnsRule.java | 62 +++++++------------ .../lang/java/rule/internal/JavaRuleUtil.java | 5 ++ .../design/xml/SimplifyBooleanReturns.xml | 26 ++++++++ 3 files changed, 55 insertions(+), 38 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java index aab907ad7c..1d3e2c5850 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java @@ -5,8 +5,8 @@ package net.sourceforge.pmd.lang.java.rule.design; import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.areComplements; +import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.getNextSibling; import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.isBooleanLiteral; -import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.isBooleanNegation; import org.checkerframework.checker.nullness.qual.Nullable; @@ -14,7 +14,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTBlock; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; -import net.sourceforge.pmd.lang.java.ast.ASTStatement; +import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind; @@ -32,7 +32,7 @@ public class SimplifyBooleanReturnsRule extends AbstractJavaRulechainRule { || !isThenBranchOfSomeIf(node)) { return null; } - return visit(node.ancestors(ASTIfStatement.class).firstOrThrow(), data); + return checkIf(node.ancestors(ASTIfStatement.class).firstOrThrow(), data, expr); } // Only explore the then branch. If we explore the else, then we'll report twice. @@ -49,49 +49,35 @@ public class SimplifyBooleanReturnsRule extends AbstractJavaRulechainRule { return false; } - private @Nullable ASTReturnStatement asReturnStatement(ASTStatement node) { - if (node instanceof ASTReturnStatement) { - return (ASTReturnStatement) node; - } else if (node instanceof ASTBlock && ((ASTBlock) node).size() == 1) { - return asReturnStatement(((ASTBlock) node).get(0)); - } - return null; - } - - @Override - public Object visit(ASTIfStatement node, Object data) { + private Object checkIf(ASTIfStatement node, Object data, ASTExpression thenExpr) { // that's the case: if..then..return; return; - if (!node.hasElse()) { - if (isFollowedByReturn(node)) { - addViolation(data, node); - } + ASTExpression elseExpr = getElseExpr(node); + if (elseExpr == null) { return data; } - ASTReturnStatement returnStatement1 = asReturnStatement(node.getElseBranch()); - ASTReturnStatement returnStatement2 = asReturnStatement(node.getThenBranch()); - - if (returnStatement1 != null && returnStatement2 != null) { - ASTExpression e1 = returnStatement1.getExpr(); - ASTExpression e2 = returnStatement2.getExpr(); - - if (isBooleanLiteral(e1) && isBooleanLiteral(e2)) { - addViolation(data, node); - } else if (isBooleanNegation(e1) ^ isBooleanNegation(e2)) { - // We get the nodes under the '!' operator - // If they are the same => error - if (areComplements(e1, e2)) { - // if (foo) return !a; - // else return a; - addViolation(data, node); - } - } + if (isBooleanLiteral(thenExpr) || isBooleanLiteral(elseExpr)) { + addViolation(data, node); + } else if (areComplements(thenExpr, elseExpr)) { + // if (foo) return !a; + // else return a; + addViolation(data, node); } return data; } - private boolean isFollowedByReturn(ASTIfStatement ifNode) { - return ifNode.asStream().followingSiblings().first() instanceof ASTReturnStatement; + private @Nullable ASTExpression getReturnExpr(JavaNode node) { + if (node instanceof ASTReturnStatement) { + return ((ASTReturnStatement) node).getExpr(); + } else if (node instanceof ASTBlock && ((ASTBlock) node).size() == 1) { + return getReturnExpr(((ASTBlock) node).get(0)); + } + return null; + } + + private @Nullable ASTExpression getElseExpr(ASTIfStatement node) { + return node.hasElse() ? getReturnExpr(node.getElseBranch()) + : getReturnExpr(getNextSibling(node)); // may be followed immediately by return } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java index 20cf44a215..464c0b4cf4 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -20,6 +20,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.lang.ast.GenericToken; import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.ast.impl.GenericNode; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; @@ -297,4 +298,8 @@ public final class JavaRuleUtil { : null; } + // consider putting this on the node interface + public static > T getNextSibling(GenericNode ifNode) { + return (T) ifNode.asStream().followingSiblings().first(); + } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanReturns.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanReturns.xml index b919c38763..a46c5a7175 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanReturns.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanReturns.xml @@ -107,6 +107,32 @@ public class SimplifyBooleanReturns { return obj != null; return obj == null; } +} + ]]> + + + Check for negated expr 2 + 1 + + + + Check for boolean literal somewhere + 1 + From 9172665e15b0ef2b1c0b555820a53bb884fb0bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 1 Nov 2020 01:13:47 +0100 Subject: [PATCH 20/45] Remove now useless method --- .../lang/java/rule/design/SimplifyBooleanReturnsRule.java | 3 +-- .../pmd/lang/java/rule/internal/JavaRuleUtil.java | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java index 1d3e2c5850..6fa8e0d37a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.java.rule.design; import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.areComplements; -import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.getNextSibling; import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.isBooleanLiteral; import org.checkerframework.checker.nullness.qual.Nullable; @@ -77,7 +76,7 @@ public class SimplifyBooleanReturnsRule extends AbstractJavaRulechainRule { private @Nullable ASTExpression getElseExpr(ASTIfStatement node) { return node.hasElse() ? getReturnExpr(node.getElseBranch()) - : getReturnExpr(getNextSibling(node)); // may be followed immediately by return + : getReturnExpr(node.getNextSibling()); // may be followed immediately by return } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java index 464c0b4cf4..20cf44a215 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -20,7 +20,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.lang.ast.GenericToken; import net.sourceforge.pmd.lang.ast.NodeStream; -import net.sourceforge.pmd.lang.ast.impl.GenericNode; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; @@ -298,8 +297,4 @@ public final class JavaRuleUtil { : null; } - // consider putting this on the node interface - public static > T getNextSibling(GenericNode ifNode) { - return (T) ifNode.asStream().followingSiblings().first(); - } } From 9c50f0fc6dd1674fbd4ae4e23b8e77b6da0553b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 1 Nov 2020 01:50:11 +0100 Subject: [PATCH 21/45] Update UnnecessaryLocalBeforeReturn --- .travis/all-java.xml | 2 +- .../UnnecessaryLocalBeforeReturnRule.java | 172 +++--------------- .../UnnecessaryLocalBeforeReturnTest.java | 1 - .../xml/UnnecessaryLocalBeforeReturn.xml | 4 +- 4 files changed, 28 insertions(+), 151 deletions(-) diff --git a/.travis/all-java.xml b/.travis/all-java.xml index 1db8013567..9cdecee1df 100644 --- a/.travis/all-java.xml +++ b/.travis/all-java.xml @@ -110,7 +110,7 @@ - + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnRule.java index e9f43f3400..c7e39160ab 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnRule.java @@ -6,173 +6,51 @@ package net.sourceforge.pmd.lang.java.rule.codestyle; import static net.sourceforge.pmd.properties.PropertyFactory.booleanProperty; -import java.util.List; -import java.util.Map; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; -import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; -import net.sourceforge.pmd.lang.java.ast.ASTExpression; -import net.sourceforge.pmd.lang.java.ast.ASTMemberSelector; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; -import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; +import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; -import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; -import net.sourceforge.pmd.lang.java.ast.AccessNode; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; -import net.sourceforge.pmd.lang.symboltable.NameOccurrence; -import net.sourceforge.pmd.lang.symboltable.Scope; +import net.sourceforge.pmd.lang.java.ast.ASTVariableAccess; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; +import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol; import net.sourceforge.pmd.properties.PropertyDescriptor; -public class UnnecessaryLocalBeforeReturnRule extends AbstractJavaRule { +public class UnnecessaryLocalBeforeReturnRule extends AbstractJavaRulechainRule { private static final PropertyDescriptor STATEMENT_ORDER_MATTERS = booleanProperty("statementOrderMatters").defaultValue(true).desc("If set to false this rule no longer requires the variable declaration and return statement to be on consecutive lines. Any variable that is used solely in a return statement will be reported.").build(); public UnnecessaryLocalBeforeReturnRule() { + super(ASTReturnStatement.class); definePropertyDescriptor(STATEMENT_ORDER_MATTERS); } - @Override - public Object visit(ASTMethodDeclaration meth, Object data) { - // skip void/abstract/native method - if (meth.isVoid() || meth.isAbstract() || meth.isNative()) { - return data; - } - return super.visit(meth, data); - } @Override - public Object visit(ASTReturnStatement rtn, Object data) { - // skip returns of literals - ASTName name = rtn.getFirstDescendantOfType(ASTName.class); - if (name == null) { - return data; + public Object visit(ASTReturnStatement returnStmt, Object data) { + if (!(returnStmt.getExpr() instanceof ASTVariableAccess)) { + return null; + } + ASTVariableAccess varExpr = (ASTVariableAccess) returnStmt.getExpr(); + JVariableSymbol sym = varExpr.getReferencedSym(); + if (sym == null) { + return null; } - // skip 'complicated' expressions - if (rtn.findDescendantsOfType(ASTExpression.class).size() > 1 - || rtn.getFirstDescendantOfType(ASTMemberSelector.class) != null - || rtn.findDescendantsOfType(ASTPrimaryExpression.class).size() > 1 || isMethodCall(rtn)) { - return data; + ASTVariableDeclaratorId varDecl = sym.tryGetNode(); + if (varDecl == null || !varDecl.isLocalVariable() || varDecl.getDeclaredAnnotations().nonEmpty()) { + return null; } - Map> vars = name.getScope() - .getDeclarations(VariableNameDeclaration.class); - for (Map.Entry> entry : vars.entrySet()) { - VariableNameDeclaration variableDeclaration = entry.getKey(); - if (variableDeclaration.getDeclaratorId().isFormalParameter()) { - continue; - } - - List usages = entry.getValue(); - - if (usages.size() == 1) { // If there is more than 1 usage, then it's not only returned - NameOccurrence occ = usages.get(0); - - if (occ.getLocation().equals(name) && isNotAnnotated(variableDeclaration)) { - String var = name.getImage(); - if (var.indexOf('.') != -1) { - var = var.substring(0, var.indexOf('.')); - } - - // Is the variable initialized with another member that is later used? - if (!isInitDataModifiedAfterInit(variableDeclaration, rtn) - && !statementsBeforeReturn(variableDeclaration, rtn)) { - addViolation(data, rtn, var); - } - } - } + if (varDecl.getUsages().size() != 1) { + return null; } - return data; - } + // then this is the only usage - private boolean statementsBeforeReturn(VariableNameDeclaration variableDeclaration, ASTReturnStatement returnStatement) { - if (!getProperty(STATEMENT_ORDER_MATTERS)) { - return false; + if (!getProperty(STATEMENT_ORDER_MATTERS) + || varDecl.ancestors(ASTLocalVariableDeclaration.class).firstOrThrow().getNextSibling() == returnStmt) { + addViolation(data, varDecl, varDecl.getName()); } - - ASTBlockStatement declarationStatement = variableDeclaration.getAccessNodeParent().getFirstParentOfType(ASTBlockStatement.class); - ASTBlockStatement returnBlockStatement = returnStatement.getFirstParentOfType(ASTBlockStatement.class); - - // double check: we should now be at the same level in the AST - both block statements are children of the same parent - if (declarationStatement.getParent() == returnBlockStatement.getParent()) { - return returnBlockStatement.getIndexInParent() - declarationStatement.getIndexInParent() > 1; - } - return false; - } - - // TODO : should node define isAfter / isBefore helper methods for Nodes? - private static boolean isAfter(Node n1, Node n2) { - return n1.getBeginLine() > n2.getBeginLine() - || n1.getBeginLine() == n2.getBeginLine() && n1.getBeginColumn() >= n2.getEndColumn(); - } - - private boolean isInitDataModifiedAfterInit(final VariableNameDeclaration variableDeclaration, - final ASTReturnStatement rtn) { - final ASTVariableInitializer initializer = variableDeclaration.getAccessNodeParent() - .getFirstDescendantOfType(ASTVariableInitializer.class); - - if (initializer != null) { - // Get the block statements for each, so we can compare apples to apples - final ASTBlockStatement initializerStmt = variableDeclaration.getAccessNodeParent() - .getFirstParentOfType(ASTBlockStatement.class); - final ASTBlockStatement rtnStmt = rtn.getFirstParentOfType(ASTBlockStatement.class); - - final List referencedNames = initializer.findDescendantsOfType(ASTName.class); - for (final ASTName refName : referencedNames) { - // TODO : Shouldn't the scope allow us to search for a var name occurrences directly, moving up through parent scopes? - Scope scope = refName.getScope(); - do { - final Map> declarations = scope - .getDeclarations(VariableNameDeclaration.class); - for (final Map.Entry> entry : declarations - .entrySet()) { - if (entry.getKey().getName().equals(refName.getImage())) { - // Variable found! Check usage locations - for (final NameOccurrence occ : entry.getValue()) { - final ASTBlockStatement location = occ.getLocation().getFirstParentOfType(ASTBlockStatement.class); - - // Is it used after initializing our "unnecessary" local but before the return statement? - if (location != null && isAfter(location, initializerStmt) && isAfter(rtnStmt, location)) { - return true; - } - } - - return false; - } - } - scope = scope.getParent(); - } while (scope != null); - } - } - - return false; - } - - private boolean isNotAnnotated(VariableNameDeclaration variableDeclaration) { - AccessNode accessNodeParent = variableDeclaration.getAccessNodeParent(); - return !accessNodeParent.hasDescendantOfType(ASTAnnotation.class); - } - - /** - * Determine if the given return statement has any embedded method calls. - * - * @param rtn - * return statement to analyze - * @return true if any method calls are made within the given return - */ - private boolean isMethodCall(ASTReturnStatement rtn) { - List suffix = rtn.findDescendantsOfType(ASTPrimarySuffix.class); - for (ASTPrimarySuffix element : suffix) { - if (element.isArguments()) { - return true; - } - } - return false; + return null; } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnTest.java index b127254902..73dc6cdac3 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.codestyle; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class UnnecessaryLocalBeforeReturnTest extends PmdRuleTst { // no additional unit tests } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryLocalBeforeReturn.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryLocalBeforeReturn.xml index c1e5f21a5f..1afde48b6d 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryLocalBeforeReturn.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryLocalBeforeReturn.xml @@ -208,8 +208,8 @@ public class UnnecessaryLocalBeforeReturnFP { #310 UnnecessaryLocalBeforeReturn statement order does not matter false - 1 - 5 + 2 + 3,10 Date: Wed, 28 Oct 2020 11:25:14 +0100 Subject: [PATCH 22/45] Update ConsecutiveAppendsShouldReuseRule --- .../ConsecutiveAppendsShouldReuseRule.java | 177 ++++++++---------- .../ConsecutiveAppendsShouldReuseTest.java | 1 - 2 files changed, 76 insertions(+), 102 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseRule.java index 26172c63a8..7cf67c997d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseRule.java @@ -4,138 +4,113 @@ package net.sourceforge.pmd.lang.java.rule.performance; -import java.util.List; -import java.util.Map; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; +import net.sourceforge.pmd.lang.java.ast.ASTAssignmentExpression; import net.sourceforge.pmd.lang.java.ast.ASTExpression; +import net.sourceforge.pmd.lang.java.ast.ASTExpressionStatement; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; -import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; -import net.sourceforge.pmd.lang.java.ast.ASTStatement; -import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; +import net.sourceforge.pmd.lang.java.ast.ASTMethodCall; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; -import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; -import net.sourceforge.pmd.lang.symboltable.NameOccurrence; +import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol; +import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol; +import net.sourceforge.pmd.lang.java.types.OverloadSelectionResult; +import net.sourceforge.pmd.lang.java.types.TypeTestUtil; +import net.sourceforge.pmd.lang.rule.RuleTargetSelector; public class ConsecutiveAppendsShouldReuseRule extends AbstractJavaRule { @Override - public Object visit(ASTBlockStatement node, Object data) { - String variable = getVariableAppended(node); - if (variable != null) { - ASTBlockStatement nextSibling = getNextBlockStatementSibling(node); - if (nextSibling != null) { - String nextVariable = getVariableAppended(nextSibling); + protected @NonNull RuleTargetSelector buildTargetSelector() { + return RuleTargetSelector.forTypes(ASTExpressionStatement.class, ASTLocalVariableDeclaration.class); + } + + @Override + public Object visit(ASTExpressionStatement node, Object data) { + Node nextSibling = node.asStream().followingSiblings().first(); + if (nextSibling instanceof ASTExpressionStatement) { + @Nullable JVariableSymbol variable = getVariableAppended(node); + if (variable != null) { + @Nullable JVariableSymbol nextVariable = getVariableAppended((ASTExpressionStatement) nextSibling); if (nextVariable != null && nextVariable.equals(variable)) { addViolation(data, node); } } } - return super.visit(node, data); + return data; } - private ASTBlockStatement getNextBlockStatementSibling(Node node) { - Node parent = node.getParent(); - int childIndex = -1; - for (int i = 0; i < parent.getNumChildren(); i++) { - if (parent.getChild(i) == node) { - childIndex = i; - break; + @Override + public Object visit(ASTLocalVariableDeclaration node, Object data) { + Node nextSibling = node.asStream().followingSiblings().first(); + if (nextSibling instanceof ASTExpressionStatement) { + @Nullable JVariableSymbol nextVariable = getVariableAppended((ASTExpressionStatement) nextSibling); + if (nextVariable != null) { + ASTVariableDeclaratorId varDecl = nextVariable.tryGetNode(); + if (varDecl != null && node.getVarIds().any(it -> it == varDecl) + && isStringBuilderAppend(varDecl.getInitializer())) { + addViolation(data, node); + } } } - if (childIndex + 1 < parent.getNumChildren()) { - Node nextSibling = parent.getChild(childIndex + 1); - if (nextSibling instanceof ASTBlockStatement) { - return (ASTBlockStatement) nextSibling; - } + return data; + } + + + private @Nullable JVariableSymbol getVariableAppended(ASTExpressionStatement node) { + ASTExpression expr = node.getExpr(); + if (expr instanceof ASTMethodCall) { + return getAsVarAccess(getAppendChainQualifier(expr)); + } else if (expr instanceof ASTAssignmentExpression) { + ASTExpression rhs = ((ASTAssignmentExpression) expr).getRightOperand(); + return getAppendChainQualifier(rhs) != null ? getAssignmentLhsAsVar(expr) : null; } return null; } - private String getVariableAppended(ASTBlockStatement node) { - if (isFirstChild(node, ASTStatement.class)) { - ASTStatement statement = (ASTStatement) node.getChild(0); - if (isFirstChild(statement, ASTStatementExpression.class)) { - ASTStatementExpression stmtExp = (ASTStatementExpression) statement.getChild(0); - if (stmtExp.getNumChildren() == 1) { - ASTPrimaryPrefix primaryPrefix = stmtExp.getFirstDescendantOfType(ASTPrimaryPrefix.class); - if (primaryPrefix != null) { - ASTName name = primaryPrefix.getFirstChildOfType(ASTName.class); - if (name != null) { - String image = name.getImage(); - if (image.endsWith(".append")) { - String variable = image.substring(0, image.indexOf('.')); - if (isAStringBuilderBuffer(primaryPrefix, variable)) { - return variable; - } - } - } - } - } else { - final ASTExpression exp = stmtExp.getFirstDescendantOfType(ASTExpression.class); - if (isFirstChild(exp, ASTPrimaryExpression.class)) { - final ASTPrimarySuffix primarySuffix = ((ASTPrimaryExpression) exp.getChild(0)) - .getFirstDescendantOfType(ASTPrimarySuffix.class); - if (primarySuffix != null) { - final String name = primarySuffix.getImage(); - if ("append".equals(name)) { - final ASTPrimaryExpression pExp = stmtExp - .getFirstDescendantOfType(ASTPrimaryExpression.class); - if (pExp != null) { - final ASTName astName = stmtExp.getFirstDescendantOfType(ASTName.class); - if (astName != null) { - final String variable = astName.getImage(); - if (isAStringBuilderBuffer(primarySuffix, variable)) { - return variable; - } - } - } - } - } - } - } - } - } else if (isFirstChild(node, ASTLocalVariableDeclaration.class)) { - ASTLocalVariableDeclaration lvd = (ASTLocalVariableDeclaration) node.getChild(0); - - ASTVariableDeclaratorId vdId = lvd.getFirstDescendantOfType(ASTVariableDeclaratorId.class); - ASTExpression exp = lvd.getFirstDescendantOfType(ASTExpression.class); - - if (exp != null) { - ASTPrimarySuffix primarySuffix = exp.getFirstDescendantOfType(ASTPrimarySuffix.class); - if (primarySuffix != null) { - final String name = primarySuffix.getImage(); - if ("append".equals(name)) { - String variable = vdId.getImage(); - if (isAStringBuilderBuffer(primarySuffix, variable)) { - return variable; - } - } - } - } + private @Nullable ASTExpression getAppendChainQualifier(final ASTExpression base) { + ASTExpression expr = base; + while (expr instanceof ASTMethodCall && isStringBuilderAppend(expr)) { + expr = ((ASTMethodCall) expr).getQualifier(); } + return base == expr ? null : expr; + } + private @Nullable JVariableSymbol getAssignmentLhsAsVar(@Nullable ASTExpression expr) { + if (expr instanceof ASTAssignmentExpression) { + return getAsVarAccess(((ASTAssignmentExpression) expr).getLeftOperand()); + } return null; } - private boolean isAStringBuilderBuffer(JavaNode node, String name) { - Map> declarations = node.getScope() - .getDeclarations(VariableNameDeclaration.class); - for (VariableNameDeclaration decl : declarations.keySet()) { - if (decl.getName().equals(name) && ConsecutiveLiteralAppendsRule.isStringBuilderOrBuffer(decl.getDeclaratorId())) { - return true; - } + private @Nullable JVariableSymbol getAsVarAccess(@Nullable ASTExpression expr) { + if (expr instanceof ASTNamedReferenceExpr) { + return ((ASTNamedReferenceExpr) expr).getReferencedSym(); + } + return null; + } + + private boolean isStringBuilderAppend(@Nullable ASTExpression e) { + if (e instanceof ASTMethodCall) { + ASTMethodCall call = (ASTMethodCall) e; + return call.getMethodName().equals("append") + && isStringBuilderAppend(call.getOverloadSelectionInfo()); } return false; } - private boolean isFirstChild(Node node, Class clazz) { - return node.getNumChildren() == 1 && clazz.isAssignableFrom(node.getChild(0).getClass()); + private boolean isStringBuilderAppend(OverloadSelectionResult result) { + if (result.isFailed()) { + return false; + } + + JExecutableSymbol symbol = result.getMethodType().getSymbol(); + return TypeTestUtil.isExactlyA(StringBuffer.class, symbol.getEnclosingClass()) + || TypeTestUtil.isExactlyA(StringBuilder.class, symbol.getEnclosingClass()); } + } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseTest.java index 7a4f7f4181..e7b68474ad 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.performance; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class ConsecutiveAppendsShouldReuseTest extends PmdRuleTst { // no additional unit tests } From d5c3f8d9084627ed4cabbc928f87c6c5912de510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 1 Nov 2020 21:29:19 +0100 Subject: [PATCH 23/45] Update alljava.xml --- .travis/all-java.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/all-java.xml b/.travis/all-java.xml index 17109e2c7b..f0fba39c3e 100644 --- a/.travis/all-java.xml +++ b/.travis/all-java.xml @@ -295,7 +295,7 @@ - + From 5b555ac1d92a3f88dde09788a22aa5ed84cdef85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 1 Nov 2020 23:25:03 +0100 Subject: [PATCH 24/45] Doc --- .../lang/java/rule/internal/JavaRuleUtil.java | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java index aa252e4872..7d21a62227 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -285,16 +285,9 @@ public final class JavaRuleUtil { return toplevel.getIndexInParent() == 0 && toplevel.getParent() instanceof ASTIfStatement; } - public static @Nullable ASTIfStatement getIfStmtIfExprInCondition(ASTExpression expr) { - ASTExpression toplevel = getTopLevelExpr(expr); - if (toplevel.getIndexInParent() == 0 && toplevel.getParent() instanceof ASTIfStatement) { - return (ASTIfStatement) toplevel.getParent(); - } - return null; - } - /** - * Will cut through argument lists, except those of enum constants & explicit invocation nodes. + * Will cut through argument lists, except those of enum constants + * and explicit invocation nodes. */ private static @NonNull ASTExpression getTopLevelExpr(ASTExpression expr) { return (ASTExpression) expr.ancestorsOrSelf() @@ -303,14 +296,14 @@ public final class JavaRuleUtil { .last(); } + /** + * Returns the variable IDS corresponding to variables declared in + * the init clause of the loop. + */ public static NodeStream getLoopVariables(ASTForStatement loop) { - @Nullable ASTStatement init = loop.getInit(); - - if (init instanceof ASTLocalVariableDeclaration) { - return ((ASTLocalVariableDeclaration) init).getVarIds(); - } - - return NodeStream.empty(); + return NodeStream.of(loop.getInit()) + .filterIs(ASTLocalVariableDeclaration.class) + .flatMap(ASTLocalVariableDeclaration::getVarIds); } // TODO at least UnusedPrivateMethod has some serialization-related logic. @@ -349,11 +342,6 @@ public final class JavaRuleUtil { && hasParameters(node, ObjectInputStream.class); } - - private static boolean areEqual(ASTExpression e1, ASTExpression e2) { - return tokenEquals(e1, e2); - } - /** * Whether one expression is the boolean negation of the other. Many * forms are not yet supported. This method is symmetric so only needs @@ -383,6 +371,16 @@ public final class JavaRuleUtil { return false; } + private static boolean areEqual(ASTExpression e1, ASTExpression e2) { + return tokenEquals(e1, e2); + } + + /** + * Returns true if both nodes have exactly the same tokens. + * + * @param node First node + * @param that Other node + */ public static boolean tokenEquals(JavaNode node, JavaNode that) { Iterator thisIt = GenericToken.range(node.getFirstToken(), node.getLastToken()); Iterator thatIt = GenericToken.range(that.getFirstToken(), that.getLastToken()); @@ -408,7 +406,11 @@ public final class JavaRuleUtil { return e instanceof ASTUnaryExpression && ((ASTUnaryExpression) e).getOperator() == UnaryOp.NEGATION; } - private static @Nullable ASTExpression unaryOperand(ASTExpression e) { + /** + * If the argument is a unary expression, returns its operand, otherwise + * returns null. + */ + public static @Nullable ASTExpression unaryOperand(ASTExpression e) { return e instanceof ASTUnaryExpression ? ((ASTUnaryExpression) e).getOperand() : null; } From 348133024d39b33688fddeb3b2f1cbc9115b75a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 1 Nov 2020 23:41:23 +0100 Subject: [PATCH 25/45] Update IdenticalCatchBranches --- .travis/all-java.xml | 2 +- .../codestyle/IdenticalCatchBranchesRule.java | 86 ++----------------- .../lang/java/rule/internal/JavaRuleUtil.java | 45 +++++++++- .../codestyle/IdenticalCatchBranchesTest.java | 1 - 4 files changed, 48 insertions(+), 86 deletions(-) diff --git a/.travis/all-java.xml b/.travis/all-java.xml index f0fba39c3e..e5579298df 100644 --- a/.travis/all-java.xml +++ b/.travis/all-java.xml @@ -86,7 +86,7 @@ - + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesRule.java index a86a433dd0..413f9b2915 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesRule.java @@ -7,16 +7,12 @@ package net.sourceforge.pmd.lang.java.rule.codestyle; import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.Set; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTCatchClause; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; -import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; import net.sourceforge.pmd.lang.java.ast.ASTTryStatement; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; /** @@ -29,7 +25,10 @@ public class IdenticalCatchBranchesRule extends AbstractJavaRule { private boolean areEquivalent(ASTCatchClause st1, ASTCatchClause st2) { - return hasSameSubTree(st1.getBody(), st2.getBody(), st1.getParameter().getName(), st2.getParameter().getName()); + String e1Name = st1.getParameter().getName(); + String e2Name = st2.getParameter().getName(); + + return JavaRuleUtil.tokenEquals(st1.getBody(), st2.getBody(), name -> name.equals(e1Name) ? e2Name : name); } @@ -96,79 +95,4 @@ public class IdenticalCatchBranchesRule extends AbstractJavaRule { } - /** - * Checks whether two nodes define the same subtree, - * up to the renaming of one local variable. - * - * @param node1 the first node to check - * @param node2 the second node to check - * @param exceptionName1 the first exception variable name - * @param exceptionName2 the second exception variable name - */ - private boolean hasSameSubTree(Node node1, Node node2, String exceptionName1, String exceptionName2) { - if (node1 == null && node2 == null) { - return true; - } else if (node1 == null || node2 == null) { - return false; - } - - //numbers of child node are different - if (node1.getNumChildren() != node2.getNumChildren()) { - return false; - } - - for (int num = 0; num < node1.getNumChildren(); num++) { - - if (!basicEquivalence(node1.getChild(num), node2.getChild(num), exceptionName1, exceptionName2)) { - return false; - } - - //subtree of nodes are different - if (!hasSameSubTree(node1.getChild(num), node2.getChild(num), - exceptionName1, exceptionName2)) { - return false; - } - } - return true; - } - - - // no subtree comparison - private boolean basicEquivalence(Node node1, Node node2, String varName1, String varName2) { - // Nodes must have the same type - if (node1.getClass() != node2.getClass()) { - return false; - } - - String image1 = node1.getImage(); - String image2 = node2.getImage(); - - // image of nodes must be the same - return Objects.equals(image1, image2) - // or must be references to the variable we allow to interchange - || Objects.equals(image1, varName1) && Objects.equals(image2, varName2) - // which means we must filter out method references. - && isNoMethodName(node1) && isNoMethodName(node2); - - } - - - private boolean isNoMethodName(Node name) { - - if (name instanceof ASTName - && (name.getParent() instanceof ASTPrimaryPrefix || name.getParent() instanceof ASTPrimarySuffix)) { - - Node prefixOrSuffix = name.getParent(); - - if (prefixOrSuffix.getParent().getNumChildren() > 1 + prefixOrSuffix.getIndexInParent()) { - // there's one next sibling - - Node next = prefixOrSuffix.getParent().getChild(prefixOrSuffix.getIndexInParent() + 1); - if (next instanceof ASTPrimarySuffix) { - return !((ASTPrimarySuffix) next).isArguments(); - } - } - } - return true; - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java index 7d21a62227..3d87496d5a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -13,6 +13,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.NonNull; @@ -52,6 +53,7 @@ import net.sourceforge.pmd.lang.java.ast.AccessNode.Visibility; import net.sourceforge.pmd.lang.java.ast.BinaryOp; import net.sourceforge.pmd.lang.java.ast.JModifier; import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.ast.JavaTokenKinds; import net.sourceforge.pmd.lang.java.ast.TypeNode; import net.sourceforge.pmd.lang.java.ast.UnaryOp; import net.sourceforge.pmd.lang.java.types.TypeTestUtil; @@ -382,18 +384,55 @@ public final class JavaRuleUtil { * @param that Other node */ public static boolean tokenEquals(JavaNode node, JavaNode that) { + return tokenEquals(node, that, null); + } + + /** + * Returns true if both nodes have the same tokens, modulo some renaming + * function. The renaming function maps unqualified variables and type + * identifiers of the first node to the other. This should be used + * in nodes living in the same lexical scope, so that unqualified + * names mean the same thing. + * + * @param node First node + * @param other Other node + * @param varRenamer A renaming function. If null, no renaming is applied. + * Must not return null, if no renaming occurs, returns its argument. + */ + public static boolean tokenEquals(@NonNull JavaNode node, + @NonNull JavaNode other, + @Nullable Function varRenamer) { + // Since type and variable names obscure one another, + // it's ok to use a single renaming function. + Iterator thisIt = GenericToken.range(node.getFirstToken(), node.getLastToken()); - Iterator thatIt = GenericToken.range(that.getFirstToken(), that.getLastToken()); + Iterator thatIt = GenericToken.range(other.getFirstToken(), other.getLastToken()); + int lastKind = 0; while (thisIt.hasNext()) { if (!thatIt.hasNext()) { return false; } JavaccToken o1 = thisIt.next(); JavaccToken o2 = thatIt.next(); - if (o1.kind != o2.kind - || !o2.getImage().equals(o2.getImage())) { + if (o1.kind != o2.kind) { return false; } + + String mappedImage = o1.getImage(); + if (varRenamer != null + && o1.kind == JavaTokenKinds.IDENTIFIER + && lastKind != JavaTokenKinds.DOT + && lastKind != JavaTokenKinds.METHOD_REF + //method name + && o1.getNext() != null && o1.getNext().kind != JavaTokenKinds.LPAREN) { + mappedImage = varRenamer.apply(mappedImage); + } + + if (!o2.getImage().equals(mappedImage)) { + return false; + } + + lastKind = o1.kind; } return !thatIt.hasNext(); } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesTest.java index f9f9bdcd15..da7244d121 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.codestyle; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class IdenticalCatchBranchesTest extends PmdRuleTst { // no additional unit tests } From 9ac49a346cdc0c43d95859d90a8780f993c062e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 2 Nov 2020 09:57:00 +0100 Subject: [PATCH 26/45] Update AssignmentInOperand --- .travis/all-java.xml | 2 +- .../errorprone/AssignmentInOperandRule.java | 65 +++++++++++-------- .../lang/java/rule/internal/JavaRuleUtil.java | 2 +- .../errorprone/AssignmentInOperandTest.java | 1 - 4 files changed, 40 insertions(+), 30 deletions(-) diff --git a/.travis/all-java.xml b/.travis/all-java.xml index 0410d09585..4809e73dac 100644 --- a/.travis/all-java.xml +++ b/.travis/all-java.xml @@ -173,7 +173,7 @@ - + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandRule.java index a36cf640d5..459eb99466 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandRule.java @@ -6,32 +6,34 @@ package net.sourceforge.pmd.lang.java.rule.errorprone; import static net.sourceforge.pmd.properties.PropertyFactory.booleanProperty; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.java.ast.ASTAssignmentExpression; import net.sourceforge.pmd.lang.java.ast.ASTExpression; +import net.sourceforge.pmd.lang.java.ast.ASTExpressionStatement; import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; +import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; import net.sourceforge.pmd.properties.PropertyDescriptor; import net.sourceforge.pmd.properties.PropertySource; /** - * * */ -public class AssignmentInOperandRule extends AbstractJavaRule { +public class AssignmentInOperandRule extends AbstractJavaRulechainRule { private static final PropertyDescriptor ALLOW_IF_DESCRIPTOR = - booleanProperty("allowIf") - .desc("Allow assignment within the conditional expression of an if statement") - .defaultValue(false).build(); + booleanProperty("allowIf") + .desc("Allow assignment within the conditional expression of an if statement") + .defaultValue(false).build(); private static final PropertyDescriptor ALLOW_FOR_DESCRIPTOR = - booleanProperty("allowFor") - .desc("Allow assignment within the conditional expression of a for statement") - .defaultValue(false).build(); + booleanProperty("allowFor") + .desc("Allow assignment within the conditional expression of a for statement") + .defaultValue(false).build(); private static final PropertyDescriptor ALLOW_WHILE_DESCRIPTOR = booleanProperty("allowWhile") @@ -45,6 +47,7 @@ public class AssignmentInOperandRule extends AbstractJavaRule { public AssignmentInOperandRule() { + super(ASTAssignmentExpression.class, ASTUnaryExpression.class); definePropertyDescriptor(ALLOW_IF_DESCRIPTOR); definePropertyDescriptor(ALLOW_FOR_DESCRIPTOR); definePropertyDescriptor(ALLOW_WHILE_DESCRIPTOR); @@ -52,24 +55,32 @@ public class AssignmentInOperandRule extends AbstractJavaRule { } @Override - public Object visit(ASTExpression node, Object data) { - Node parent = node.getParent(); - if ((parent instanceof ASTIfStatement && !getProperty(ALLOW_IF_DESCRIPTOR) - || parent instanceof ASTWhileStatement && !getProperty(ALLOW_WHILE_DESCRIPTOR) - || parent instanceof ASTForStatement && parent.getChild(1) == node - && !getProperty(ALLOW_FOR_DESCRIPTOR)) - && (node.hasDescendantOfType(ASTAssignmentOperator.class) - || !getProperty(ALLOW_INCREMENT_DECREMENT_DESCRIPTOR) && hasIncrement(node))) { - - addViolation(data, node); - return data; - } - return super.visit(node, data); + public Object visit(ASTAssignmentExpression node, Object data) { + checkAssignment(node, (RuleContext) data); + return null; } - private boolean hasIncrement(ASTExpression node) { - // todo use node streams - return node.findDescendantsOfType(ASTUnaryExpression.class).stream().anyMatch(it -> !it.getOperator().isPure()); + @Override + public Object visit(ASTUnaryExpression node, Object data) { + if (!getProperty(ALLOW_INCREMENT_DECREMENT_DESCRIPTOR) && !node.getOperator().isPure()) { + checkAssignment(node, (RuleContext) data); + } + return null; + } + + private void checkAssignment(ASTExpression impureExpr, RuleContext ctx) { + ASTExpression toplevel = JavaRuleUtil.getTopLevelExpr(impureExpr); + JavaNode parent = toplevel.getParent(); + if (parent instanceof ASTExpressionStatement) { + // that's ok + return; + } + if (parent instanceof ASTIfStatement && !getProperty(ALLOW_IF_DESCRIPTOR) + || parent instanceof ASTWhileStatement && !getProperty(ALLOW_WHILE_DESCRIPTOR) + || parent instanceof ASTForStatement && ((ASTForStatement) parent).getCondition() == toplevel && !getProperty(ALLOW_FOR_DESCRIPTOR)) { + + addViolation(ctx, impureExpr); + } } public boolean allowsAllAssignments() { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java index 3d87496d5a..c1ff4e25d3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -291,7 +291,7 @@ public final class JavaRuleUtil { * Will cut through argument lists, except those of enum constants * and explicit invocation nodes. */ - private static @NonNull ASTExpression getTopLevelExpr(ASTExpression expr) { + public static @NonNull ASTExpression getTopLevelExpr(ASTExpression expr) { return (ASTExpression) expr.ancestorsOrSelf() .takeWhile(it -> it instanceof ASTExpression || it instanceof ASTArgumentList && it.getParent() instanceof ASTExpression) diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandTest.java index e083a2d0fe..3432ef4411 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.errorprone; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class AssignmentInOperandTest extends PmdRuleTst { // no additional unit tests } From 5314c9d46925454d6cd1067a7ff1c53a88ebadd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 10 Nov 2020 12:54:25 +0100 Subject: [PATCH 27/45] Cleanup after merge --- .../main/java/net/sourceforge/pmd/lang/ast/NodeStream.java | 2 +- .../java/rule/bestpractices/UnusedPrivateMethodRule.java | 6 +++--- .../pmd/lang/java/rule/internal/JavaRuleUtil.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/NodeStream.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/NodeStream.java index a40bff02ed..68161495c4 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/NodeStream.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/NodeStream.java @@ -129,7 +129,7 @@ import net.sourceforge.pmd.lang.ast.internal.StreamImpl; * * @since 7.0.0 */ -public interface NodeStream extends Iterable<@NonNull T> { +public interface NodeStream<@NonNull T extends Node> extends Iterable<@NonNull T> { /** * Returns a node stream consisting of the results of replacing each diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java index efad927fbf..de67db1867 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java @@ -4,11 +4,11 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; -import java.util.Arrays; +import static net.sourceforge.pmd.util.CollectionUtil.setOf; + import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -33,7 +33,7 @@ import net.sourceforge.pmd.util.CollectionUtil; public class UnusedPrivateMethodRule extends AbstractIgnoredAnnotationRule { private static final Set SERIALIZATION_METHODS = - new HashSet<>(Arrays.asList("readObject", "writeObject", "readResolve", "writeReplace")); + setOf("readObject", "writeObject", "readResolve", "writeReplace"); @Override protected Collection defaultSuppressionAnnotations() { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java index c1ff4e25d3..64143db2df 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -360,7 +360,7 @@ public final class JavaRuleUtil { if (ifx1.getOperator().getComplement() != ifx2.getOperator()) { return false; } - if (ifx1.getOperator().isEquality()) { + if (ifx1.getOperator().hasSamePrecedenceAs(BinaryOp.EQ)) { // NOT(a == b, a != b) // NOT(a == b, b != a) return areEqual(ifx1.getLeftOperand(), ifx2.getLeftOperand()) From b918d9ae937effdafa31f1d783152f9c2153e231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 10 Nov 2020 12:58:08 +0100 Subject: [PATCH 28/45] Check fixes #2130 --- .../bestpractices/xml/UnusedLocalVariable.xml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedLocalVariable.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedLocalVariable.xml index 75afe6e196..3c0afd4c3b 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedLocalVariable.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedLocalVariable.xml @@ -381,4 +381,25 @@ public class UnusedLocalVariable { } ]]> + + #2130 [java] UnusedLocalVariable: false-negative with array + 1 + [] constructors = String.class.getConstructors(); + } + } + ]]> + From 6b9fc07e68d619b3f1d3f3d1780c088cb51d4b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 10 Nov 2020 13:00:20 +0100 Subject: [PATCH 29/45] Check fixes #2890 --- .../bestpractices/xml/UnusedPrivateMethod.xml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml index 1f82a91999..fc6bf2840c 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml @@ -1647,6 +1647,45 @@ public class UnusedPrivateMethodFP { private void privateBooleanMethod(String s, boolean isTrue) { System.out.println(s); } +} + ]]> + + + UnusedPrivateMethod false positive #2890 + 0 + optionIndexes, MenuEntry[] entries, int index1, int index2) { + MenuEntry entry1 = entries[index1], + entry2 = entries[index2]; + + entries[index1] = entry2; + entries[index2] = entry1; + + client.setMenuEntries(entries); + + // Update optionIndexes + String option1 = Text.removeTags(entry1.getOption()).toLowerCase(), + option2 = Text.removeTags(entry2.getOption()).toLowerCase(); + + List list1 = optionIndexes.get(option1), + list2 = optionIndexes.get(option2); + + // call remove(Object) instead of remove(int) + list1.remove((Integer) index1); + list2.remove((Integer) index2); + + sortedInsert(list1, index2); + sortedInsert(list2, index1); + } + + private static > void sortedInsert(List list, T value) // NOPMD: UnusedPrivateMethod: false positive + { + int idx = Collections.binarySearch(list, value); + list.add(idx < 0 ? -idx - 1 : idx, value); + } + } ]]> From 6246cc0f705b32816558833980907c34ec395643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 10 Nov 2020 13:06:30 +0100 Subject: [PATCH 30/45] Check fixes #1189 --- .../bestpractices/xml/UnusedPrivateMethod.xml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml index fc6bf2840c..bff35b4889 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml @@ -1689,4 +1689,29 @@ public class UnusedPrivateMethodFP { } ]]> + + [java] UnusedPrivateMethod false positive from inner class via external class #1189 + 1 + 4 + + From 2d068f9ac086e3a63d6754b3fe039b2febe16185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 10 Nov 2020 13:08:46 +0100 Subject: [PATCH 31/45] Check fixes #770 --- .../bestpractices/xml/UnusedPrivateMethod.xml | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml index bff35b4889..95e8536e44 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml @@ -1714,4 +1714,37 @@ public class UnusedPrivateMethodFP { } ]]> + + UnusedPrivateMethod yields false positive for counter-variant arguments #770 + 0 + + From 2621e9c63687e03661016c720cc1324f3183ae89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Wed, 18 Nov 2020 17:48:10 +0100 Subject: [PATCH 32/45] Update InefficientStringBuffering --- .../lang/java/rule/internal/JavaRuleUtil.java | 8 + .../InefficientStringBufferingRule.java | 278 +++--------------- .../InefficientStringBufferingTest.java | 1 - .../xml/InefficientStringBuffering.xml | 13 +- 4 files changed, 54 insertions(+), 246 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java index 64143db2df..a7d1fc56ce 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -116,6 +116,14 @@ public final class JavaRuleUtil { return false; } + public static boolean isStringConcatExpr(@Nullable ASTExpression e) { + if (e instanceof ASTInfixExpression) { + ASTInfixExpression infix = (ASTInfixExpression) e; + return infix.getOperator() == BinaryOp.ADD && TypeTestUtil.isA(String.class, infix); + } + return false; + } + /** * Returns true if the node is a {@link ASTMethodDeclaration} that * is a main method. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingRule.java index 9cf762af10..c3eece6c66 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingRule.java @@ -4,31 +4,18 @@ package net.sourceforge.pmd.lang.java.rule.performance; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - +import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression; -import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; -import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; -import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression; -import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; -import net.sourceforge.pmd.lang.java.ast.ASTLiteral; -import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; -import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; -import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType; -import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; -import net.sourceforge.pmd.lang.java.ast.ASTType; -import net.sourceforge.pmd.lang.java.ast.AccessNode; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall; +import net.sourceforge.pmd.lang.java.ast.ASTExpression; +import net.sourceforge.pmd.lang.java.ast.ASTList; +import net.sourceforge.pmd.lang.java.ast.ASTMethodCall; +import net.sourceforge.pmd.lang.java.ast.InvocationNode; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; +import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; import net.sourceforge.pmd.lang.java.types.TypeTestUtil; +import net.sourceforge.pmd.lang.java.types.TypeTestUtil.InvocationMatcher; /** * How this rule works: find additive expressions: + check that the addition is @@ -37,240 +24,49 @@ import net.sourceforge.pmd.lang.java.types.TypeTestUtil; * * @author mgriffa */ -public class InefficientStringBufferingRule extends AbstractJavaRule { +public class InefficientStringBufferingRule extends AbstractJavaRulechainRule { + + private static final InvocationMatcher APPEND_MATCHER = InvocationMatcher.parse("java.lang.AbstractStringBuilder#append(_*)"); public InefficientStringBufferingRule() { - addRuleChainVisit(ASTAdditiveExpression.class); + super(ASTConstructorCall.class, ASTMethodCall.class); } @Override - public Object visit(ASTAdditiveExpression node, Object data) { - if (node.getParent() instanceof ASTConditionalExpression || node.getNthParent(2) instanceof ASTConditionalExpression) { - // ignore concats in ternary expressions - return data; + public Object visit(ASTMethodCall node, Object data) { + if (APPEND_MATCHER.matchesCall(node)) { + checkArgument(node.getArguments(), (RuleContext) data); } - - ASTBlockStatement bs = node.getFirstParentOfType(ASTBlockStatement.class); - if (bs == null) { - return data; - } - - int immediateLiterals = 0; - int immediateStringLiterals = 0; - List nodes = node.findDescendantsOfType(ASTLiteral.class); - for (ASTLiteral literal : nodes) { - if (literal.getNthParent(3) instanceof ASTAdditiveExpression) { - immediateLiterals++; - if (literal.isStringLiteral()) { - immediateStringLiterals++; - } - } - if (literal.isIntLiteral() || literal.isFloatLiteral() || literal.isDoubleLiteral() - || literal.isLongLiteral()) { - return data; - } - } - - boolean onlyStringLiterals = immediateLiterals == immediateStringLiterals - && immediateLiterals == node.getNumChildren(); - if (onlyStringLiterals) { - return data; - } - - // if literal + final, return - List nameNodes = node.findDescendantsOfType(ASTName.class); - for (ASTName name : nameNodes) { - if (name.getNameDeclaration() != null && name.getNameDeclaration() instanceof VariableNameDeclaration) { - VariableNameDeclaration vnd = (VariableNameDeclaration) name.getNameDeclaration(); - AccessNode accessNodeParent = vnd.getAccessNodeParent(); - if (accessNodeParent.isFinal()) { - return data; - } - } - } - - // if literal primitive type and not strings variables, then return - boolean stringFound = false; - for (ASTName name : nameNodes) { - if (!isPrimitiveType(name) && isStringType(name)) { - stringFound = true; - break; - } - } - if (!stringFound && immediateStringLiterals == 0) { - return data; - } - - if (bs.isAllocation()) { - for (Iterator iterator = nameNodes.iterator(); iterator.hasNext();) { - ASTName name = iterator.next(); - if (!name.getImage().endsWith("length")) { - break; - } else if (!iterator.hasNext()) { - return data; // All names end with length - } - } - - if (isAllocatedStringBuffer(node)) { - addViolation(data, node); - } else if (isInStringBufferOperationChain(node, "append")) { - addViolation(data, node); - } - } else if (isInStringBufferOperationChain(node, "append")) { - addViolation(data, node); - } - return data; + return null; } - private boolean isStringType(ASTName name) { - ASTType type = getTypeNode(name); - if (type != null) { - List types = type.findDescendantsOfType(ASTClassOrInterfaceType.class); - if (!types.isEmpty()) { - return TypeTestUtil.isA(String.class, types.get(0)); - } + @Override + public Object visit(ASTConstructorCall node, Object data) { + if (TypeTestUtil.isA(StringBuilder.class, node.getTypeNode()) + || TypeTestUtil.isA(StringBuffer.class, node.getTypeNode())) { + checkArgument(node.getArguments(), (RuleContext) data); } - return false; + return null; } - private boolean isPrimitiveType(ASTName name) { - ASTType type = getTypeNode(name); - return type != null && !type.findChildrenOfType(ASTPrimitiveType.class).isEmpty(); + private void checkArgument(ASTArgumentList argList, RuleContext ctx) { + ASTExpression arg = ASTList.singleOrNull(argList); + + if (JavaRuleUtil.isStringConcatExpr(arg) + // ignore concatenations that produce constants + && !arg.isCompileTimeConstant()) { + addViolation(ctx, arg); + } } - private ASTType getTypeNode(ASTName name) { - ASTType result = null; - if (name.getNameDeclaration() instanceof VariableNameDeclaration) { - VariableNameDeclaration vnd = (VariableNameDeclaration) name.getNameDeclaration(); - if (vnd.getAccessNodeParent() instanceof ASTLocalVariableDeclaration) { - ASTLocalVariableDeclaration l = (ASTLocalVariableDeclaration) vnd.getAccessNodeParent(); - result = l.getTypeNode(); - } else if (vnd.getAccessNodeParent() instanceof ASTFormalParameter) { - ASTFormalParameter p = (ASTFormalParameter) vnd.getAccessNodeParent(); - result = p.getTypeNode(); - } - } - return result; - } - - static boolean isInStringBufferOperationChain(Node node, String methodName) { - ASTPrimaryExpression expr = node.getFirstParentOfType(ASTPrimaryExpression.class); - MethodCallChain methodCalls = MethodCallChain.wrap(expr); - while (expr != null && methodCalls == null) { - expr = expr.getFirstParentOfType(ASTPrimaryExpression.class); - methodCalls = MethodCallChain.wrap(expr); - } - - if (methodCalls != null && !methodCalls.isExactlyOfAnyType(StringBuffer.class, StringBuilder.class)) { - methodCalls = null; - } - - return methodCalls != null && methodCalls.getMethodNames().contains(methodName); - } - - /** - * @deprecated will be removed with PMD 7 - */ - @Deprecated - protected static boolean isInStringBufferOperation(Node node, int length, String methodName) { - if (!(node.getNthParent(length) instanceof ASTStatementExpression)) { - return false; - } - ASTStatementExpression s = node.getFirstParentOfType(ASTStatementExpression.class); - if (s == null) { - return false; - } - ASTName n = s.getFirstDescendantOfType(ASTName.class); - if (n == null || !n.getImage().contains(methodName) - || !(n.getNameDeclaration() instanceof VariableNameDeclaration)) { + public static boolean isInStringBufferOperationChain(Node node, String append) { + // todo this was replaced by something that doesn't really work + if (!(node instanceof ASTExpression)) { return false; } + Node parent = node.getParent(); - // TODO having to hand-code this kind of dredging around is ridiculous - // we need something to support this in the framework - // but, "for now" (tm): - // if more than one arg to append(), skip it - ASTArgumentList argList = s.getFirstDescendantOfType(ASTArgumentList.class); - if (argList == null || argList.getNumChildren() > 1) { - return false; - } - return ConsecutiveLiteralAppendsRule.isStringBuilderOrBuffer(((VariableNameDeclaration) n.getNameDeclaration()).getDeclaratorId()); - } - - private boolean isAllocatedStringBuffer(ASTAdditiveExpression node) { - ASTAllocationExpression ao = node.getFirstParentOfType(ASTAllocationExpression.class); - if (ao == null) { - return false; - } - // note that the child can be an ArrayDimsAndInits, for example, from - // java.lang.FloatingDecimal: t = new int[ nWords+wordcount+1 ]; - ASTClassOrInterfaceType an = ao.getFirstChildOfType(ASTClassOrInterfaceType.class); - return ConsecutiveLiteralAppendsRule.isStringBuilderOrBuffer(an); - } - - private static class MethodCallChain { - private final ASTPrimaryExpression primary; - - private MethodCallChain(ASTPrimaryExpression primary) { - this.primary = primary; - } - - // Note: The impl here is technically not correct: The type of a method call - // chain is the result of the last method called, not the type of the - // first receiver object (== PrimaryPrefix). - boolean isExactlyOfAnyType(Class clazz, Class ... clazzes) { - ASTPrimaryPrefix typeNode = getTypeNode(); - - if (TypeTestUtil.isExactlyA(clazz, typeNode)) { - return true; - } - if (clazzes != null) { - for (Class c : clazzes) { - if (TypeTestUtil.isExactlyA(c, typeNode)) { - return true; - } - } - } - return false; - } - - ASTPrimaryPrefix getTypeNode() { - return primary.getFirstChildOfType(ASTPrimaryPrefix.class); - } - - List getMethodNames() { - List methodNames = new ArrayList<>(); - - ASTPrimaryPrefix prefix = getTypeNode(); - ASTName name = prefix.getFirstChildOfType(ASTName.class); - if (name != null) { - String firstMethod = name.getImage(); - int dot = firstMethod.lastIndexOf('.'); - if (dot != -1) { - firstMethod = firstMethod.substring(dot + 1); - } - methodNames.add(firstMethod); - } - - for (ASTPrimarySuffix suffix : primary.findChildrenOfType(ASTPrimarySuffix.class)) { - if (suffix.getImage() != null) { - methodNames.add(suffix.getImage()); - } - } - return methodNames; - } - - static MethodCallChain wrap(ASTPrimaryExpression primary) { - if (primary != null && isMethodCall(primary)) { - return new MethodCallChain(primary); - } - return null; - } - - private static boolean isMethodCall(ASTPrimaryExpression primary) { - return primary.getNumChildren() >= 2 - && primary.getChild(0) instanceof ASTPrimaryPrefix - && primary.getChild(1) instanceof ASTPrimarySuffix; - } + return parent instanceof ASTMethodCall + && APPEND_MATCHER.matchesCall((InvocationNode) parent); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingTest.java index 7ab7078a81..a7c07cecfa 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.performance; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class InefficientStringBufferingTest extends PmdRuleTst { // no additional unit tests } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InefficientStringBuffering.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InefficientStringBuffering.xml index 0c31fbb10a..dea5e40e83 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InefficientStringBuffering.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InefficientStringBuffering.xml @@ -301,7 +301,7 @@ public class Foo { 1503099, append with two string lengths - 1 + 0 No violation: Avoid concat in append method invocations - 0 + 3 + 26,39,45 Date: Wed, 18 Nov 2020 22:39:41 +0100 Subject: [PATCH 33/45] Extract some reusable logic --- .../lang/java/rule/internal/JavaRuleUtil.java | 58 ++++++++++++++++++- .../InefficientStringBufferingRule.java | 12 +--- .../RedundantFieldInitializerRule.java | 25 +------- .../performance/UselessStringValueOfRule.java | 8 +-- 4 files changed, 65 insertions(+), 38 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java index a7d1fc56ce..b45f793066 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -31,6 +31,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTBodyDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTForStatement; @@ -42,8 +43,10 @@ import net.sourceforge.pmd.lang.java.ast.ASTInitializer; import net.sourceforge.pmd.lang.java.ast.ASTLabeledStatement; import net.sourceforge.pmd.lang.java.ast.ASTList; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodCall; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral; import net.sourceforge.pmd.lang.java.ast.ASTNumericLiteral; import net.sourceforge.pmd.lang.java.ast.ASTStatement; import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression; @@ -56,6 +59,8 @@ import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.ast.JavaTokenKinds; import net.sourceforge.pmd.lang.java.ast.TypeNode; import net.sourceforge.pmd.lang.java.ast.UnaryOp; +import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind; +import net.sourceforge.pmd.lang.java.types.JTypeMirror; import net.sourceforge.pmd.lang.java.types.TypeTestUtil; import net.sourceforge.pmd.util.CollectionUtil; @@ -116,7 +121,7 @@ public final class JavaRuleUtil { return false; } - public static boolean isStringConcatExpr(@Nullable ASTExpression e) { + public static boolean isStringConcatExpr(@Nullable JavaNode e) { if (e instanceof ASTInfixExpression) { ASTInfixExpression infix = (ASTInfixExpression) e; return infix.getOperator() == BinaryOp.ADD && TypeTestUtil.isA(String.class, infix); @@ -124,6 +129,39 @@ public final class JavaRuleUtil { return false; } + /** + * If the parameter is an operand of a binary infix expression, + * returns the other operand. Otherwise returns null. + */ + public static @Nullable ASTExpression getOtherOperandIfInInfixExpr(@Nullable JavaNode e) { + if (e != null && e.getParent() instanceof ASTInfixExpression) { + return (ASTExpression) e.getParent().getChild(1 - e.getIndexInParent()); + } + return null; + } + + /** + * Returns true if the expression is a stringbuilder (or stringbuffer) + * append call, or a constructor call for one of these classes. + */ + public static boolean isStringBuilderCtorOrAppend(@Nullable ASTExpression e) { + if (e instanceof ASTMethodCall) { + ASTMethodCall call = (ASTMethodCall) e; + if ("append".equals(call.getMethodName())) { + ASTExpression qual = ((ASTMethodCall) e).getQualifier(); + return qual != null && isStringBufferOrBuilder(qual); + } + } else if (e instanceof ASTConstructorCall) { + return isStringBufferOrBuilder(((ASTConstructorCall) e).getTypeNode()); + } + return false; + } + + private static boolean isStringBufferOrBuilder(TypeNode node) { + return TypeTestUtil.isExactlyA(StringBuilder.class, node) + || TypeTestUtil.isExactlyA(StringBuffer.class, node); + } + /** * Returns true if the node is a {@link ASTMethodDeclaration} that * is a main method. @@ -461,4 +499,22 @@ public final class JavaRuleUtil { return e instanceof ASTUnaryExpression ? ((ASTUnaryExpression) e).getOperand() : null; } + + /** + * Returns true if the expression is the default field value for + * the given type. + */ + public static boolean isDefaultValue(JTypeMirror type, ASTExpression expr) { + if (type.isPrimitive()) { + if (type.isPrimitive(PrimitiveTypeKind.BOOLEAN)) { + return expr instanceof ASTBooleanLiteral && !((ASTBooleanLiteral) expr).isTrue(); + } else { + Object constValue = expr.getConstValue(); + return constValue instanceof Number && ((Number) constValue).doubleValue() == 0d + || constValue instanceof Character && constValue.equals('\u0000'); + } + } else { + return expr instanceof ASTNullLiteral; + } + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingRule.java index c3eece6c66..b85b553c62 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingRule.java @@ -11,11 +11,8 @@ import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTList; import net.sourceforge.pmd.lang.java.ast.ASTMethodCall; -import net.sourceforge.pmd.lang.java.ast.InvocationNode; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; -import net.sourceforge.pmd.lang.java.types.TypeTestUtil; -import net.sourceforge.pmd.lang.java.types.TypeTestUtil.InvocationMatcher; /** * How this rule works: find additive expressions: + check that the addition is @@ -26,15 +23,13 @@ import net.sourceforge.pmd.lang.java.types.TypeTestUtil.InvocationMatcher; */ public class InefficientStringBufferingRule extends AbstractJavaRulechainRule { - private static final InvocationMatcher APPEND_MATCHER = InvocationMatcher.parse("java.lang.AbstractStringBuilder#append(_*)"); - public InefficientStringBufferingRule() { super(ASTConstructorCall.class, ASTMethodCall.class); } @Override public Object visit(ASTMethodCall node, Object data) { - if (APPEND_MATCHER.matchesCall(node)) { + if (JavaRuleUtil.isStringBuilderCtorOrAppend(node)) { checkArgument(node.getArguments(), (RuleContext) data); } return null; @@ -42,8 +37,7 @@ public class InefficientStringBufferingRule extends AbstractJavaRulechainRule { @Override public Object visit(ASTConstructorCall node, Object data) { - if (TypeTestUtil.isA(StringBuilder.class, node.getTypeNode()) - || TypeTestUtil.isA(StringBuffer.class, node.getTypeNode())) { + if (JavaRuleUtil.isStringBuilderCtorOrAppend(node)) { checkArgument(node.getArguments(), (RuleContext) data); } return null; @@ -67,6 +61,6 @@ public class InefficientStringBufferingRule extends AbstractJavaRulechainRule { Node parent = node.getParent(); return parent instanceof ASTMethodCall - && APPEND_MATCHER.matchesCall((InvocationNode) parent); + && JavaRuleUtil.isStringBuilderCtorOrAppend((ASTMethodCall) parent); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerRule.java index 801e1f4ab0..575fdf70c3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerRule.java @@ -4,17 +4,14 @@ package net.sourceforge.pmd.lang.java.rule.performance; -import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTFieldAccess; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral; import net.sourceforge.pmd.lang.java.ast.ASTVariableAccess; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.JModifier; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; -import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind; -import net.sourceforge.pmd.lang.java.types.JTypeMirror; +import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; /** * Detects redundant field initializers, i.e. the field initializer expressions @@ -35,7 +32,7 @@ public class RedundantFieldInitializerRule extends AbstractJavaRulechainRule { for (ASTVariableDeclaratorId varId : fieldDeclaration.getVarIds()) { ASTExpression init = varId.getInitializer(); if (init != null) { - if (isDefaultValue(varId.getTypeMirror(), init)) { + if (JavaRuleUtil.isDefaultValue(varId.getTypeMirror(), init) && !isOkExpr(init)) { addViolation(data, varId); } } @@ -44,24 +41,6 @@ public class RedundantFieldInitializerRule extends AbstractJavaRulechainRule { return data; } - private boolean isDefaultValue(JTypeMirror type, ASTExpression expr) { - if (type.isPrimitive()) { - if (type.isPrimitive(PrimitiveTypeKind.BOOLEAN)) { - return expr instanceof ASTBooleanLiteral && !((ASTBooleanLiteral) expr).isTrue(); - } else { - if (!isOkExpr(expr)) { - // whitelist named constants or calculations involving them - return false; - } - Object constValue = expr.getConstValue(); - return constValue instanceof Number && ((Number) constValue).doubleValue() == 0d - || constValue instanceof Character && constValue.equals('\u0000'); - } - } else { - return expr instanceof ASTNullLiteral; - } - } - private static boolean isOkExpr(ASTExpression e) { return e.descendantsOrSelf().none(it -> it instanceof ASTVariableAccess || it instanceof ASTFieldAccess); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UselessStringValueOfRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UselessStringValueOfRule.java index 64a2a8c1c7..0720956404 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UselessStringValueOfRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UselessStringValueOfRule.java @@ -8,10 +8,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.lang.java.ast.ASTExpression; -import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression; import net.sourceforge.pmd.lang.java.ast.ASTMethodCall; -import net.sourceforge.pmd.lang.java.ast.BinaryOp; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; import net.sourceforge.pmd.lang.java.types.TypeTestUtil; import net.sourceforge.pmd.lang.rule.RuleTargetSelector; @@ -25,8 +24,7 @@ public class UselessStringValueOfRule extends AbstractJavaRule { @Override public Object visit(ASTMethodCall node, Object data) { - if (node.getParent() instanceof ASTInfixExpression - && ((ASTInfixExpression) node.getParent()).getOperator() == BinaryOp.ADD) { + if (JavaRuleUtil.isStringConcatExpr(node.getParent())) { ASTExpression valueOfArg = getValueOfArg(node); if (valueOfArg == null) { return data; //not a valueOf call @@ -35,7 +33,7 @@ public class UselessStringValueOfRule extends AbstractJavaRule { return data; } - ASTExpression sibling = (ASTExpression) node.getParent().getChild(1 - node.getIndexInParent()); + ASTExpression sibling = JavaRuleUtil.getOtherOperandIfInInfixExpr(node); if (TypeTestUtil.isExactlyA(String.class, sibling) && !valueOfArg.getTypeMirror().isArray() // In `String.valueOf(a) + String.valueOf(b)`, From e946fda40fb8c763acdb71b0a39165b6d1e8e753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Thu, 10 Dec 2020 21:04:25 +0100 Subject: [PATCH 34/45] Rename new method --- .../lang/java/ast/ASTVariableDeclaratorId.java | 8 ++++---- .../AvoidReassigningCatchVariablesRule.java | 2 +- .../AvoidReassigningLoopVariablesRule.java | 4 ++-- .../AvoidReassigningParametersRule.java | 2 +- .../MethodArgumentCouldBeFinalRule.java | 2 +- .../java/rule/codestyle/UnnecessaryCastRule.java | 2 +- .../UnnecessaryLocalBeforeReturnRule.java | 2 +- .../lang/java/rule/design/SingularFieldRule.java | 2 +- .../rule/errorprone/CheckSkipResultRule.java | 2 +- .../lang/java/rule/internal/JavaRuleUtil.java | 2 +- .../UnsynchronizedStaticFormatterRule.java | 2 +- .../InsufficientStringBufferDeclarationRule.java | 2 +- .../UseStringBufferForStringAppendsRule.java | 2 +- .../lang/java/symboltable/AcceptanceTest.java | 8 ++++---- .../pmd/lang/java/ast/UsageResolutionTest.kt | 16 ++++++++-------- 15 files changed, 29 insertions(+), 29 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java index aaf4d4642e..bdedcc44ab 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java @@ -36,7 +36,7 @@ import net.sourceforge.pmd.lang.symboltable.NameOccurrence; * *

Since this node conventionally represents the declared variable in PMD, our symbol table * populates it with a {@link VariableNameDeclaration}, and its usages can be accessed through - * the method {@link #oldGetUsages ()}. + * the method {@link #getUsages ()}. * *

Type resolution assigns the type of the variable to this node. See {@link #getType()}'s * documentation for the contract of this method. @@ -79,10 +79,10 @@ public final class ASTVariableDeclaratorId extends AbstractTypedSymbolDeclarator } /** - * @deprecated transitional, use {@link #getUsages()} + * @deprecated transitional, use {@link #getLocalUsages()} */ @Deprecated - public List oldGetUsages() { + public List getUsages() { return getScope().getDeclarations(VariableNameDeclaration.class).get(nameDeclaration); } @@ -94,7 +94,7 @@ public final class ASTVariableDeclaratorId extends AbstractTypedSymbolDeclarator *

Note that a variable initializer is not part of the usages * (though this should be evident from the return type). */ - public List getUsages() { + public List getLocalUsages() { return usages; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesRule.java index 94d0fcbbd4..874c9ae288 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningCatchVariablesRule.java @@ -23,7 +23,7 @@ public class AvoidReassigningCatchVariablesRule extends AbstractJavaRule { @Override public Object visit(ASTCatchParameter catchParam, Object data) { ASTVariableDeclaratorId caughtExceptionId = catchParam.getVarId(); - for (ASTNamedReferenceExpr usage : caughtExceptionId.getUsages()) { + for (ASTNamedReferenceExpr usage : caughtExceptionId.getLocalUsages()) { if (usage.getAccessType() == AccessType.WRITE) { addViolation(data, usage, caughtExceptionId.getName()); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java index afa96196e8..0826840ba2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java @@ -71,7 +71,7 @@ public class AvoidReassigningLoopVariablesRule extends AbstractJavaRulechainRule } ASTVariableDeclaratorId loopVar = loopStmt.getVarId(); boolean ignoreNext = behavior == ForeachReassignOption.FIRST_ONLY; - for (ASTNamedReferenceExpr usage : loopVar.getUsages()) { + for (ASTNamedReferenceExpr usage : loopVar.getLocalUsages()) { if (usage.getAccessType() == AccessType.WRITE) { if (ignoreNext) { ignoreNext = false; @@ -95,7 +95,7 @@ public class AvoidReassigningLoopVariablesRule extends AbstractJavaRulechainRule NodeStream loopVars = JavaRuleUtil.getLoopVariables(loopStmt); if (behavior == ForReassignOption.DENY) { for (ASTVariableDeclaratorId loopVar : loopVars) { - for (ASTNamedReferenceExpr usage : loopVar.getUsages()) { + for (ASTNamedReferenceExpr usage : loopVar.getLocalUsages()) { if (usage.getAccessType() == AccessType.WRITE) { if (update != null && usage.ancestors(ASTForUpdate.class).first() == update) { continue; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersRule.java index 69a97a24ef..6cfe5cbdf7 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersRule.java @@ -35,7 +35,7 @@ public class AvoidReassigningParametersRule extends AbstractJavaRulechainRule { private void lookForViolations(ASTMethodOrConstructorDeclaration node, Object data) { for (ASTFormalParameter formal : node.getFormalParameters()) { ASTVariableDeclaratorId varId = formal.getVarId(); - for (ASTNamedReferenceExpr usage : varId.getUsages()) { + for (ASTNamedReferenceExpr usage : varId.getLocalUsages()) { if (usage.getAccessType() == AccessType.WRITE) { addViolation(data, usage, varId.getName()); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalRule.java index 4b3a6c3b95..bd95e874e8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalRule.java @@ -48,7 +48,7 @@ public class MethodArgumentCouldBeFinalRule extends AbstractJavaRulechainRule { continue; } boolean used = false; - for (ASTNamedReferenceExpr usage : var.getUsages()) { + for (ASTNamedReferenceExpr usage : var.getLocalUsages()) { used = true; if (usage.getAccessType() == AccessType.WRITE) { continue outer; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryCastRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryCastRule.java index a726cb489c..82fd6bdedb 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryCastRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryCastRule.java @@ -90,7 +90,7 @@ public class UnnecessaryCastRule extends AbstractJavaRule { return; } ASTVariableDeclaratorId decl = node.getFirstDescendantOfType(ASTVariableDeclaratorId.class); - List usages = decl.oldGetUsages(); + List usages = decl.getUsages(); for (NameOccurrence no : usages) { ASTCastExpression castExpression = findCastExpression(no.getLocation()); if (castExpression != null) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnRule.java index c7e39160ab..ce35ada222 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnRule.java @@ -42,7 +42,7 @@ public class UnnecessaryLocalBeforeReturnRule extends AbstractJavaRulechainRule return null; } - if (varDecl.getUsages().size() != 1) { + if (varDecl.getLocalUsages().size() != 1) { return null; } // then this is the only usage diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java index 00c43a4a13..ce1777ce48 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java @@ -88,7 +88,7 @@ public class SingularFieldRule extends AbstractLombokAwareRule { for (ASTVariableDeclarator declarator : node.findChildrenOfType(ASTVariableDeclarator.class)) { ASTVariableDeclaratorId declaration = (ASTVariableDeclaratorId) declarator.getChild(0); - List usages = declaration.oldGetUsages(); + List usages = declaration.getUsages(); Node decl = null; boolean violation = true; for (int ix = 0; ix < usages.size(); ix++) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultRule.java index 1a0a4484cf..3b5710f780 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultRule.java @@ -24,7 +24,7 @@ public class CheckSkipResultRule extends AbstractJavaRule { if (!TypeTestUtil.isA(InputStream.class, node.getTypeNode())) { return data; } - for (NameOccurrence occ : node.oldGetUsages()) { + for (NameOccurrence occ : node.getUsages()) { JavaNameOccurrence jocc = (JavaNameOccurrence) occ; NameOccurrence qualifier = jocc.getNameForWhichThisIsAQualifier(); if (qualifier != null && "skip".equals(qualifier.getImage())) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java index b45f793066..cd441f7ebe 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -281,7 +281,7 @@ public final class JavaRuleUtil { * us to be sure of it. */ public static boolean isNeverUsed(ASTVariableDeclaratorId varId) { - return CollectionUtil.none(varId.getUsages(), JavaRuleUtil::isReadUsage); + return CollectionUtil.none(varId.getLocalUsages(), JavaRuleUtil::isReadUsage); } private static boolean isReadUsage(ASTNamedReferenceExpr expr) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticFormatterRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticFormatterRule.java index 1e1fccfeb6..414d547b90 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticFormatterRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticFormatterRule.java @@ -70,7 +70,7 @@ public class UnsynchronizedStaticFormatterRule extends AbstractJavaRule { return data; } } - for (NameOccurrence occ : var.oldGetUsages()) { + for (NameOccurrence occ : var.getUsages()) { Node n = occ.getLocation(); // ignore usages, that don't call a method. if (!n.getImage().contains(".")) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java index 35a59e8534..3aeeffa35a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java @@ -67,7 +67,7 @@ public class InsufficientStringBufferDeclarationRule extends AbstractJavaRule { anticipatedLength += getConstructorAppendsLength(node); - List usage = node.oldGetUsages(); + List usage = node.getUsages(); Map> blocks = new HashMap<>(); for (NameOccurrence no : usage) { JavaNameOccurrence jno = (JavaNameOccurrence) no; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsRule.java index 46a27b763c..ac91f98e46 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsRule.java @@ -46,7 +46,7 @@ public class UseStringBufferForStringAppendsRule extends AbstractJavaRule { // Remember how often we the variable has been used int usageCounter = 0; - for (NameOccurrence no : node.oldGetUsages()) { + for (NameOccurrence no : node.getUsages()) { Node name = no.getLocation(); ASTStatementExpression statement = name.getFirstParentOfType(ASTStatementExpression.class); if (statement == null) { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java index d60e2869a8..b229cee8d8 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java @@ -88,8 +88,8 @@ public class AcceptanceTest extends BaseNonParserTest { ASTVariableDeclaratorId declaration = acu.findDescendantsOfType(ASTVariableDeclaratorId.class).get(1); assertEquals(3, declaration.getBeginLine()); assertEquals("bbbbbbbbbb", declaration.getImage()); - assertEquals(1, declaration.oldGetUsages().size()); - NameOccurrence no = declaration.oldGetUsages().get(0); + assertEquals(1, declaration.getUsages().size()); + NameOccurrence no = declaration.getUsages().get(0); Node location = no.getLocation(); assertEquals(6, location.getBeginLine()); // System.out.println("variable " + declaration.getImage() + " is used @@ -124,7 +124,7 @@ public class AcceptanceTest extends BaseNonParserTest { ASTCompilationUnit acu = parseCode(NameOccurrencesTest.TEST_ENUM); ASTVariableDeclaratorId vdi = acu.findDescendantsOfType(ASTVariableDeclaratorId.class).get(0); - List usages = vdi.oldGetUsages(); + List usages = vdi.getUsages(); assertEquals(2, usages.size()); assertEquals(5, usages.get(0).getLocation().getBeginLine()); assertEquals(9, usages.get(1).getLocation().getBeginLine()); @@ -135,7 +135,7 @@ public class AcceptanceTest extends BaseNonParserTest { ASTCompilationUnit acu = parseCode(TEST_INNER_CLASS); ASTVariableDeclaratorId vdi = acu.findDescendantsOfType(ASTClassOrInterfaceDeclaration.class).get(1) // get inner class .getFirstDescendantOfType(ASTVariableDeclaratorId.class); // get first declaration - List usages = vdi.oldGetUsages(); + List usages = vdi.getUsages(); assertEquals(2, usages.size()); assertEquals(5, usages.get(0).getLocation().getBeginLine()); assertEquals(10, usages.get(1).getLocation().getBeginLine()); diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/UsageResolutionTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/UsageResolutionTest.kt index f7951cf9ba..381d95f0d9 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/UsageResolutionTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/UsageResolutionTest.kt @@ -40,11 +40,11 @@ class UsageResolutionTest : ProcessorTestSpec({ } """) val (barF1, fooF1, fooF2, localF2, localF22) = acu.descendants(ASTVariableDeclaratorId::class.java).toList() - barF1.usages.map { it.text.toString() }.shouldContainExactly("this.f1", "super.f1") - fooF1.usages.map { it.text.toString() }.shouldContainExactly("f1", "this.f1") - fooF2.usages.map { it.text.toString() }.shouldContainExactly("this.f2") - localF2.usages.shouldBeEmpty() - localF22.usages.shouldBeSingleton { + barF1.localUsages.map { it.text.toString() }.shouldContainExactly("this.f1", "super.f1") + fooF1.localUsages.map { it.text.toString() }.shouldContainExactly("f1", "this.f1") + fooF2.localUsages.map { it.text.toString() }.shouldContainExactly("this.f2") + localF2.localUsages.shouldBeEmpty() + localF22.localUsages.shouldBeSingleton { it.accessType shouldBe WRITE } } @@ -63,13 +63,13 @@ class UsageResolutionTest : ProcessorTestSpec({ val (p) = acu.descendants(ASTVariableDeclaratorId::class.java).toList() p::isRecordComponent shouldBe true - p.usages.shouldHaveSize(2) - p.usages[0].shouldBeA { + p.localUsages.shouldHaveSize(2) + p.localUsages[0].shouldBeA { it.referencedSym!!.shouldBeA { it.tryGetNode() shouldBe p } } - p.usages[1].shouldBeA { + p.localUsages[1].shouldBeA { it.referencedSym!!.shouldBeA { it.tryGetNode() shouldBe p } From 2626025eeff4d26ed7c6fc8fcee87a3bcafa5069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 13 Feb 2021 17:07:18 +0100 Subject: [PATCH 35/45] Fix some tests Mostly caused by untested merges --- .../pmd/lang/java/ast/ASTReturnStatement.java | 9 --------- .../pmd/lang/java/ast/ASTVariableDeclaratorId.java | 13 +++++++------ .../bestpractices/UnusedFormalParameterRule.java | 3 +-- .../rule/bestpractices/UnusedPrivateFieldRule.java | 2 +- .../performance/RedundantFieldInitializerRule.java | 7 ++++--- .../net/sourceforge/pmd/lang/java/types/Lub.java | 6 ------ .../java/types/internal/infer/SpecialMethodsTest.kt | 6 ++++-- 7 files changed, 17 insertions(+), 29 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReturnStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReturnStatement.java index ea67c7f742..bd95e6a0d1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReturnStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReturnStatement.java @@ -6,8 +6,6 @@ package net.sourceforge.pmd.lang.java.ast; import org.checkerframework.checker.nullness.qual.Nullable; -import net.sourceforge.pmd.lang.ast.NodeStream; - /** * A return statement in a method or constructor body. * @@ -30,13 +28,6 @@ public final class ASTReturnStatement extends AbstractStatement { return visitor.visit(this, data); } - /** - * Returns the method, ctor or lambda that this statement terminates. - */ - public JavaNode getTarget() { - return ancestors().map(NodeStream.asInstanceOf(ASTMethodOrConstructorDeclaration.class, ASTLambdaExpression.class)).first(); - } - /** * Returns the returned expression, or null if this is a simple return. */ diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java index bdedcc44ab..fdc882acbc 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java @@ -36,7 +36,7 @@ import net.sourceforge.pmd.lang.symboltable.NameOccurrence; * *

Since this node conventionally represents the declared variable in PMD, our symbol table * populates it with a {@link VariableNameDeclaration}, and its usages can be accessed through - * the method {@link #getUsages ()}. + * the method {@link #getUsages()}. * *

Type resolution assigns the type of the variable to this node. See {@link #getType()}'s * documentation for the contract of this method. @@ -105,11 +105,6 @@ public final class ASTVariableDeclaratorId extends AbstractTypedSymbolDeclarator usages.add(usage); } - @Override - public Visibility getVisibility() { - return getModifierOwnerParent().getVisibility(); - } - /** * Returns the extra array dimensions associated with this variable. * For example in the declaration {@code int a[]}, {@link #getTypeNode()} @@ -134,6 +129,12 @@ public final class ASTVariableDeclaratorId extends AbstractTypedSymbolDeclarator return getModifierOwnerParent().getModifiers(); } + @Override + public Visibility getVisibility() { + return isPatternBinding() ? Visibility.V_LOCAL + : getModifierOwnerParent().getVisibility(); + } + private AccessNode getModifierOwnerParent() { JavaNode parent = getParent(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java index 0c78a21b1e..7f6d3dad29 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java @@ -14,7 +14,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.AccessNode.Visibility; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; -import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil; import net.sourceforge.pmd.properties.PropertyDescriptor; @@ -47,7 +46,7 @@ public class UnusedFormalParameterRule extends AbstractJavaRule { if (!node.getEnclosingType().isInterface()) { for (ASTFormalParameter formal : node.getFormalParameters()) { ASTVariableDeclaratorId varId = formal.getVarId(); - if (JavaRuleUtil.isNeverUsed(varId)) { + if (JavaRuleUtil.isNeverUsed(varId) && !JavaRuleUtil.isExplicitUnusedVarName(varId.getName())) { addViolation(data, varId, new Object[] {node instanceof ASTMethodDeclaration ? "method" : "constructor", varId.getName(),}); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java index 14f2d28cec..446b71a197 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java @@ -39,7 +39,7 @@ public class UnusedPrivateFieldRule extends AbstractLombokAwareRule { public Object visitJavaNode(JavaNode node, Object data) { if (node instanceof ASTAnyTypeDeclaration) { ASTAnyTypeDeclaration type = (ASTAnyTypeDeclaration) node; - if (hasIgnoredAnnotation(type) || hasLombokAnnotation(type)) { + if (hasIgnoredAnnotation(type)) { return null; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerRule.java index 575fdf70c3..9970dd31ea 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerRule.java @@ -32,7 +32,7 @@ public class RedundantFieldInitializerRule extends AbstractJavaRulechainRule { for (ASTVariableDeclaratorId varId : fieldDeclaration.getVarIds()) { ASTExpression init = varId.getInitializer(); if (init != null) { - if (JavaRuleUtil.isDefaultValue(varId.getTypeMirror(), init) && !isOkExpr(init)) { + if (!isWhitelisted(init) && JavaRuleUtil.isDefaultValue(varId.getTypeMirror(), init)) { addViolation(data, varId); } } @@ -41,7 +41,8 @@ public class RedundantFieldInitializerRule extends AbstractJavaRulechainRule { return data; } - private static boolean isOkExpr(ASTExpression e) { - return e.descendantsOrSelf().none(it -> it instanceof ASTVariableAccess || it instanceof ASTFieldAccess); + // whitelist if there are named variables in there + private static boolean isWhitelisted(ASTExpression e) { + return e.descendantsOrSelf().any(it -> it instanceof ASTVariableAccess || it instanceof ASTFieldAccess); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/Lub.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/Lub.java index e39c71baf6..154db01020 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/Lub.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/Lub.java @@ -356,12 +356,6 @@ final class Lub { } } - private static void checkGlbComponent(Collection types, JTypeMirror ci) { - if (ci.isPrimitive() || ci instanceof JWildcardType || ci instanceof JIntersectionType) { - throw new IllegalArgumentException("Bad intersection type component: " + ci + " in " + types); - } - } - private static @NonNull List flattenRemoveTrivialBound(Collection types) { List bounds = new ArrayList<>(types.size()); diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/SpecialMethodsTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/SpecialMethodsTest.kt index 400f8d192a..efecaa6052 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/SpecialMethodsTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/SpecialMethodsTest.kt @@ -14,6 +14,7 @@ import net.sourceforge.pmd.lang.java.symbols.JFormalParamSymbol import net.sourceforge.pmd.lang.java.types.captureMatcher import net.sourceforge.pmd.lang.java.types.parseWithTypeInferenceSpy import net.sourceforge.pmd.lang.java.types.typeDsl +import net.sourceforge.pmd.lang.java.types.varId import java.util.function.Supplier /** @@ -201,16 +202,17 @@ class SpecialMethodsTest : ProcessorTestSpec({ """.trimIndent()) val (compLhs, compRhs) = acu.descendants(ASTVariableAccess::class.java).toList() + val id = acu.varId("comp") spy.shouldBeOk { compLhs.referencedSym.shouldBeA { - it.tryGetNode() shouldBe null // this could be controversial + it.tryGetNode() shouldBe id it.declaringSymbol.shouldBeA() } // same spec compRhs.referencedSym.shouldBeA { - it.tryGetNode() shouldBe null + it.tryGetNode() shouldBe id it.declaringSymbol.shouldBeA() } } From 533b316eaacf7647fdd5c3cf27dc5fe12e92814f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 13 Feb 2021 19:47:51 +0100 Subject: [PATCH 36/45] Checkstyle + PMD --- .../pmd/lang/java/ast/ASTDoStatement.java | 1 + .../pmd/lang/java/ast/BinaryOp.java | 3 ++- .../AvoidReassigningLoopVariablesRule.java | 12 +++++------ .../UnusedFormalParameterRule.java | 2 +- .../lang/java/rule/internal/JavaRuleUtil.java | 20 ++----------------- .../ConsecutiveAppendsShouldReuseRule.java | 4 ++-- 6 files changed, 13 insertions(+), 29 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDoStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDoStatement.java index 4a4d32d917..0b89f8b660 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDoStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDoStatement.java @@ -35,6 +35,7 @@ public final class ASTDoStatement extends AbstractStatement implements ASTLoopSt * Returns the statement that will be run while the guard * evaluates to true. */ + @Override public ASTStatement getBody() { return (ASTStatement) getChild(0); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/BinaryOp.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/BinaryOp.java index 0759def477..08739cd071 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/BinaryOp.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/BinaryOp.java @@ -181,7 +181,8 @@ public enum BinaryOp implements InternalInterfaces.OperatorLike { case GE: return LT; case GT: return LE; case LT: return GE; + + default: return null; } - return null; } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java index 0826840ba2..138edf64b5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java @@ -175,7 +175,7 @@ public class AvoidReassigningLoopVariablesRule extends AbstractJavaRulechainRule ASTStatement body = ((ASTLoopStatement) stmt).getBody(); for (JavaNode child : stmt.children()) { - if (child != body) { + if (child != body) { // NOPMD checkVorViolations(child); } } @@ -195,10 +195,7 @@ public class AvoidReassigningLoopVariablesRule extends AbstractJavaRulechainRule checkVorViolations(ifStmt.getCondition()); mayExit |= withGuard(true).roamStatementsForExit(ifStmt.getThenBranch()); mayExit |= withGuard(this.guarded).roamStatementsForExit(ifStmt.getElseBranch()); - - } - // these two catch-all clauses implement other statements & eg switch branches - else if (stmt instanceof ASTExpression) { + } else if (stmt instanceof ASTExpression) { // these two catch-all clauses implement other statements & eg switch branches checkVorViolations(stmt); } else if (!(stmt instanceof ASTLocalClassStatement)) { mayExit |= roamStatementsForExit(stmt.children()); @@ -211,10 +208,11 @@ public class AvoidReassigningLoopVariablesRule extends AbstractJavaRulechainRule if (node == null) { return; } + final boolean onlyConsiderWrite = guarded || mayExit; node.descendants(ASTNamedReferenceExpr.class) .filter(it -> loopVarNames.contains(it.getName())) - .filter(it -> (guarded || mayExit) ? JavaRuleUtil.isVarAccessStrictlyWrite(it) - : JavaRuleUtil.isVarAccessReadAndWrite(it)) + .filter(it -> onlyConsiderWrite ? JavaRuleUtil.isVarAccessStrictlyWrite(it) + : JavaRuleUtil.isVarAccessReadAndWrite(it)) .forEach(it -> addViolation(ruleCtx, it, it.getName())); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java index 7f6d3dad29..64f2895624 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java @@ -47,7 +47,7 @@ public class UnusedFormalParameterRule extends AbstractJavaRule { for (ASTFormalParameter formal : node.getFormalParameters()) { ASTVariableDeclaratorId varId = formal.getVarId(); if (JavaRuleUtil.isNeverUsed(varId) && !JavaRuleUtil.isExplicitUnusedVarName(varId.getName())) { - addViolation(data, varId, new Object[] {node instanceof ASTMethodDeclaration ? "method" : "constructor", varId.getName(),}); + addViolation(data, varId, new Object[] {node instanceof ASTMethodDeclaration ? "method" : "constructor", varId.getName(), }); } } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java index cfc71dec50..b6d553544d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.java @@ -37,7 +37,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; -import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression; import net.sourceforge.pmd.lang.java.ast.ASTInitializer; import net.sourceforge.pmd.lang.java.ast.ASTLabeledStatement; @@ -336,15 +335,6 @@ public final class JavaRuleUtil { .collect(Collectors.toSet()); } - /** - * True if the variable is incremented or decremented via a compound - * assignment operator, or a unary increment/decrement expression. - */ - public static boolean isInIfCondition(ASTExpression expr) { - ASTExpression toplevel = getTopLevelExpr(expr); - return toplevel.getIndexInParent() == 0 && toplevel.getParent() instanceof ASTIfStatement; - } - /** * Will cut through argument lists, except those of enum constants * and explicit invocation nodes. @@ -374,10 +364,7 @@ public final class JavaRuleUtil { */ public static boolean isSerialPersistentFields(final ASTFieldDeclaration field) { return field.hasModifiers(JModifier.FINAL, JModifier.STATIC, JModifier.PRIVATE) - && field.getVarIds().any( - it -> "serialPersistentFields".equals(it.getName()) - && TypeTestUtil.isA(ObjectStreamField[].class, it) - ); + && field.getVarIds().any(it -> "serialPersistentFields".equals(it.getName()) && TypeTestUtil.isA(ObjectStreamField[].class, it)); } /** @@ -386,10 +373,7 @@ public final class JavaRuleUtil { */ public static boolean isSerialVersionUID(ASTFieldDeclaration field) { return field.hasModifiers(JModifier.FINAL, JModifier.STATIC) - && field.getVarIds().any( - it -> "serialVersionUID".equals(it.getName()) - && it.getTypeMirror().isPrimitive(LONG) - ); + && field.getVarIds().any(it -> "serialVersionUID".equals(it.getName()) && it.getTypeMirror().isPrimitive(LONG)); } /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseRule.java index 7cf67c997d..00812b3de6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseRule.java @@ -77,7 +77,7 @@ public class ConsecutiveAppendsShouldReuseRule extends AbstractJavaRule { while (expr instanceof ASTMethodCall && isStringBuilderAppend(expr)) { expr = ((ASTMethodCall) expr).getQualifier(); } - return base == expr ? null : expr; + return base == expr ? null : expr; // NOPMD } private @Nullable JVariableSymbol getAssignmentLhsAsVar(@Nullable ASTExpression expr) { @@ -97,7 +97,7 @@ public class ConsecutiveAppendsShouldReuseRule extends AbstractJavaRule { private boolean isStringBuilderAppend(@Nullable ASTExpression e) { if (e instanceof ASTMethodCall) { ASTMethodCall call = (ASTMethodCall) e; - return call.getMethodName().equals("append") + return "append".equals(call.getMethodName()) && isStringBuilderAppend(call.getOverloadSelectionInfo()); } return false; From ed7b2f056722d244b61cafb3ece422ba05da1c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 13 Feb 2021 20:49:16 +0100 Subject: [PATCH 37/45] Fix a bug with siblings --- .../java/net/sourceforge/pmd/lang/ast/Node.java | 2 +- .../pmd/lang/ast/impl/AbstractNodeTest.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java index fde30c85f0..a01761f08a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java @@ -445,7 +445,7 @@ public interface Node { default @Nullable Node getNextSibling() { Node parent = getParent(); int idx = getIndexInParent(); - if (parent != null && idx < parent.getNumChildren()) { + if (parent != null && idx + 1 < parent.getNumChildren()) { return parent.getChild(idx + 1); } return null; diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/impl/AbstractNodeTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/impl/AbstractNodeTest.java index 84417431cb..2060b78597 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/impl/AbstractNodeTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/impl/AbstractNodeTest.java @@ -9,6 +9,7 @@ import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.root; import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.tree; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; import java.util.List; @@ -113,6 +114,22 @@ public class AbstractNodeTest { } } + @Test + public void testPrevNextSiblings() { + DummyRoot root = tree(() -> root(node(), node())); + + assertNull(root.getNextSibling()); + assertNull(root.getPreviousSibling()); + + DummyNode c0 = root.getChild(0); + DummyNode c1 = root.getChild(1); + + assertSame(c0, c1.getPreviousSibling()); + assertSame(c1, c0.getNextSibling()); + assertNull(c1.getNextSibling()); + assertNull(c0.getPreviousSibling()); + } + /** * Explicitly tests the {@code remove} method, and implicitly the {@code removeChildAtIndex} method. * This is a border case as the root node does not have any parent. From 0e8d2c9c079efc1764080e0072b87955359cf9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 14 Feb 2021 16:20:29 +0100 Subject: [PATCH 38/45] Update SystemPrintln --- .ci/files/all-java.xml | 2 +- .../main/resources/category/java/bestpractices.xml | 10 ++++------ .../java/rule/bestpractices/SystemPrintlnTest.java | 1 - .../java/rule/bestpractices/xml/SystemPrintln.xml | 13 +------------ 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/.ci/files/all-java.xml b/.ci/files/all-java.xml index e9077b1b17..2a0300ce7a 100644 --- a/.ci/files/all-java.xml +++ b/.ci/files/all-java.xml @@ -47,7 +47,7 @@ - + diff --git a/pmd-java/src/main/resources/category/java/bestpractices.xml b/pmd-java/src/main/resources/category/java/bestpractices.xml index 178cbb9a2f..e4942c2e9b 100644 --- a/pmd-java/src/main/resources/category/java/bestpractices.xml +++ b/pmd-java/src/main/resources/category/java/bestpractices.xml @@ -1202,7 +1202,7 @@ public void bar() { @@ -1215,11 +1215,9 @@ will (and by priority) and avoid clogging the Standard out log. diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/SystemPrintlnTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/SystemPrintlnTest.java index aebe3bb168..e17f5ee7be 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/SystemPrintlnTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/SystemPrintlnTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class SystemPrintlnTest extends PmdRuleTst { // no additional unit tests } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/SystemPrintln.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/SystemPrintln.xml index db6fc05e16..6068a8e945 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/SystemPrintln.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/SystemPrintln.xml @@ -8,7 +8,7 @@ one 1 - System.out.println is used + Usage of System.out/err many 3 - - System.out.println is used - System.out.println is used - System.out.println is used - #1217 SystemPrintln always says "System.out.print is used" 4 - - System.out.print is used - System.out.println is used - System.err.print is used - System.err.println is used - Date: Sun, 14 Feb 2021 16:41:15 +0100 Subject: [PATCH 39/45] Update AvoidUsingHardCodedIPRule --- .ci/files/all-java.xml | 2 +- .../pmd/properties/PropertyFactory.java | 21 ++++ .../sourceforge/pmd/util/CollectionUtil.java | 8 +- .../AvoidUsingHardCodedIPRule.java | 101 +++++++----------- .../AvoidUsingHardCodedIPTest.java | 1 - 5 files changed, 65 insertions(+), 68 deletions(-) diff --git a/.ci/files/all-java.xml b/.ci/files/all-java.xml index 2a0300ce7a..e560dfc782 100644 --- a/.ci/files/all-java.xml +++ b/.ci/files/all-java.xml @@ -19,7 +19,7 @@ - + diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyFactory.java index 94654fef2b..b8f7638b71 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyFactory.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyFactory.java @@ -4,14 +4,18 @@ package net.sourceforge.pmd.properties; +import static java.util.Arrays.asList; + import java.util.List; import java.util.Map; +import java.util.function.Function; import net.sourceforge.pmd.properties.PropertyBuilder.GenericCollectionPropertyBuilder; import net.sourceforge.pmd.properties.PropertyBuilder.GenericPropertyBuilder; import net.sourceforge.pmd.properties.PropertyBuilder.RegexPropertyBuilder; import net.sourceforge.pmd.properties.constraints.NumericConstraints; import net.sourceforge.pmd.properties.constraints.PropertyConstraint; +import net.sourceforge.pmd.util.CollectionUtil; //@formatter:off /** @@ -320,4 +324,21 @@ public final class PropertyFactory { } + /** + * Returns a builder for a property having as value a list of {@code }. The + * format of the individual items is the same as for {@linkplain #enumProperty(String, Map)}. + * + * @param name Name of the property to build + * @param enumClass Class of the values + * @param labelMaker Function that associates enum constants to their label + * @param Value type of the property + * + * @return A new builder + */ + public static > GenericCollectionPropertyBuilder> enumListProperty(String name, Class enumClass, Function labelMaker) { + Map enumMap = CollectionUtil.associateBy(asList(enumClass.getEnumConstants()), labelMaker); + return enumListProperty(name, enumMap); + } + + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/CollectionUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/CollectionUtil.java index 218ddf5a76..cfcb4fe5eb 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/CollectionUtil.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/CollectionUtil.java @@ -4,13 +4,13 @@ package net.sourceforge.pmd.util; +import static java.util.Arrays.asList; import static java.util.Collections.emptyIterator; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -259,7 +259,7 @@ public final class CollectionUtil { } List union = new ArrayList<>(); union.add(first); - union.addAll(Arrays.asList(rest)); + union.addAll(asList(rest)); return Collections.unmodifiableList(union); } @@ -302,7 +302,7 @@ public final class CollectionUtil { @SuppressWarnings("unchecked") public static Set setUnion(Collection set, V first, V... newElements) { if (set instanceof PSet) { - return ((PSet) set).plus(first).plusAll(Arrays.asList(newElements)); + return ((PSet) set).plus(first).plusAll(asList(newElements)); } Set newSet = new LinkedHashSet<>(set.size() + 1 + newElements.length); newSet.addAll(set); @@ -414,7 +414,7 @@ public final class CollectionUtil { if (from == null) { return emptyList(); } - return map(Arrays.asList(from), f); + return map(asList(from), f); } /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPRule.java index d09dc1a2c5..85f0b1e430 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPRule.java @@ -1,101 +1,83 @@ -/** +/* * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.java.rule.bestpractices; -import java.util.Collections; -import java.util.HashMap; +import static java.util.Arrays.asList; + +import java.util.EnumSet; import java.util.List; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.ASTLiteral; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.java.ast.ASTStringLiteral; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; import net.sourceforge.pmd.properties.PropertyDescriptor; import net.sourceforge.pmd.properties.PropertyFactory; -public class AvoidUsingHardCodedIPRule extends AbstractJavaRule { +public class AvoidUsingHardCodedIPRule extends AbstractJavaRulechainRule { - // why is everything public? + private enum AddressKinds { + IPV4("IPv4"), + IPV6("IPv6"), + IPV4_MAPPED_IPV6("IPv4 mapped IPv6"); - public static final String IPV4 = "IPv4"; - public static final String IPV6 = "IPv6"; - public static final String IPV4_MAPPED_IPV6 = "IPv4 mapped IPv6"; + private final String label; - private static final Map ADDRESSES_TO_CHECK; - - static { - Map tmp = new HashMap<>(); - tmp.put(IPV4, IPV4); - tmp.put(IPV6, IPV6); - tmp.put(IPV4_MAPPED_IPV6, IPV4_MAPPED_IPV6); - ADDRESSES_TO_CHECK = Collections.unmodifiableMap(tmp); + AddressKinds(String label) { + this.label = label; + } } - public static final PropertyDescriptor> CHECK_ADDRESS_TYPES_DESCRIPTOR = - PropertyFactory.enumListProperty("checkAddressTypes", ADDRESSES_TO_CHECK) - .desc("Check for IP address types.") - .defaultValue(ADDRESSES_TO_CHECK.keySet()).build(); + private static final PropertyDescriptor> CHECK_ADDRESS_TYPES_DESCRIPTOR = + PropertyFactory.enumListProperty("checkAddressTypes", AddressKinds.class, k -> k.label) + .desc("Check for IP address types.") + .defaultValue(asList(AddressKinds.values())) + .build(); // Provides 4 capture groups that can be used for additional validation - protected static final String IPV4_REGEXP = "([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})"; + private static final String IPV4_REGEXP = "([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})"; // Uses IPv4 pattern, but changes the groups to be non-capture - protected static final String IPV6_REGEXP = "(?:(?:[0-9a-fA-F]{1,4})?\\:)+(?:[0-9a-fA-F]{1,4}|" - + IPV4_REGEXP.replace("(", "(?:") + ")?"; + private static final String IPV6_REGEXP = "(?:(?:[0-9a-fA-F]{1,4})?\\:)+(?:[0-9a-fA-F]{1,4}|" + + IPV4_REGEXP.replace("(", "(?:") + ")?"; - protected static final Pattern IPV4_PATTERN = Pattern.compile("^" + IPV4_REGEXP + "$"); - protected static final Pattern IPV6_PATTERN = Pattern.compile("^" + IPV6_REGEXP + "$"); + private static final Pattern IPV4_PATTERN = Pattern.compile("^" + IPV4_REGEXP + "$"); + private static final Pattern IPV6_PATTERN = Pattern.compile("^" + IPV6_REGEXP + "$"); - protected boolean checkIPv4; - protected boolean checkIPv6; - protected boolean checkIPv4MappedIPv6; + private final EnumSet kindsToCheck = EnumSet.noneOf(AddressKinds.class); public AvoidUsingHardCodedIPRule() { + super(ASTStringLiteral.class); definePropertyDescriptor(CHECK_ADDRESS_TYPES_DESCRIPTOR); - - addRuleChainVisit(ASTCompilationUnit.class); - addRuleChainVisit(ASTLiteral.class); } @Override - public Object visit(ASTCompilationUnit node, Object data) { - checkIPv4 = false; - checkIPv6 = false; - checkIPv4MappedIPv6 = false; - for (Object addressType : getProperty(CHECK_ADDRESS_TYPES_DESCRIPTOR)) { - if (IPV4.equals(addressType)) { - checkIPv4 = true; - } else if (IPV6.equals(addressType)) { - checkIPv6 = true; - } else if (IPV4_MAPPED_IPV6.equals(addressType)) { - checkIPv4MappedIPv6 = true; - } - } - return data; + public void start(RuleContext ctx) { + kindsToCheck.clear(); + kindsToCheck.addAll(getProperty(CHECK_ADDRESS_TYPES_DESCRIPTOR)); } @Override - public Object visit(ASTLiteral node, Object data) { - if (!node.isStringLiteral()) { - return data; - } - - // Remove the quotes - final String image = node.getImage().substring(1, node.getImage().length() - 1); + public Object visit(ASTStringLiteral node, Object data) { + final String image = node.getConstValue(); // Note: We used to check the addresses using // InetAddress.getByName(String), but that's extremely slow, // so we created more robust checking methods. if (image.length() > 0) { final char firstChar = Character.toUpperCase(image.charAt(0)); + + boolean checkIPv4 = kindsToCheck.contains(AddressKinds.IPV4); + boolean checkIPv6 = kindsToCheck.contains(AddressKinds.IPV6); + boolean checkIPv4MappedIPv6 = kindsToCheck.contains(AddressKinds.IPV4_MAPPED_IPV6); + if (checkIPv4 && isIPv4(firstChar, image) || isIPv6(firstChar, image, checkIPv6, checkIPv4MappedIPv6)) { addViolation(data, node); } @@ -216,13 +198,8 @@ public class AvoidUsingHardCodedIPRule extends AbstractJavaRule { } } - public boolean hasChosenAddressTypes() { - return getProperty(CHECK_ADDRESS_TYPES_DESCRIPTOR).size() > 0; - } - - @Override public String dysfunctionReason() { - return hasChosenAddressTypes() ? null : "No address types specified"; + return !getProperty(CHECK_ADDRESS_TYPES_DESCRIPTOR).isEmpty() ? null : "No address types specified"; } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPTest.java index 8b97968659..63e4523ca2 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class AvoidUsingHardCodedIPTest extends PmdRuleTst { // no additional unit tests } From 63bc84f724f4ab3cba1903b64e2eeda0a2bd5b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 14 Feb 2021 16:54:34 +0100 Subject: [PATCH 40/45] Update CheckResultSet --- .ci/files/all-java.xml | 2 +- .../bestpractices/CheckResultSetRule.java | 83 +++++-------------- .../bestpractices/CheckResultSetTest.java | 1 - .../rule/bestpractices/xml/CheckResultSet.xml | 12 ++- 4 files changed, 32 insertions(+), 66 deletions(-) diff --git a/.ci/files/all-java.xml b/.ci/files/all-java.xml index e560dfc782..f07cb4da92 100644 --- a/.ci/files/all-java.xml +++ b/.ci/files/all-java.xml @@ -20,7 +20,7 @@ - + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetRule.java index fdc79937e0..9cb1e5ab74 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetRule.java @@ -4,90 +4,51 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; +import static net.sourceforge.pmd.util.CollectionUtil.setOf; + +import java.sql.ResultSet; import java.util.Set; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; +import net.sourceforge.pmd.lang.java.ast.ASTMethodCall; import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; -import net.sourceforge.pmd.lang.java.ast.ASTType; -import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; -import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.types.TypeTestUtil; /** * Rule that verifies, that the return values of next(), first(), last(), etc. * calls to a java.sql.ResultSet are actually verified. - * */ public class CheckResultSetRule extends AbstractJavaRule { - private Map resultSetVariables = new HashMap<>(); + private static final Set METHODS = setOf("next", "previous", "last", "first"); - private static Set methods = new HashSet<>(); - - static { - methods.add(".next"); - methods.add(".previous"); - methods.add(".last"); - methods.add(".first"); + @Override + public Object visit(ASTWhileStatement node, Object data) { + return data; } @Override - public Object visit(ASTMethodDeclaration node, Object data) { - resultSetVariables.clear(); - return super.visit(node, data); + public Object visit(ASTReturnStatement node, Object data) { + return data; } @Override - public Object visit(ASTLocalVariableDeclaration node, Object data) { - ASTClassOrInterfaceType type = null; - if (!node.isTypeInferred()) { - type = node.getFirstChildOfType(ASTType.class).getFirstDescendantOfType(ASTClassOrInterfaceType.class); - } - if (type != null && (type.getType() != null && "java.sql.ResultSet".equals(type.getType().getName()) - || "ResultSet".equals(type.getImage()))) { - ASTVariableDeclarator declarator = node.getFirstChildOfType(ASTVariableDeclarator.class); - if (declarator != null) { - ASTName name = declarator.getFirstDescendantOfType(ASTName.class); - if (type.getType() != null || name != null && name.getImage().endsWith("executeQuery")) { - ASTVariableDeclaratorId id = declarator.getFirstChildOfType(ASTVariableDeclaratorId.class); - resultSetVariables.put(id.getImage(), node); - } - } + public Object visit(ASTIfStatement node, Object data) { + return data; + } + + @Override + public Object visit(ASTMethodCall node, Object data) { + if (isResultSetMethod(node)) { + addViolation(data, node); } return super.visit(node, data); } - @Override - public Object visit(ASTName node, Object data) { - String image = node.getImage(); - String var = getResultSetVariableName(image); - if (var != null && resultSetVariables.containsKey(var) - && node.getFirstParentOfType(ASTIfStatement.class) == null - && node.getFirstParentOfType(ASTWhileStatement.class) == null - && node.getFirstParentOfType(ASTReturnStatement.class) == null) { - - addViolation(data, resultSetVariables.get(var)); - } - return super.visit(node, data); - } - - private String getResultSetVariableName(String image) { - if (image.contains(".")) { - for (String method : methods) { - if (image.endsWith(method)) { - return image.substring(0, image.lastIndexOf(method)); - } - } - } - return null; + private boolean isResultSetMethod(ASTMethodCall node) { + return METHODS.contains(node.getMethodName()) + && TypeTestUtil.isDeclaredInClass(ResultSet.class, node.getMethodType()); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetTest.java index 0b4109f74f..effe3630b2 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class CheckResultSetTest extends PmdRuleTst { // no additional unit tests } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/CheckResultSet.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/CheckResultSet.xml index 6b259ce064..2d4968401f 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/CheckResultSet.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/CheckResultSet.xml @@ -8,6 +8,7 @@ The result set is appropriately tested before using it, no violation. 0 This most common violation case, not testing is done before a call to 'last()'. 1 This most common violation case, not testing is done before a call to 'first()'. 1 Using a 'while' instead of 'if' shouldn't result in a violation. 0 #942 CheckResultSet False Positive 1 #1135 CheckResultSet ignores results set declared outside of try/catch (good case) 0 #1135 CheckResultSet ignores results set declared outside of try/catch 1 #1135 CheckResultSet ignores results set declared outside of try/catch - prevent false positive 0 stringList = new ArrayList(); From bfc833d5811453e7c73fa7b5b0f57d24dbafa5aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 14 Feb 2021 17:18:30 +0100 Subject: [PATCH 41/45] Update GuardLogStatement --- .ci/files/all-java.xml | 2 +- .../bestpractices/GuardLogStatementRule.java | 187 ++++++------------ .../bestpractices/GuardLogStatementTest.java | 1 - .../bestpractices/xml/GuardLogStatement.xml | 3 + 4 files changed, 66 insertions(+), 127 deletions(-) diff --git a/.ci/files/all-java.xml b/.ci/files/all-java.xml index f07cb4da92..36cc693f5a 100644 --- a/.ci/files/all-java.xml +++ b/.ci/files/all-java.xml @@ -26,7 +26,7 @@ - + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/GuardLogStatementRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/GuardLogStatementRule.java index 4ea5b89cc4..3b1f1df0a5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/GuardLogStatementRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/GuardLogStatementRule.java @@ -11,21 +11,22 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.logging.Level; + +import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.Rule; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression; -import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; +import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTExpression; +import net.sourceforge.pmd.lang.java.ast.ASTExpressionStatement; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; -import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; -import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; +import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression; +import net.sourceforge.pmd.lang.java.ast.ASTMethodCall; +import net.sourceforge.pmd.lang.java.ast.ASTStringLiteral; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.types.TypeTestUtil; import net.sourceforge.pmd.properties.PropertyDescriptor; /** @@ -101,120 +102,47 @@ public class GuardLogStatementRule extends AbstractJavaRule implements Rule { } @Override - public Object visit(ASTStatementExpression node, Object data) { - if (node.getNumChildren() < 1 || !(node.getChild(0) instanceof ASTPrimaryExpression)) { - // only consider primary expressions - return node; + public Object visit(ASTExpressionStatement node, Object data) { + ASTExpression expr = node.getExpr(); + if (!(expr instanceof ASTMethodCall)) { + return null; } - ASTPrimaryExpression primary = (ASTPrimaryExpression) node.getChild(0); - if (primary.getNumChildren() >= 2 && primary.getChild(0) instanceof ASTPrimaryPrefix) { - ASTPrimaryPrefix prefix = (ASTPrimaryPrefix) primary.getChild(0); - String methodCall = getMethodCallName(prefix); - String logLevel = getLogLevelName(primary, methodCall); - - if (guardStmtByLogLevel.containsKey(methodCall) && logLevel != null - && primary.getChild(1) instanceof ASTPrimarySuffix - && primary.getChild(1).hasDescendantOfType(ASTAdditiveExpression.class)) { - - if (!hasGuard(primary, methodCall, logLevel)) { - super.addViolation(data, node); - } + ASTMethodCall methodCall = (ASTMethodCall) expr; + String logLevel = getLogLevelName(methodCall); + if (logLevel != null && guardStmtByLogLevel.containsKey(logLevel)) { + if (!hasGuard(methodCall, logLevel)) { + addViolation(data, node); } } - return super.visit(node, data); + return null; } - private boolean hasGuard(ASTPrimaryExpression node, String methodCall, String logLevel) { - ASTIfStatement ifStatement = node.getFirstParentOfType(ASTIfStatement.class); + private boolean hasGuard(ASTMethodCall node, String logLevel) { + ASTIfStatement ifStatement = node.ancestors(ASTIfStatement.class).first(); if (ifStatement == null) { return false; } - // an if statement always has an expression - ASTExpression expr = ifStatement.getFirstChildOfType(ASTExpression.class); - List guardCalls = expr.findDescendantsOfType(ASTPrimaryPrefix.class); - if (guardCalls.isEmpty()) { - return false; - } + for (ASTMethodCall maybeAGuardCall : ifStatement.getCondition().descendantsOrSelf().filterIs(ASTMethodCall.class)) { + String guardMethodName = maybeAGuardCall.getMethodName(); + // the guard is adapted to the actual log statement - boolean foundGuard = false; - // check all conditions in the if expression - for (ASTPrimaryPrefix guardCall : guardCalls) { - if (guardCall.getNumChildren() < 1 - || guardCall.getChild(0).getImage() == null) { + if (!guardStmtByLogLevel.get(logLevel).contains(guardMethodName)) { continue; } - String guardMethodCall = getLastPartOfName(guardCall.getChild(0)); - boolean guardMethodCallMatches = guardStmtByLogLevel.get(methodCall).contains(guardMethodCall); - boolean hasArguments = guardCall.getParent().hasDescendantOfType(ASTArgumentList.class); - - if (guardMethodCallMatches && !JAVA_UTIL_LOG_GUARD_METHOD.equals(guardMethodCall)) { - // simple case: guard method without the need to check arguments found - foundGuard = true; - } else if (guardMethodCallMatches && hasArguments) { + if (JAVA_UTIL_LOG_GUARD_METHOD.equals(guardMethodName)) { // java.util.logging: guard method with argument. Verify the log level - String guardArgLogLevel = getLogLevelName(guardCall.getParent(), guardMethodCall); - foundGuard = logLevel.equals(guardArgLogLevel); - } - - if (foundGuard) { - break; - } - } - - return foundGuard; - } - - /** - * Extracts the method name of the method call. - * @param prefix the method call - * @return the name of the called method - */ - private String getMethodCallName(ASTPrimaryPrefix prefix) { - String result = ""; - if (prefix.getNumChildren() == 1 && prefix.getChild(0) instanceof ASTName) { - result = getLastPartOfName(prefix.getChild(0)); - } - return result; - } - - private String getLastPartOfName(Node name) { - String result = ""; - if (name != null) { - result = name.getImage(); - } - int dotIndex = result.lastIndexOf('.'); - if (dotIndex > -1 && result.length() > dotIndex + 1) { - result = result.substring(dotIndex + 1); - } - return result; - } - - /** - * Gets the first child, first grand child, ... of the given types. - * The children must follow the given order of types - * - * @param root the node from where to start the search - * @param childrenTypes the list of types - * @param should match the last type of childrenType, otherwise you'll get a ClassCastException - * @return the found child node or null - */ - @SafeVarargs - private static N getFirstChild(Node root, Class ... childrenTypes) { - Node current = root; - for (Class clazz : childrenTypes) { - Node child = current.getFirstChildOfType(clazz); - if (child != null) { - current = child; + if (logLevel.equals(getJutilLogLevelInFirstArg(maybeAGuardCall))) { + return true; + } } else { - return null; + return true; } + } - @SuppressWarnings("unchecked") - N result = (N) current; - return result; + return false; } /** @@ -222,32 +150,41 @@ public class GuardLogStatementRule extends AbstractJavaRule implements Rule { * itself or - in case java util logging is used, then it is the first argument of * the method call (if it exists). * - * @param node the method call - * @param methodCallName the called method name previously determined + * @param methodCall the method call + * * @return the log level or null if it could not be determined */ - private String getLogLevelName(Node node, String methodCallName) { - if (!JAVA_UTIL_LOG_METHOD.equals(methodCallName) && !JAVA_UTIL_LOG_GUARD_METHOD.equals(methodCallName)) { - return methodCallName; - } - - String logLevel = null; - ASTPrimarySuffix suffix = node.getFirstDescendantOfType(ASTPrimarySuffix.class); - if (suffix != null) { - ASTArgumentList argumentList = suffix.getFirstDescendantOfType(ASTArgumentList.class); - if (argumentList != null && argumentList.getNumChildren() > 0) { - // at least one argument - the log level. If the method call is "log", then a message might follow - ASTName name = GuardLogStatementRule.getFirstChild(argumentList.getChild(0), - ASTPrimaryExpression.class, ASTPrimaryPrefix.class, ASTName.class); - String lastPart = getLastPartOfName(name); - lastPart = lastPart.toLowerCase(Locale.ROOT); - if (!lastPart.isEmpty()) { - logLevel = lastPart; - } + private @Nullable String getLogLevelName(ASTMethodCall methodCall) { + String methodName = methodCall.getMethodName(); + if (!JAVA_UTIL_LOG_METHOD.equals(methodName) && !JAVA_UTIL_LOG_GUARD_METHOD.equals(methodName)) { + if (isUnguardedAccessOk(methodCall, 0)) { + return null; } + return methodName; // probably logger.warn(...) } - return logLevel; + // else it's java.util.logging, eg + // LOGGER.log(Level.FINE, "m") + if (isUnguardedAccessOk(methodCall, 1)) { + return null; + } + + return getJutilLogLevelInFirstArg(methodCall); + } + + private @Nullable String getJutilLogLevelInFirstArg(ASTMethodCall methodCall) { + ASTExpression firstArg = methodCall.getArguments().toStream().get(0); + if (TypeTestUtil.isA(Level.class, firstArg) && firstArg instanceof ASTNamedReferenceExpr) { + return ((ASTNamedReferenceExpr) firstArg).getName().toLowerCase(Locale.ROOT); + } + return null; + } + + private boolean isUnguardedAccessOk(ASTMethodCall call, int messageArgIndex) { + // return true if the statement has limited overhead even if unguarded, + // so that we can ignore it + ASTExpression messageArg = call.getArguments().toStream().get(messageArgIndex); + return messageArg instanceof ASTStringLiteral || messageArg instanceof ASTLambdaExpression; } private void extractProperties() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/GuardLogStatementTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/GuardLogStatementTest.java index c4ade9053a..02f1628b38 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/GuardLogStatementTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/GuardLogStatementTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class GuardLogStatementTest extends PmdRuleTst { // no additional unit tests } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/GuardLogStatement.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/GuardLogStatement.xml index 182d5d6d42..782c4ab244 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/GuardLogStatement.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/GuardLogStatement.xml @@ -45,6 +45,7 @@ public class Test { Guarded call - OK - java util 0 #370 rule not considering lambdas 0 Date: Sun, 14 Feb 2021 17:22:18 +0100 Subject: [PATCH 42/45] Update WhileLoopWithLiteralBoolean Do/while is now reported on the literal and not the 'do' keyword. This means violation lines will change. --- .ci/files/all-java.xml | 2 +- pmd-java/src/main/resources/category/java/bestpractices.xml | 2 +- .../rule/bestpractices/xml/WhileLoopWithLiteralBoolean.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.ci/files/all-java.xml b/.ci/files/all-java.xml index 36cc693f5a..10b84a5b46 100644 --- a/.ci/files/all-java.xml +++ b/.ci/files/all-java.xml @@ -61,7 +61,7 @@ - + diff --git a/pmd-java/src/main/resources/category/java/bestpractices.xml b/pmd-java/src/main/resources/category/java/bestpractices.xml index e4942c2e9b..bf5753c6b4 100644 --- a/pmd-java/src/main/resources/category/java/bestpractices.xml +++ b/pmd-java/src/main/resources/category/java/bestpractices.xml @@ -1799,7 +1799,7 @@ a block `{}` is sufficient. diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/WhileLoopWithLiteralBoolean.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/WhileLoopWithLiteralBoolean.xml index 15d29a5f46..41770e7cfd 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/WhileLoopWithLiteralBoolean.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/WhileLoopWithLiteralBoolean.xml @@ -7,7 +7,7 @@ do while true 1 - 3 + 4 do while false 1 - 3 + 4 Date: Sun, 14 Feb 2021 17:34:56 +0100 Subject: [PATCH 43/45] Update AbstractClassWithoutAbstractMethod --- .ci/files/all-java.xml | 2 +- ...bstractClassWithoutAbstractMethodRule.java | 28 ++++++------------- ...bstractClassWithoutAbstractMethodTest.java | 1 - .../AbstractClassWithoutAbstractMethod.xml | 2 +- 4 files changed, 10 insertions(+), 23 deletions(-) diff --git a/.ci/files/all-java.xml b/.ci/files/all-java.xml index 10b84a5b46..f944998e80 100644 --- a/.ci/files/all-java.xml +++ b/.ci/files/all-java.xml @@ -9,7 +9,7 @@ - + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodRule.java index e6f5f12208..0d0d8c33e4 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodRule.java @@ -5,45 +5,33 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; -import org.checkerframework.checker.nullness.qual.NonNull; - import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTExtendsList; -import net.sourceforge.pmd.lang.java.ast.ASTImplementsList; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.rule.RuleTargetSelector; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule; -public class AbstractClassWithoutAbstractMethodRule extends AbstractJavaRule { +public class AbstractClassWithoutAbstractMethodRule extends AbstractJavaRulechainRule { - @Override - protected @NonNull RuleTargetSelector buildTargetSelector() { - return RuleTargetSelector.forTypes(ASTClassOrInterfaceDeclaration.class); + public AbstractClassWithoutAbstractMethodRule() { + super(ASTClassOrInterfaceDeclaration.class); } @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - if (!node.isAbstract() || doesExtend(node) || doesImplement(node)) { + if (node.isInterface() || !node.isAbstract() || doesExtend(node) || doesImplement(node)) { return data; } - int countOfAbstractMethods = 0; - for (ASTMethodDeclaration methodDecl : node.descendants(ASTMethodDeclaration.class)) { - if (methodDecl.isAbstract()) { - countOfAbstractMethods++; - } - } - if (countOfAbstractMethods == 0) { + if (node.getDeclarations(ASTMethodDeclaration.class).none(ASTMethodDeclaration::isAbstract)) { addViolation(data, node); } return data; } private boolean doesExtend(ASTClassOrInterfaceDeclaration node) { - return node.getFirstChildOfType(ASTExtendsList.class) != null; + return node.getSuperClassTypeNode() != null; } private boolean doesImplement(ASTClassOrInterfaceDeclaration node) { - return node.getFirstChildOfType(ASTImplementsList.class) != null; + return !node.getSuperInterfaceTypeNodes().isEmpty(); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodTest.java index 16409a73f1..a7ff179f29 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodTest.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices; import net.sourceforge.pmd.testframework.PmdRuleTst; -@org.junit.Ignore("Rule has not been updated yet") public class AbstractClassWithoutAbstractMethodTest extends PmdRuleTst { // no additional unit tests } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AbstractClassWithoutAbstractMethod.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AbstractClassWithoutAbstractMethod.xml index 2efff27060..386251c7af 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AbstractClassWithoutAbstractMethod.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AbstractClassWithoutAbstractMethod.xml @@ -44,7 +44,7 @@ public abstract class Foo { abstract class implements interface 0 From 72b80cef70f1c165b7e95addf743f33ac1a90509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 19 Feb 2021 17:01:14 +0100 Subject: [PATCH 44/45] Activate InefficientStringBuffering test --- .ci/files/all-java.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/files/all-java.xml b/.ci/files/all-java.xml index 37cda67720..2b2ca03950 100644 --- a/.ci/files/all-java.xml +++ b/.ci/files/all-java.xml @@ -298,7 +298,7 @@ - + From b3fad369417cba9a057eec1d18a9779312545be5 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 19 Feb 2021 18:52:25 +0100 Subject: [PATCH 45/45] [doc] Update release notes --- docs/pages/7_0_0_release_notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/pages/7_0_0_release_notes.md b/docs/pages/7_0_0_release_notes.md index c952dc27c5..dc5cc565e6 100644 --- a/docs/pages/7_0_0_release_notes.md +++ b/docs/pages/7_0_0_release_notes.md @@ -122,18 +122,22 @@ The following previously deprecated rules have been finally removed: * java-bestpractices * [#342](https://github.com/pmd/pmd/issues/342): \[java] AccessorMethodGeneration: Name clash with another public field not properly handled * [#755](https://github.com/pmd/pmd/issues/755): \[java] AccessorClassGeneration false positive for private constructors + * [#770](https://github.com/pmd/pmd/issues/770): \[java] UnusedPrivateMethod yields false positive for counter-variant arguments * [#807](https://github.com/pmd/pmd/issues/807): \[java] AccessorMethodGeneration false positive with overloads + * [#1189](https://github.com/pmd/pmd/issues/1189): \[java] UnusedPrivateMethod false positive from inner class via external class * [#1212](https://github.com/pmd/pmd/issues/1212): \[java] Don't raise JUnitTestContainsTooManyAsserts on JUnit 5's assertAll * [#1422](https://github.com/pmd/pmd/issues/1422): \[java] JUnitTestsShouldIncludeAssert false positive with inherited @Rule field * [#1565](https://github.com/pmd/pmd/issues/1565): \[java] JUnitAssertionsShouldIncludeMessage false positive with AssertJ * [#1969](https://github.com/pmd/pmd/issues/1969): \[java] MissingOverride false-positive triggered by package-private method overwritten in another package by extending class * [#1998](https://github.com/pmd/pmd/issues/1998): \[java] AccessorClassGeneration false-negative: subclass calls private constructor + * [#2130](https://github.com/pmd/pmd/issues/2130): \[java] UnusedLocalVariable: false-negative with array * [#2147](https://github.com/pmd/pmd/issues/2147): \[java] JUnitTestsShouldIncludeAssert - false positives with lambdas and static methods * [#2542](https://github.com/pmd/pmd/issues/2542): \[java] UseCollectionIsEmpty can not detect the case `foo.bar().size()` * [#2796](https://github.com/pmd/pmd/issue/2796): \[java] UnusedAssignment false positive with call chains * [#2797](https://github.com/pmd/pmd/issues/2797): \[java] MissingOverride long-standing issues * [#2806](https://github.com/pmd/pmd/issues/2806): \[java] SwitchStmtsShouldHaveDefault false-positive with Java 14 switch non-fallthrough branches * [#2883](https://github.com/pmd/pmd/issues/2883): \[java] JUnitAssertionsShouldIncludeMessage false positive with method call + * [#2890](https://github.com/pmd/pmd/issues/2890): \[java] UnusedPrivateMethod false positive with generics * java-codestyle * [#1673](https://github.com/pmd/pmd/issues/1673): \[java] UselessParentheses false positive with conditional operator * [#1790](https://github.com/pmd/pmd/issues/1790): \[java] UnnecessaryFullyQualifiedName false positive with enum constant