From 230b041c28ae7f719e3141cb9de3985c1b2d9e85 Mon Sep 17 00:00:00 2001 From: xioayuge <45328272+xioayuge@users.noreply.github.com> Date: Sun, 31 May 2020 19:48:58 +0800 Subject: [PATCH 01/99] Update LawOfDemeterRule.java --- .../pmd/lang/java/rule/design/LawOfDemeterRule.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterRule.java index 2d91046194..8447b26dfb 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterRule.java @@ -146,7 +146,9 @@ public class LawOfDemeterRule extends AbstractJavaRule { if (firstMethodCallInChain.isNotBuilder()) { List suffixes = findSuffixesWithoutArguments(expression); for (ASTPrimarySuffix suffix : suffixes) { - result.add(new MethodCall(expression, suffix)); + if (!expression.hasDescendantOfType(ASTCastExpression.class)) { + result.add(new MethodCall(expression, suffix)); + } } } } @@ -175,7 +177,7 @@ public class LawOfDemeterRule extends AbstractJavaRule { private static List findSuffixesWithoutArguments(ASTPrimaryExpression expr) { List result = new ArrayList<>(); if (hasRealPrefix(expr)) { - List suffixes = expr.findDescendantsOfType(ASTPrimarySuffix.class); + List suffixes = expr.findChildrenOfType(ASTPrimarySuffix.class); for (ASTPrimarySuffix suffix : suffixes) { if (!suffix.isArguments()) { result.add(suffix); From 6068f57e28d4098c8f87361cd50d8d108dcad115 Mon Sep 17 00:00:00 2001 From: Artem Krosheninnikov Date: Sat, 13 Jun 2020 21:23:25 +0300 Subject: [PATCH 02/99] Update jcommander, logback-classic and snakeyaml --- pmd-apex-jorje/pom.xml | 6 +++--- pmd-doc/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pmd-apex-jorje/pom.xml b/pmd-apex-jorje/pom.xml index 0e384db63b..ebd8bd644f 100644 --- a/pmd-apex-jorje/pom.xml +++ b/pmd-apex-jorje/pom.xml @@ -54,12 +54,12 @@ ch.qos.logback logback-classic - 1.1.7 + 1.2.3 ch.qos.logback logback-core - 1.1.7 + 1.2.3 com.google.code.findbugs @@ -119,7 +119,7 @@ org.yaml snakeyaml - 1.17 + 1.26 aopalliance diff --git a/pmd-doc/pom.xml b/pmd-doc/pom.xml index 1b4b2dc7c5..4903ec018f 100644 --- a/pmd-doc/pom.xml +++ b/pmd-doc/pom.xml @@ -100,7 +100,7 @@ org.yaml snakeyaml - 1.19 + 1.26 diff --git a/pom.xml b/pom.xml index 062f361a90..3fb02bb15e 100644 --- a/pom.xml +++ b/pom.xml @@ -626,7 +626,7 @@ com.beust jcommander - 1.72 + 1.78 org.ow2.asm From 1986ae65daee73516d7897b039fb86a11e2ec9b1 Mon Sep 17 00:00:00 2001 From: Artem Krosheninnikov Date: Sun, 14 Jun 2020 12:59:36 +0300 Subject: [PATCH 03/99] revert jcommander as after 1.72 some methods were removed --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3fb02bb15e..062f361a90 100644 --- a/pom.xml +++ b/pom.xml @@ -626,7 +626,7 @@ com.beust jcommander - 1.78 + 1.72 org.ow2.asm From 2b882e8655f12d9233d518760bbdeb3bd6fa64fe Mon Sep 17 00:00:00 2001 From: Artem Krosheninnikov Date: Thu, 18 Jun 2020 00:48:57 +0300 Subject: [PATCH 04/99] fix issue #2594, update exec-maven-plugin everywhere --- .travis/build-coveralls.sh | 2 +- .travis/build-deploy.sh | 2 +- .travis/build-doc.sh | 2 +- .travis/build-publish.sh | 2 +- .travis/build-sonar.sh | 2 +- do-release.sh | 2 +- pmd-apex/pom.xml | 2 +- pmd-doc/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis/build-coveralls.sh b/.travis/build-coveralls.sh index b58629a5fa..4f2fcafb3c 100755 --- a/.travis/build-coveralls.sh +++ b/.travis/build-coveralls.sh @@ -4,7 +4,7 @@ set -e source .travis/logger.sh source .travis/common-functions.sh -VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec) +VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) log_info "Building PMD Coveralls.io report ${VERSION} on branch ${TRAVIS_BRANCH}" if ! travis_isPush; then diff --git a/.travis/build-deploy.sh b/.travis/build-deploy.sh index 18dc1c824a..75a03e0484 100755 --- a/.travis/build-deploy.sh +++ b/.travis/build-deploy.sh @@ -7,7 +7,7 @@ source .travis/github-releases-api.sh source .travis/sourceforge-api.sh source .travis/regression-tester.sh -VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec) +VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) log_info "Building PMD ${VERSION} on branch ${TRAVIS_BRANCH}" MVN_BUILD_FLAGS="-B -V" diff --git a/.travis/build-doc.sh b/.travis/build-doc.sh index 1d81fd2bff..2431635127 100755 --- a/.travis/build-doc.sh +++ b/.travis/build-doc.sh @@ -8,7 +8,7 @@ source .travis/sourceforge-api.sh source .travis/pmd-code-api.sh function main() { - VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec) + VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) log_info "Building PMD Documentation ${VERSION} on branch ${TRAVIS_BRANCH}" # diff --git a/.travis/build-publish.sh b/.travis/build-publish.sh index e93f47a270..6b25635bb2 100644 --- a/.travis/build-publish.sh +++ b/.travis/build-publish.sh @@ -6,7 +6,7 @@ source .travis/common-functions.sh source .travis/github-releases-api.sh source .travis/sourceforge-api.sh -VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec) +VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) log_info "PMD Release ${VERSION}" if ! travis_isPush; then diff --git a/.travis/build-sonar.sh b/.travis/build-sonar.sh index 7e51aaf46d..71d2314e2f 100755 --- a/.travis/build-sonar.sh +++ b/.travis/build-sonar.sh @@ -4,7 +4,7 @@ set -e source .travis/logger.sh source .travis/common-functions.sh -VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec) +VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) log_info "Building PMD Sonar ${VERSION} on branch ${TRAVIS_BRANCH}" if ! travis_isPush; then diff --git a/do-release.sh b/do-release.sh index 128b65b129..25d5785ed3 100755 --- a/do-release.sh +++ b/do-release.sh @@ -25,7 +25,7 @@ echo "Releasing PMD" echo "-------------------------------------------" # see also https://gist.github.com/pdunnavant/4743895 -CURRENT_VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec) +CURRENT_VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) RELEASE_VERSION=${CURRENT_VERSION%-SNAPSHOT} MAJOR=$(echo $RELEASE_VERSION | cut -d . -f 1) MINOR=$(echo $RELEASE_VERSION | cut -d . -f 2) diff --git a/pmd-apex/pom.xml b/pmd-apex/pom.xml index 0465942f81..8800163eb6 100644 --- a/pmd-apex/pom.xml +++ b/pmd-apex/pom.xml @@ -105,7 +105,7 @@ org.codehaus.mojo exec-maven-plugin - 1.4.0 + 3.0.0 net.sourceforge.pmd.util.designer.Designer true diff --git a/pmd-doc/pom.xml b/pmd-doc/pom.xml index 1b4b2dc7c5..8e953090a2 100644 --- a/pmd-doc/pom.xml +++ b/pmd-doc/pom.xml @@ -24,7 +24,7 @@ org.codehaus.mojo exec-maven-plugin - 1.6.0 + 3.0.0 generate-rule-docs From 5b0c20a93f6aeed0a3f88cc6c19bd9b22ef9925c Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Sat, 20 Jun 2020 19:49:46 +0200 Subject: [PATCH 05/99] [apex] Support top-level enums in rules --- .../apex/multifile/ApexMultifileVisitor.java | 7 ++ .../pmd/lang/apex/rule/AbstractApexRule.java | 5 ++ .../lang/apex/rule/AbstractApexRuleTest.java | 86 +++++++++++++++++++ .../codestyle/xml/ClassNamingConventions.xml | 37 ++++++++ 4 files changed, 135 insertions(+) create mode 100644 pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRuleTest.java diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileVisitor.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileVisitor.java index 66d796d408..e24af12287 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileVisitor.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileVisitor.java @@ -8,6 +8,7 @@ import java.util.Stack; import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; +import net.sourceforge.pmd.lang.apex.ast.ASTUserEnum; import net.sourceforge.pmd.lang.apex.ast.ASTUserTrigger; import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorReducedAdapter; @@ -41,6 +42,12 @@ public class ApexMultifileVisitor extends ApexParserVisitorReducedAdapter { } + @Override + public Object visit(ASTUserEnum node, Object data) { + return data; // ignore + } + + @Override public Object visit(ASTMethod node, Object data) { stack.peek().addOperation(node.getQualifiedName().getOperation(), node.getSignature()); diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRule.java index 8f1a29b57d..7b69025111 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRule.java @@ -137,12 +137,17 @@ public abstract class AbstractApexRule extends AbstractRule protected void visitAll(List nodes, RuleContext ctx) { for (Object element : nodes) { + // all nodes of type ApexRootNode... if (element instanceof ASTUserClass) { visit((ASTUserClass) element, ctx); } else if (element instanceof ASTUserInterface) { visit((ASTUserInterface) element, ctx); } else if (element instanceof ASTUserTrigger) { visit((ASTUserTrigger) element, ctx); + } else if (element instanceof ASTUserEnum) { + visit((ASTUserEnum) element, ctx); + } else if (element instanceof ASTAnonymousClass) { + visit((ASTAnonymousClass) element, ctx); } } } diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRuleTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRuleTest.java new file mode 100644 index 0000000000..896f647c18 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRuleTest.java @@ -0,0 +1,86 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule; + +import static org.junit.Assert.assertEquals; + +import java.util.Collections; + +import org.junit.Test; + +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.apex.ast.ASTAnonymousClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserEnum; +import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; +import net.sourceforge.pmd.lang.apex.ast.ASTUserTrigger; +import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.ast.ApexParserTestBase; + +import apex.jorje.semantic.ast.compilation.Compilation; + +public class AbstractApexRuleTest extends ApexParserTestBase { + + @Test + public void shouldVisitTopLevelClass() { + run("class Foo { }"); + } + + @Test + public void shouldVisitTopLevelInterface() { + run("interface Foo { }"); + } + + @Test + public void shouldVisitTopLevelTrigger() { + run("trigger Foo on Account (before insert, before update) { }"); + } + + @Test + public void shouldVisitTopLevelEnum() { + run("enum Foo { }"); + } + + private void run(String code) { + ApexNode node = parse(code); + RuleContext ctx = new RuleContext(); + ctx.setLanguageVersion(apex.getDefaultVersion()); + TopLevelRule rule = new TopLevelRule(); + rule.apply(Collections.singletonList(node), ctx); + assertEquals(1, ctx.getReport().size()); + } + + private static class TopLevelRule extends AbstractApexRule { + @Override + public Object visit(ASTUserClass node, Object data) { + addViolation(data, node); + return data; + } + + @Override + public Object visit(ASTUserInterface node, Object data) { + addViolation(data, node); + return data; + } + + @Override + public Object visit(ASTUserTrigger node, Object data) { + addViolation(data, node); + return data; + } + + @Override + public Object visit(ASTUserEnum node, Object data) { + addViolation(data, node); + return data; + } + + @Override + public Object visit(ASTAnonymousClass node, Object data) { + addViolation(data, node); + return data; + } + } +} diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ClassNamingConventions.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ClassNamingConventions.xml index c5095ffec8..7eeac5de01 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ClassNamingConventions.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ClassNamingConventions.xml @@ -63,6 +63,14 @@ public class Foo { ]]> + + top-level enum all is well + 0 + + + test class default is title case 1 @@ -121,6 +129,17 @@ public class Foo { ]]> + + top-level enum default is title case + 1 + + The enum name 'fooEnum' doesn't match '[A-Z][a-zA-Z0-9_]*' + + + + custom test class pattern [a-zA-Z0-9_]+ @@ -157,4 +176,22 @@ public class FOO_CLASS { } public interface FOO_INTERFACE { } ]]> + + + custom enum pattern + E[a-zA-Z0-9_]+ + 0 + + + + + custom enum pattern + E[a-zA-Z0-9_]+ + 1 + + From 9810cdd654ecd2237666ccea0e3b5547bf6c1614 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Sat, 20 Jun 2020 19:54:54 +0200 Subject: [PATCH 06/99] Fix StdCyclomaticComplexity: enums should not be considered In Apex enums, there can't be any custom methods. --- .../lang/apex/rule/design/StdCyclomaticComplexityRule.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/StdCyclomaticComplexityRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/StdCyclomaticComplexityRule.java index cd9bad69c8..a70fc7f626 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/StdCyclomaticComplexityRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/StdCyclomaticComplexityRule.java @@ -129,13 +129,6 @@ public class StdCyclomaticComplexityRule extends AbstractApexRule { @Override public Object visit(ASTUserEnum node, Object data) { - entryStack.push(new Entry()); - super.visit(node, data); - Entry classEntry = entryStack.pop(); - if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { - addViolation(data, node, new String[] { "class", node.getImage(), - classEntry.getComplexityAverage() + "(Highest = " + classEntry.highestDecisionPoints + ')', }); - } return data; } From dd0532f09504cfab4a101811bd132df58759d77a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 20 Jun 2020 01:01:41 +0200 Subject: [PATCH 07/99] Add UnusedAssignment to replace data flow rule --- .../rule/errorprone/UnusedAssignmentRule.java | 235 +++++++++++ .../resources/category/java/errorprone.xml | 19 + .../rule/errorprone/UnusedAssignmentTest.java | 11 + .../rule/errorprone/xml/UnusedAssignment.xml | 375 ++++++++++++++++++ 4 files changed, 640 insertions(+) create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java create mode 100644 pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentTest.java create mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java new file mode 100644 index 0000000000..6765027c6c --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -0,0 +1,235 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + + +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 net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; +import net.sourceforge.pmd.lang.java.ast.ASTBlock; +import net.sourceforge.pmd.lang.java.ast.ASTExpression; +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.ASTPrimaryPrefix; +import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; +import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; +import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; +import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.rule.errorprone.UnusedAssignmentRule.LivenessVisitor.ScopeData; +import net.sourceforge.pmd.lang.java.rule.errorprone.UnusedAssignmentRule.LivenessVisitor.ScopeData.AssignmentEntry; +import net.sourceforge.pmd.lang.java.symboltable.ClassScope; +import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; +import net.sourceforge.pmd.lang.symboltable.Scope; + +public class UnusedAssignmentRule extends AbstractJavaRule { + + + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + + ScopeData bodyData = new ScopeData(); + + // for (ASTFormalParameter param : node.getFormalParameters()) { + // bodyData.varsThatMustBeUsed.add(param.getVariableDeclaratorId().getNameDeclaration()); + // } + + ScopeData endData = (ScopeData) node.getBody().jjtAccept(new LivenessVisitor(), bodyData); + + if (endData.usedAssignments.size() < endData.allAssignments.size()) { + HashSet unused = new HashSet<>(endData.allAssignments); + unused.removeAll(endData.usedAssignments); + // allAssignments is the unused assignments now + + for (AssignmentEntry entry : unused) { + addViolationWithMessage(data, entry.rhs, "The value assigned to variable ''{0}'' is never used", new Object[] {entry.var.getImage()}); + } + } + // if (!endData.varsThatMustBeUsed.isEmpty()) { + // HashSet assignedVars = new HashSet<>(); + // for (AssignmentEntry assignment : endData.allAssignments) { + // assignedVars.add(assignment.var); + // } + + // for (VariableNameDeclaration var : endData.varsThatMustBeUsed) { + // if (assignedVars.contains(var)) { + // addViolationWithMessage(data, var.getNode(), "The variable ''{0}'' is assigned, but never accessed", new Object[] {var.getImage()}); + // } else { + // addViolationWithMessage(data, var.getNode(), "The variable ''{0}'' is never used", new Object[] {var.getImage()}); + // } + // } + // } + + return super.visit(node, data); + } + + static class LivenessVisitor extends JavaParserVisitorAdapter { + + void bar(int i) { + int j = 0; + int z = 0; + if (i < 10) { + j = i; + } + } + + static class ScopeData { + + final Set varsThatMustBeUsed = new HashSet<>(); + + final Set allAssignments = new HashSet<>(); + final Set usedAssignments = new HashSet<>(); + + final Map> liveAssignments = new HashMap<>(); + + static class AssignmentEntry { + + final VariableNameDeclaration var; + final JavaNode rhs; + + AssignmentEntry(VariableNameDeclaration var, JavaNode rhs) { + this.var = var; + this.rhs = rhs; + } + } + + public void assign(VariableNameDeclaration var, JavaNode rhs) { + AssignmentEntry entry = new AssignmentEntry(var, rhs); + liveAssignments.put(var, Collections.singletonList(entry)); // kills the previous value + allAssignments.add(entry); + } + + public void use(VariableNameDeclaration var) { + List live = liveAssignments.get(var); + // may be null for implicit assignments, like method parameter + if (live != null) { + usedAssignments.addAll(live); + } + } + } + + + @Override + public Object visit(ASTBlock node, Object data) { + + for (JavaNode child : node.children()) { + // each statement's output is passed as input to the next + data = child.jjtAccept(this, data); + } + + return data; + } + + @Override + public Object visit(ASTVariableDeclarator node, Object data) { + VariableNameDeclaration var = node.getVariableId().getNameDeclaration(); + ASTVariableInitializer rhs = node.getInitializer(); + if (rhs != null) { + rhs.jjtAccept(this, data); + ((ScopeData) data).assign(var, rhs); + } + return data; + } + + @Override + public Object visit(ASTExpression node, Object data) { + return checkAssignment(node, data); + } + + @Override + public Object visit(ASTStatementExpression node, Object data) { + return checkAssignment(node, data); + } + + public Object checkAssignment(JavaNode node, Object data) { + if (node.getNumChildren() == 3) { + // assignment + assert node.getChild(1) instanceof ASTAssignmentOperator; + + // visit the rhs as it is evaluated before + ASTExpression rhs = (ASTExpression) node.getChild(2); + rhs.jjtAccept(this, data); + + VariableNameDeclaration lhsVar = getLhsVar(node.getChild(0), true); + if (lhsVar != null) { + ((ScopeData) data).assign(lhsVar, rhs); + } else { + node.getChild(0).jjtAccept(this, data); + } + return data; + } else { + return super.visit(node, data); + } + } + + @Override + public Object visit(ASTPrimaryExpression node, Object data) { + super.visit(node, data); // visit subexpressions + + VariableNameDeclaration var = getLhsVar(node, false); + if (var != null) { + ((ScopeData) data).use(var); + } + return data; + } + + private VariableNameDeclaration getLhsVar(JavaNode primary, boolean inLhs) { + if (primary instanceof ASTPrimaryExpression) { + ASTPrimaryPrefix prefix = (ASTPrimaryPrefix) primary.getChild(0); + + if (prefix.usesThisModifier()) { + if (primary.getNumChildren() != 2 && inLhs) { + return null; + } + ASTPrimarySuffix suffix = (ASTPrimarySuffix) primary.getChild(1); + if (suffix.isArguments() || suffix.isArrayDereference()) { + return null; + } + return findVar(primary.getScope(), true, suffix.getImage()); + } else { + if (inLhs && primary.getNumChildren() > 1) { + return null; + } + + if (prefix.getChild(0) instanceof ASTName) { + return findVar(prefix.getScope(), false, prefix.getChild(0).getImage()); + } + } + } + + return null; + } + + private VariableNameDeclaration findVar(Scope scope, boolean isThis, String name) { + if (name == null) { + return null; + } + if (isThis) { + scope = scope.getEnclosingScope(ClassScope.class); + } + + while (scope != null) { + for (VariableNameDeclaration decl : scope.getDeclarations(VariableNameDeclaration.class).keySet()) { + if (decl.getImage().equals(name)) { + return decl; + } + } + + scope = scope.getParent(); + } + + return null; + } + } + +} diff --git a/pmd-java/src/main/resources/category/java/errorprone.xml b/pmd-java/src/main/resources/category/java/errorprone.xml index 8033820e7f..fb0559eef5 100644 --- a/pmd-java/src/main/resources/category/java/errorprone.xml +++ b/pmd-java/src/main/resources/category/java/errorprone.xml @@ -3276,6 +3276,25 @@ public String convert(int x) { + + + + TODO + + 3 + + + + + + + + + + + + + + + + + + + + + + + + DD anomaly + 1 + + + + + DU anomaly + 1 + + + + + UR anomaly + 0 + + + + + more komplex anomalysis + 4 + + + + + #1393 PMD hanging during DataflowAnomalyAnalysis + + 3 + + + + + #408 Assert statements causing + 0 + + + + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 1. DU-Anomaly(b) + 1 + 6 + + Found 'DU'-anomaly for variable 'b' (lines '6'-'7'). + + + + + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 2. DU-Anomaly(a) + 2 + 3,5 + + Found 'DU'-anomaly for variable 'a' (lines '3'-'7'). + Found 'DU'-anomaly for variable 'a' (lines '5'-'7'). + + + + + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 3. DU-Anomaly(a) + 1 + 5 + + Found 'DU'-anomaly for variable 'a' (lines '5'-'7'). + + + + + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 4. DU-Anomaly(a) + 2 + 3,6 + + Found 'DU'-anomaly for variable 'a' (lines '3'-'9'). + Found 'DU'-anomaly for variable 'a' (lines '6'-'9'). + + + + + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 5. DU-Anomaly(a) + 1 + 6 + + Found 'DU'-anomaly for variable 'a' (lines '6'-'9'). + + + + + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 6. DU-Anomaly(a) + 4 + 6,8,10,12 + + Found 'DU'-anomaly for variable 'a' (lines '6'-'14'). + Found 'DU'-anomaly for variable 'a' (lines '8'-'14'). + Found 'DU'-anomaly for variable 'a' (lines '10'-'14'). + Found 'DU'-anomaly for variable 'a' (lines '12'-'14'). + + + + + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 7. DU-Anomaly(a) + 1 + 9 + + Found 'DU'-anomaly for variable 'a' (lines '9'-'11'). + + + + + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 9. DU-Anomaly(t1) + 1 + 5 + + Found 'DU'-anomaly for variable 't1' (lines '5'-'6'). + + + + + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 12. DU-Anomaly(t1) + 1 + 6 + + Found 'DU'-anomaly for variable 't1' (lines '6'-'7'). + + + + + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 13 + 0 + + + + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 14. DU-Anomaly(t1, t2) + 2 + 4,6 + + Found 'DU'-anomaly for variable 't2' (lines '4'-'7'). + Found 'DU'-anomaly for variable 't1' (lines '6'-'7'). + + + + + + #1749 DD False Positive in DataflowAnomalyAnalysis + 1 + 4 + + Found 'DU'-anomaly for variable 'a' (lines '4'-'5'). + + + + From e8bd8c1d7179e1aa1ad15632ffd8af9cc27408f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 20 Jun 2020 01:36:52 +0200 Subject: [PATCH 08/99] Fix conditional flow --- .../rule/errorprone/UnusedAssignmentRule.java | 185 +++++++++++++----- .../rule/errorprone/xml/UnusedAssignment.xml | 124 ++++++++++-- 2 files changed, 252 insertions(+), 57 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 6765027c6c..10ce9a41c6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -5,29 +5,30 @@ package net.sourceforge.pmd.lang.java.rule.errorprone; +import java.util.ArrayDeque; import java.util.Collections; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; -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.ASTMethodDeclaration; 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.ASTReturnStatement; import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; +import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.rule.errorprone.UnusedAssignmentRule.LivenessVisitor.ScopeData; -import net.sourceforge.pmd.lang.java.rule.errorprone.UnusedAssignmentRule.LivenessVisitor.ScopeData.AssignmentEntry; import net.sourceforge.pmd.lang.java.symboltable.ClassScope; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; import net.sourceforge.pmd.lang.symboltable.Scope; @@ -75,61 +76,53 @@ public class UnusedAssignmentRule extends AbstractJavaRule { static class LivenessVisitor extends JavaParserVisitorAdapter { - void bar(int i) { - int j = 0; - int z = 0; - if (i < 10) { - j = i; - } - } - static class ScopeData { + private final Deque breakAddresses = new ArrayDeque<>(); + private final Map namedBreaks = new HashMap<>(); - final Set varsThatMustBeUsed = new HashSet<>(); - - final Set allAssignments = new HashSet<>(); - final Set usedAssignments = new HashSet<>(); - - final Map> liveAssignments = new HashMap<>(); - - static class AssignmentEntry { - - final VariableNameDeclaration var; - final JavaNode rhs; - - AssignmentEntry(VariableNameDeclaration var, JavaNode rhs) { - this.var = var; - this.rhs = rhs; - } - } - - public void assign(VariableNameDeclaration var, JavaNode rhs) { - AssignmentEntry entry = new AssignmentEntry(var, rhs); - liveAssignments.put(var, Collections.singletonList(entry)); // kills the previous value - allAssignments.add(entry); - } - - public void use(VariableNameDeclaration var) { - List live = liveAssignments.get(var); - // may be null for implicit assignments, like method parameter - if (live != null) { - usedAssignments.addAll(live); - } - } - } + // following deals with control flow @Override - public Object visit(ASTBlock node, Object data) { + public Object visit(JavaNode node, Object data) { for (JavaNode child : node.children()) { - // each statement's output is passed as input to the next + // each output is passed as input to the next (most relevant for blocks) data = child.jjtAccept(this, data); } return data; } + @Override + public Object visit(ASTIfStatement node, Object data) { + ScopeData before = (ScopeData) node.getCondition().jjtAccept(this, data); + + ScopeData thenData = before.fork(); + thenData = (ScopeData) node.getThenBranch().jjtAccept(this, thenData); + if (node.hasElse()) { + ScopeData elseData = (ScopeData) node.getElseBranch().jjtAccept(this, before.fork()); + return thenData.join(elseData); + } else { + return before.join(thenData); + } + } + + @Override + public Object visit(ASTThrowStatement node, Object data) { + data = super.visit(node, data); + return ((ScopeData) data).abruptCompletion(); + } + + @Override + public Object visit(ASTReturnStatement node, Object data) { + data = super.visit(node, data); + return ((ScopeData) data).abruptCompletion(); + } + + // following deals with assignment + + @Override public Object visit(ASTVariableDeclarator node, Object data) { VariableNameDeclaration var = node.getVariableId().getNameDeclaration(); @@ -141,6 +134,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return data; } + @Override public Object visit(ASTExpression node, Object data) { return checkAssignment(node, data); @@ -172,6 +166,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } + // variable usage + @Override public Object visit(ASTPrimaryExpression node, Object data) { super.visit(node, data); // visit subexpressions @@ -232,4 +228,101 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } + private static class ScopeData { + + // final Set varsThatMustBeUsed = new HashSet<>(); + + final Set allAssignments; + final Set usedAssignments; + + final Map> liveAssignments; + + private ScopeData() { + this(new HashSet(), new HashSet(), new HashMap>()); + } + + private ScopeData(Set allAssignments, + Set usedAssignments, + Map> liveAssignments) { + this.allAssignments = allAssignments; + this.usedAssignments = usedAssignments; + this.liveAssignments = liveAssignments; + } + + void assign(VariableNameDeclaration var, JavaNode rhs) { + AssignmentEntry entry = new AssignmentEntry(var, rhs); + liveAssignments.put(var, Collections.singleton(entry)); // kills the previous value + allAssignments.add(entry); + } + + void use(VariableNameDeclaration var) { + Set live = liveAssignments.get(var); + // may be null for implicit assignments, like method parameter + if (live != null) { + usedAssignments.addAll(live); + } + } + + ScopeData fork() { + return new ScopeData(this.allAssignments, this.usedAssignments, new HashMap<>(this.liveAssignments)); + } + + ScopeData abruptCompletion() { + this.liveAssignments.clear(); + return this; + } + + ScopeData join(ScopeData sub) { + // Merge live assignments of forked scopes + if (sub != this) { + for (VariableNameDeclaration var : this.liveAssignments.keySet()) { + Set myAssignments = this.liveAssignments.get(var); + Set subScopeAssignments = sub.liveAssignments.get(var); + if (subScopeAssignments == null) { + this.liveAssignments.put(var, myAssignments); + continue; + } + joinLive(var, myAssignments, subScopeAssignments); + } + + for (VariableNameDeclaration var : sub.liveAssignments.keySet()) { + Set subScopeAssignments = sub.liveAssignments.get(var); + Set myAssignments = this.liveAssignments.get(var); + if (myAssignments == null) { + this.liveAssignments.put(var, subScopeAssignments); + continue; + } + joinLive(var, myAssignments, subScopeAssignments); + } + + } + + return this; + } + + private void joinLive(VariableNameDeclaration var, Set live1, Set live2) { + Set newLive = new HashSet<>(live1.size() + live2.size()); + newLive.addAll(live2); + newLive.addAll(live1); + this.liveAssignments.put(var, newLive); + } + + ScopeData join(Iterable scopes) { + for (ScopeData sub : scopes) { + this.join(sub); + } + return this; + } + } + + static class AssignmentEntry { + + final VariableNameDeclaration var; + final JavaNode rhs; + + AssignmentEntry(VariableNameDeclaration var, JavaNode rhs) { + this.var = var; + this.rhs = rhs; + } + } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 4b62dc8493..f4ec0ded8e 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -21,6 +21,10 @@ DD anomaly 1 + 3 + + The value assigned to variable 'i' is never used + - more komplex anomalysis - 4 + Conditional flow 0 + 3 + 3,4,6 + + The value assigned to variable 'j' is never used + The value assigned to variable 'z' is never used + The value assigned to variable 'j' is never used + + + Conditional flow 1 + 1 + 4 + + The value assigned to variable 'z' is never used + + + + + + Conditional flow 2 + 1 + 3 + + The value assigned to variable 'j' is never used + + + + + + Conditional flow with abrupt throw + 2 + 3,6 + + The value assigned to variable 'j' is never used + The value assigned to variable 'j' is never used + + + + + + Conditional flow with abrupt return + 2 + 3,6 + + The value assigned to variable 'j' is never used + The value assigned to variable 'j' is never used + + + + #1393 PMD hanging during DataflowAnomalyAnalysis @@ -136,7 +240,7 @@ public class AssertTest { 1 6 - Found 'DU'-anomaly for variable 'b' (lines '6'-'7'). + The value assigned to variable 'b' is never used #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 2. DU-Anomaly(a) - 2 - 3,5 + 1 + 5 - Found 'DU'-anomaly for variable 'a' (lines '3'-'7'). - Found 'DU'-anomaly for variable 'a' (lines '5'-'7'). + The value assigned to variable 'a' is never used #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 4. DU-Anomaly(a) - 2 - 3,6 + 1 + 6 - Found 'DU'-anomaly for variable 'a' (lines '3'-'9'). - Found 'DU'-anomaly for variable 'a' (lines '6'-'9'). + The value assigned to variable 'a' is never used Date: Sat, 20 Jun 2020 02:10:48 +0200 Subject: [PATCH 09/99] Handle while loop --- .../rule/errorprone/UnusedAssignmentRule.java | 31 ++++++++++++++++++- .../rule/errorprone/xml/UnusedAssignment.xml | 10 ++---- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 10ce9a41c6..9b09d66f56 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -11,6 +11,7 @@ import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; @@ -26,6 +27,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; +import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; @@ -108,6 +110,17 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } + @Override + public Object visit(ASTWhileStatement node, Object data) { + ScopeData before = (ScopeData) node.getCondition().jjtAccept(this, data); + + ScopeData iter = (ScopeData) node.getBody().jjtAccept(this, before.fork()); + iter = (ScopeData) node.getCondition().jjtAccept(this, iter); + iter = (ScopeData) node.getBody().jjtAccept(this, iter); + + return before.join(iter); + } + @Override public Object visit(ASTThrowStatement node, Object data) { data = super.visit(node, data); @@ -316,7 +329,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } static class AssignmentEntry { - final VariableNameDeclaration var; final JavaNode rhs; @@ -324,5 +336,22 @@ public class UnusedAssignmentRule extends AbstractJavaRule { this.var = var; this.rhs = rhs; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AssignmentEntry that = (AssignmentEntry) o; + return Objects.equals(rhs, that.rhs); + } + + @Override + public int hashCode() { + return Objects.hash(rhs); + } } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index f4ec0ded8e..b47fb98624 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -294,16 +294,12 @@ class Test{ #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 4. DU-Anomaly(a) - 1 - 6 - - The value assigned to variable 'a' is never used - + 0 Date: Sat, 20 Jun 2020 02:27:10 +0200 Subject: [PATCH 10/99] Handle break --- .../rule/errorprone/UnusedAssignmentRule.java | 12 +++ .../rule/errorprone/xml/UnusedAssignment.xml | 101 +++++++++++++++++- 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 9b09d66f56..7f1f021d60 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -15,6 +15,7 @@ import java.util.Objects; import java.util.Set; import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; +import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; @@ -127,6 +128,12 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return ((ScopeData) data).abruptCompletion(); } + @Override + public Object visit(ASTBreakStatement node, Object data) { + data = super.visit(node, data); + return ((ScopeData) data).abruptCompletion(); + } + @Override public Object visit(ASTReturnStatement node, Object data) { data = super.visit(node, data); @@ -169,6 +176,11 @@ public class UnusedAssignmentRule extends AbstractJavaRule { VariableNameDeclaration lhsVar = getLhsVar(node.getChild(0), true); if (lhsVar != null) { + if (node.getChild(1).getImage().length() >= 2) { + // compound assignment, to use BEFORE assigning + ((ScopeData) data).use(lhsVar); + } + ((ScopeData) data).assign(lhsVar, rhs); } else { node.getChild(0).jjtAccept(this, data); diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index b47fb98624..1bc4f5eec3 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -293,7 +293,7 @@ class Test{ - #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 4. DU-Anomaly(a) + While loop 1 0 + + While loop 2 + 2 + 4,7 + + The value assigned to variable 'i' is never used + The value assigned to variable 'i' is never used + + + + + While loop with break + 1 + 7 + + The value assigned to variable 'i' is never used + + = 30) { + i = a + 1; // unused + break; + } + a = a + 3; + i = i + 1; // used by itself + } + } +} + ]]> + + + While loop without break (control case) + 0 + = 30) { + i = a + 1; // used by below + // break; // no break here + } + a = a + 3; + i = i + 1; // used by itself + } + } +} + ]]> + + + + While loop with break 2 + 1 + 12 + + The value assigned to variable 'i' is never used + + = 30) { + i = i + 1; // used now + break outer; + } + a = a + 3; + i = i + 2; // unused (kills itself) + } + } + + System.out.println(i); // uses i = i + 1 + } +} + ]]> + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 5. DU-Anomaly(a) 1 From d0a96e78086aff90a7b49c7e48d47356fa0a3806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 20 Jun 2020 02:54:16 +0200 Subject: [PATCH 11/99] Finish loops --- .../rule/errorprone/UnusedAssignmentRule.java | 91 ++++++-- .../rule/errorprone/xml/UnusedAssignment.xml | 195 ++++++++++++++++-- 2 files changed, 243 insertions(+), 43 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 7f1f021d60..24782384da 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -5,9 +5,7 @@ package net.sourceforge.pmd.lang.java.rule.errorprone; -import java.util.ArrayDeque; import java.util.Collections; -import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -16,7 +14,11 @@ import java.util.Set; import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement; +import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; 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.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTName; @@ -24,6 +26,7 @@ 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.ASTReturnStatement; +import net.sourceforge.pmd.lang.java.ast.ASTStatement; import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; @@ -79,13 +82,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { static class LivenessVisitor extends JavaParserVisitorAdapter { - - private final Deque breakAddresses = new ArrayDeque<>(); - private final Map namedBreaks = new HashMap<>(); - // following deals with control flow - @Override public Object visit(JavaNode node, Object data) { @@ -99,29 +97,73 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTIfStatement node, Object data) { - ScopeData before = (ScopeData) node.getCondition().jjtAccept(this, data); + ScopeData before = acceptOpt(node.getCondition(), (ScopeData) data); - ScopeData thenData = before.fork(); - thenData = (ScopeData) node.getThenBranch().jjtAccept(this, thenData); - if (node.hasElse()) { - ScopeData elseData = (ScopeData) node.getElseBranch().jjtAccept(this, before.fork()); - return thenData.join(elseData); - } else { - return before.join(thenData); - } + ScopeData thenData = acceptOpt(node.getThenBranch(), before.fork()); + ScopeData elseData = acceptOpt(node.getElseBranch(), before.fork()); + + return thenData.join(elseData); } @Override public Object visit(ASTWhileStatement node, Object data) { - ScopeData before = (ScopeData) node.getCondition().jjtAccept(this, data); + // perform a few "iterations", to make sure that assignments in + // the body can affect themselves in the next iteration, and + // that they affect the condition + ScopeData before = acceptOpt(node.getCondition(), (ScopeData) data); - ScopeData iter = (ScopeData) node.getBody().jjtAccept(this, before.fork()); - iter = (ScopeData) node.getCondition().jjtAccept(this, iter); - iter = (ScopeData) node.getBody().jjtAccept(this, iter); + ScopeData iter = acceptOpt(node.getBody(), before.fork()); + iter = acceptOpt(node.getCondition(), iter); + iter = acceptOpt(node.getBody(), iter); return before.join(iter); } + @Override + public Object visit(ASTDoStatement node, Object data) { + // same as while but don't check the condition first + ScopeData before = (ScopeData) data; + + ScopeData iter = acceptOpt(node.getBody(), before.fork()); + iter = acceptOpt(node.getCondition(), iter); + iter = acceptOpt(node.getBody(), iter); + + return before.join(iter); + } + + @Override + public Object visit(ASTForStatement node, Object data) { + ASTStatement body = node.getBody(); + if (node.isForeach()) { + // the iterable expression + ScopeData before = (ScopeData) node.getChild(1).jjtAccept(this, data); + + ScopeData iter = acceptOpt(body, before.fork()); + iter = acceptOpt(body, iter); // the body must be able to affect itself + + return before.join(iter); + } else { + ASTForInit init = node.getFirstChildOfType(ASTForInit.class); + ASTExpression cond = node.getCondition(); + ASTForUpdate update = node.getFirstChildOfType(ASTForUpdate.class); + + ScopeData before = (ScopeData) data; + before = acceptOpt(init, before); + before = acceptOpt(cond, before); + + ScopeData iter = acceptOpt(body, before.fork()); + iter = acceptOpt(update, iter); + iter = acceptOpt(cond, iter); + iter = acceptOpt(body, iter); // the body must be able to affect itself + + return before.join(iter); + } + } + + private ScopeData acceptOpt(JavaNode node, ScopeData before) { + return node == null ? before : (ScopeData) node.jjtAccept(this, before); + } + @Override public Object visit(ASTThrowStatement node, Object data) { data = super.visit(node, data); @@ -216,14 +258,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (suffix.isArguments() || suffix.isArrayDereference()) { return null; } - return findVar(primary.getScope(), true, suffix.getImage()); + return findVar(primary.getScope(), true, substringBeforeFirst(suffix.getImage(), '.')); } else { if (inLhs && primary.getNumChildren() > 1) { return null; } if (prefix.getChild(0) instanceof ASTName) { - return findVar(prefix.getScope(), false, prefix.getChild(0).getImage()); + return findVar(prefix.getScope(), false, substringBeforeFirst(prefix.getChild(0).getImage(), '.')); } } } @@ -231,6 +273,11 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return null; } + private static String substringBeforeFirst(String str, char delim) { + int i = str.indexOf(delim); + return i < 0 ? str : str.substring(0, i); + } + private VariableNameDeclaration findVar(Scope scope, boolean isThis, String name) { if (name == null) { return null; diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 1bc4f5eec3..5c598f01d7 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -179,8 +179,12 @@ public class Foo { #1393 PMD hanging during DataflowAnomalyAnalysis - - 3 + 2 + 10,19 + + The value assigned to variable 'fail' is never used + The value assigned to variable 'fail' is never used + - #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 2. DU-Anomaly(a) - 1 - 5 + For loop + 0 + + + + + For loop 2 + 2 + 3,5 The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used + + + + For loop 3 + 0 + + + + + For loop 4 + 0 + - #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 5. DU-Anomaly(a) - 1 - 6 - - Found 'DU'-anomaly for variable 'a' (lines '6'-'9'). - + Do while 0 + 0 + + Do while 1 + 1 + 3 + + The value assigned to variable 'a' is never used + + + + + + Do while with break + 2 + 7,8 + + The value assigned to variable 'i' is never used + The value assigned to variable 'a' is never used + + = 20) { + i = 4; + a *= 5; + break; + } + + a = i + 3; + i += 3; + } while (i < 30); + } +} + ]]> + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 6. DU-Anomaly(a) 4 @@ -482,11 +578,11 @@ class Test{ - #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 9. DU-Anomaly(t1) + Usage as LHS of method 1 5 - Found 'DU'-anomaly for variable 't1' (lines '5'-'6'). + The value assigned to variable 't1' is never used - #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 12. DU-Anomaly(t1) + Assignment in operand 1 6 - Found 'DU'-anomaly for variable 't1' (lines '6'-'7'). + The value assigned to variable 't1' is never used - #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 13 + Assignment in operand 2 + 1 + 7 + + The value assigned to variable 't1' is never used + + + + + + Assignment in operand 3 0 - #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 14. DU-Anomaly(t1, t2) + Assignment in operand 4 2 4,6 - Found 'DU'-anomaly for variable 't2' (lines '4'-'7'). - Found 'DU'-anomaly for variable 't1' (lines '6'-'7'). + The value assigned to variable 't2' is never used + The value assigned to variable 't1' is never used 1 4 - Found 'DU'-anomaly for variable 'a' (lines '4'-'5'). + The value assigned to variable 'a' is never used + + + Compound assignment + 1 + 4 + + The value assigned to variable 'a' is never used + + + + + Another case + 1 + 3,5 + + The value assigned to variable 'iter' is never used + The value assigned to variable 'iter' is never used + + From e771242597b6e469b9b42756b936410731f53575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 20 Jun 2020 03:18:05 +0200 Subject: [PATCH 12/99] Switch --- .../rule/errorprone/UnusedAssignmentRule.java | 149 ++++++++++++--- .../rule/errorprone/xml/UnusedAssignment.xml | 171 +++++++++++++++--- 2 files changed, 265 insertions(+), 55 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 24782384da..20cab97228 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -5,7 +5,9 @@ package net.sourceforge.pmd.lang.java.rule.errorprone; +import java.util.ArrayDeque; import java.util.Collections; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -28,6 +30,10 @@ import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; import net.sourceforge.pmd.lang.java.ast.ASTStatement; import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; +import net.sourceforge.pmd.lang.java.ast.ASTSwitchExpression; +import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel; +import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabeledRule; +import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; @@ -41,6 +47,15 @@ import net.sourceforge.pmd.lang.symboltable.Scope; public class UnusedAssignmentRule extends AbstractJavaRule { + /* + TODO + named break targets + continue; + yield + constructors + initializers + + */ + @Override public Object visit(ASTMethodDeclaration node, Object data) { @@ -80,7 +95,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return super.visit(node, data); } - static class LivenessVisitor extends JavaParserVisitorAdapter { + private static class LivenessVisitor extends JavaParserVisitorAdapter { + + private final Deque unnamedBreakTargets = new ArrayDeque<>(); + private final Map namedBreakTargets = new HashMap<>(); // following deals with control flow @@ -95,6 +113,43 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return data; } + @Override + public Object visit(ASTSwitchStatement node, Object data) { + return processSwitch(node, (ScopeData) data, node.getTestedExpression()); + } + + @Override + public Object visit(ASTSwitchExpression node, Object data) { + return processSwitch(node, (ScopeData) data, node.getChild(0)); + } + + private ScopeData processSwitch(JavaNode switchLike, ScopeData data, JavaNode testedExpr) { + ScopeData before = acceptOpt(testedExpr, data); + + unnamedBreakTargets.push(before.fork()); + + ScopeData current = before; + for (int i = 1; i < switchLike.getNumChildren(); i++) { + JavaNode child = switchLike.getChild(i); + if (child instanceof ASTSwitchLabel) { + current = before.fork().join(current); + } else if (child instanceof ASTSwitchLabeledRule) { + // 'current' stays == 'before' so that the final join does nothing + current = acceptOpt(child.getChild(1), before.fork()); + current = doBreak(current); // process this as if it was followed by a break + } else { + // statement in a regular fallthrough switch block + current = acceptOpt(child, current); + } + } + + before = unnamedBreakTargets.pop(); + + // join with the last state, which is the exit point of the + // switch, if it's not closed by a break; + return before.join(current); + } + @Override public Object visit(ASTIfStatement node, Object data) { ScopeData before = acceptOpt(node.getCondition(), (ScopeData) data); @@ -112,10 +167,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // that they affect the condition ScopeData before = acceptOpt(node.getCondition(), (ScopeData) data); + unnamedBreakTargets.push(before); + ScopeData iter = acceptOpt(node.getBody(), before.fork()); iter = acceptOpt(node.getCondition(), iter); iter = acceptOpt(node.getBody(), iter); + unnamedBreakTargets.pop(); + return before.join(iter); } @@ -124,10 +183,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // same as while but don't check the condition first ScopeData before = (ScopeData) data; + unnamedBreakTargets.push(before); + ScopeData iter = acceptOpt(node.getBody(), before.fork()); iter = acceptOpt(node.getCondition(), iter); iter = acceptOpt(node.getBody(), iter); + unnamedBreakTargets.pop(); + return before.join(iter); } @@ -138,9 +201,13 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // the iterable expression ScopeData before = (ScopeData) node.getChild(1).jjtAccept(this, data); + unnamedBreakTargets.push(before); + ScopeData iter = acceptOpt(body, before.fork()); iter = acceptOpt(body, iter); // the body must be able to affect itself + unnamedBreakTargets.pop(); + return before.join(iter); } else { ASTForInit init = node.getFirstChildOfType(ASTForInit.class); @@ -151,11 +218,15 @@ public class UnusedAssignmentRule extends AbstractJavaRule { before = acceptOpt(init, before); before = acceptOpt(cond, before); + unnamedBreakTargets.push(before); + ScopeData iter = acceptOpt(body, before.fork()); iter = acceptOpt(update, iter); iter = acceptOpt(cond, iter); iter = acceptOpt(body, iter); // the body must be able to affect itself + unnamedBreakTargets.pop(); + return before.join(iter); } } @@ -165,20 +236,29 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } @Override - public Object visit(ASTThrowStatement node, Object data) { - data = super.visit(node, data); - return ((ScopeData) data).abruptCompletion(); + public Object visit(ASTBreakStatement node, Object data) { + if (node.getImage() == null) { + return doBreak((ScopeData) data); + } else { + // TODO + return ((ScopeData) data).abruptCompletion(); + } + } + + public ScopeData doBreak(ScopeData data) { + unnamedBreakTargets.getFirst().join(data); + return data.abruptCompletion(); } @Override - public Object visit(ASTBreakStatement node, Object data) { - data = super.visit(node, data); + public Object visit(ASTThrowStatement node, Object data) { + super.visit(node, data); return ((ScopeData) data).abruptCompletion(); } @Override public Object visit(ASTReturnStatement node, Object data) { - data = super.visit(node, data); + super.visit(node, data); return ((ScopeData) data).abruptCompletion(); } @@ -208,13 +288,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } public Object checkAssignment(JavaNode node, Object data) { + ScopeData result = (ScopeData) data; if (node.getNumChildren() == 3) { // assignment assert node.getChild(1) instanceof ASTAssignmentOperator; // visit the rhs as it is evaluated before - ASTExpression rhs = (ASTExpression) node.getChild(2); - rhs.jjtAccept(this, data); + JavaNode rhs = node.getChild(2); + result = acceptOpt(rhs, result); VariableNameDeclaration lhsVar = getLhsVar(node.getChild(0), true); if (lhsVar != null) { @@ -225,9 +306,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { ((ScopeData) data).assign(lhsVar, rhs); } else { - node.getChild(0).jjtAccept(this, data); + result = acceptOpt(node.getChild(0), result); } - return data; + return result; } else { return super.visit(node, data); } @@ -346,27 +427,27 @@ public class UnusedAssignmentRule extends AbstractJavaRule { ScopeData join(ScopeData sub) { // Merge live assignments of forked scopes - if (sub != this) { - for (VariableNameDeclaration var : this.liveAssignments.keySet()) { - Set myAssignments = this.liveAssignments.get(var); - Set subScopeAssignments = sub.liveAssignments.get(var); - if (subScopeAssignments == null) { - this.liveAssignments.put(var, myAssignments); - continue; - } - joinLive(var, myAssignments, subScopeAssignments); - } + if (sub == this || sub.liveAssignments.isEmpty()) { + return this; + } - for (VariableNameDeclaration var : sub.liveAssignments.keySet()) { - Set subScopeAssignments = sub.liveAssignments.get(var); - Set myAssignments = this.liveAssignments.get(var); - if (myAssignments == null) { - this.liveAssignments.put(var, subScopeAssignments); - continue; - } - joinLive(var, myAssignments, subScopeAssignments); + for (VariableNameDeclaration var : this.liveAssignments.keySet()) { + Set myAssignments = this.liveAssignments.get(var); + Set subScopeAssignments = sub.liveAssignments.get(var); + if (subScopeAssignments == null) { + continue; } + joinLive(var, myAssignments, subScopeAssignments); + } + for (VariableNameDeclaration var : sub.liveAssignments.keySet()) { + Set subScopeAssignments = sub.liveAssignments.get(var); + Set myAssignments = this.liveAssignments.get(var); + if (myAssignments == null) { + this.liveAssignments.put(var, subScopeAssignments); + continue; + } + joinLive(var, myAssignments, subScopeAssignments); } return this; @@ -385,6 +466,11 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } return this; } + + @Override + public String toString() { + return liveAssignments.toString(); + } } static class AssignmentEntry { @@ -396,6 +482,11 @@ public class UnusedAssignmentRule extends AbstractJavaRule { this.rhs = rhs; } + @Override + public String toString() { + return var.getImage() + " := " + rhs; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 5c598f01d7..4aafb3d11d 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -4,19 +4,19 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - - - - - - - - - - - - - + + ok + 0 + + DD anomaly @@ -526,14 +526,14 @@ class Test{ - #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 6. DU-Anomaly(a) + Switch statement 0 4 6,8,10,12 - Found 'DU'-anomaly for variable 'a' (lines '6'-'14'). - Found 'DU'-anomaly for variable 'a' (lines '8'-'14'). - Found 'DU'-anomaly for variable 'a' (lines '10'-'14'). - Found 'DU'-anomaly for variable 'a' (lines '12'-'14'). + The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used - #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 7. DU-Anomaly(a) + Switch statement 1 + 0 + + + + + Switch statement 2 + 0 + 0) break; // else fallthrough + case 2 : a = 2; break; + case 3 : a = 3; break; + default : a = a + 1; + } + + System.out.println(a); + } +} + ]]> + + + + Switch fallthrough + 1 + 6 + + The value assigned to variable 'a' is never used + + + + + + Switch fallthrough 2 1 9 - Found 'DU'-anomaly for variable 'a' (lines '9'-'11'). + The value assigned to variable 'a' is never used + + Switch non-fallthrough + 0 + a = 1; + case 2 -> a = 2; + case 3 -> a = 3; + default -> a = a + 1; + } + System.out.println(a); + } +} + ]]> + + + + Switch expr non-fallthrough + 4 + 6,7,8,9 + + The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used + + a = 1; + case 2 -> a = 2; + case 3 -> a = 3; + default -> a = a + 1; + }; + + System.out.println(a); + } +} + ]]> + + Usage as LHS of method 1 @@ -681,9 +803,6 @@ public class Test { public void test(){ int a = 0; a = a + 3; - - int i = 0; - i += 3; // same with compound } } ]]> @@ -706,7 +825,7 @@ public class Test { Another case - 1 + 2 3,5 The value assigned to variable 'iter' is never used From 75b5fbf62b0ab733201fc76c21b2521c698f77d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 20 Jun 2020 04:53:49 +0200 Subject: [PATCH 13/99] Continue statement --- .../rule/errorprone/UnusedAssignmentRule.java | 138 +++++++++++------- .../rule/errorprone/xml/UnusedAssignment.xml | 99 ++++++++++++- 2 files changed, 174 insertions(+), 63 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 20cab97228..967e034db6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -16,6 +16,7 @@ import java.util.Set; import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement; +import net.sourceforge.pmd.lang.java.ast.ASTContinueStatement; import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTForInit; @@ -98,6 +99,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private static class LivenessVisitor extends JavaParserVisitorAdapter { private final Deque unnamedBreakTargets = new ArrayDeque<>(); + // continue jumps to the condition check, while break jumps to after the loop + private final Deque unnamedContinueTargets = new ArrayDeque<>(); + private final Map namedBreakTargets = new HashMap<>(); // following deals with control flow @@ -136,7 +140,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } else if (child instanceof ASTSwitchLabeledRule) { // 'current' stays == 'before' so that the final join does nothing current = acceptOpt(child.getChild(1), before.fork()); - current = doBreak(current); // process this as if it was followed by a break + current = doBreak(current, unnamedBreakTargets); // process this as if it was followed by a break } else { // statement in a regular fallthrough switch block current = acceptOpt(child, current); @@ -162,36 +166,12 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTWhileStatement node, Object data) { - // perform a few "iterations", to make sure that assignments in - // the body can affect themselves in the next iteration, and - // that they affect the condition - ScopeData before = acceptOpt(node.getCondition(), (ScopeData) data); - - unnamedBreakTargets.push(before); - - ScopeData iter = acceptOpt(node.getBody(), before.fork()); - iter = acceptOpt(node.getCondition(), iter); - iter = acceptOpt(node.getBody(), iter); - - unnamedBreakTargets.pop(); - - return before.join(iter); + return handleLoop((ScopeData) data, null, node.getCondition(), null, node.getBody(), true); } @Override public Object visit(ASTDoStatement node, Object data) { - // same as while but don't check the condition first - ScopeData before = (ScopeData) data; - - unnamedBreakTargets.push(before); - - ScopeData iter = acceptOpt(node.getBody(), before.fork()); - iter = acceptOpt(node.getCondition(), iter); - iter = acceptOpt(node.getBody(), iter); - - unnamedBreakTargets.pop(); - - return before.join(iter); + return handleLoop((ScopeData) data, null, node.getCondition(), null, node.getBody(), false); } @Override @@ -199,57 +179,98 @@ public class UnusedAssignmentRule extends AbstractJavaRule { ASTStatement body = node.getBody(); if (node.isForeach()) { // the iterable expression - ScopeData before = (ScopeData) node.getChild(1).jjtAccept(this, data); - - unnamedBreakTargets.push(before); - - ScopeData iter = acceptOpt(body, before.fork()); - iter = acceptOpt(body, iter); // the body must be able to affect itself - - unnamedBreakTargets.pop(); - - return before.join(iter); + JavaNode init = node.getChild(1); + return handleLoop((ScopeData) data, init, null, null, body, true); } else { ASTForInit init = node.getFirstChildOfType(ASTForInit.class); ASTExpression cond = node.getCondition(); ASTForUpdate update = node.getFirstChildOfType(ASTForUpdate.class); - - ScopeData before = (ScopeData) data; - before = acceptOpt(init, before); - before = acceptOpt(cond, before); - - unnamedBreakTargets.push(before); - - ScopeData iter = acceptOpt(body, before.fork()); - iter = acceptOpt(update, iter); - iter = acceptOpt(cond, iter); - iter = acceptOpt(body, iter); // the body must be able to affect itself - - unnamedBreakTargets.pop(); - - return before.join(iter); + return handleLoop((ScopeData) data, init, cond, update, body, true); } } + + private ScopeData handleLoop(ScopeData before, + JavaNode init, + JavaNode cond, + JavaNode update, + JavaNode body, + boolean checkFirstIter) { + + // perform a few "iterations", to make sure that assignments in + // the body can affect themselves in the next iteration, and + // that they affect the condition, etc + + before = acceptOpt(init, before); + if (checkFirstIter) { + before = acceptOpt(cond, before); + } + + ScopeData breakTarget = before.forkEmpty(); + ScopeData continueTarget = before.forkEmpty(); + + unnamedBreakTargets.push(breakTarget); + unnamedContinueTargets.push(continueTarget); + + ScopeData iter = acceptOpt(body, before.fork()); + // make the body live in the other parts of the loop, + // including itself + iter = acceptOpt(update, iter); + iter = acceptOpt(cond, iter); + iter = acceptOpt(body, iter); + + breakTarget = unnamedBreakTargets.pop(); + + continueTarget = unnamedContinueTargets.pop(); + if (!continueTarget.liveAssignments.isEmpty()) { + // make assignments that are only live before a continue + // live inside the other parts of the loop + continueTarget = acceptOpt(cond, continueTarget); + continueTarget = acceptOpt(body, continueTarget); + continueTarget = acceptOpt(update, continueTarget); + } + ScopeData result = breakTarget.join(continueTarget).join(iter); + if (checkFirstIter) { + result = result.join(before); + } + + return result; + } + + private ScopeData acceptOpt(JavaNode node, ScopeData before) { return node == null ? before : (ScopeData) node.jjtAccept(this, before); } @Override - public Object visit(ASTBreakStatement node, Object data) { + public Object visit(ASTContinueStatement node, Object data) { if (node.getImage() == null) { - return doBreak((ScopeData) data); + return doBreak((ScopeData) data, unnamedContinueTargets); } else { // TODO return ((ScopeData) data).abruptCompletion(); } } - public ScopeData doBreak(ScopeData data) { - unnamedBreakTargets.getFirst().join(data); + @Override + public Object visit(ASTBreakStatement node, Object data) { + if (node.getImage() == null) { + return doBreak((ScopeData) data, unnamedBreakTargets); + } else { + // TODO + return ((ScopeData) data).abruptCompletion(); + } + } + + public ScopeData doBreak(ScopeData data, Deque targets) { + // basically, assignments that are live at the point of the break + // are also live after the break (wherever it lands) + targets.push(targets.getFirst().join(data)); return data.abruptCompletion(); } + // both of those exit the scope of the method/ctor, so their assignments go dead + @Override public Object visit(ASTThrowStatement node, Object data) { super.visit(node, data); @@ -420,11 +441,16 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return new ScopeData(this.allAssignments, this.usedAssignments, new HashMap<>(this.liveAssignments)); } + ScopeData forkEmpty() { + return new ScopeData(this.allAssignments, this.usedAssignments, new HashMap<>()); + } + ScopeData abruptCompletion() { this.liveAssignments.clear(); return this; } + ScopeData join(ScopeData sub) { // Merge live assignments of forked scopes if (sub == this || sub.liveAssignments.isEmpty()) { diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 4aafb3d11d..211ae98cc1 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -324,13 +324,9 @@ class Test{ ]]> - - #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 3. DU-Anomaly(a) - 1 - 5 - - Found 'DU'-anomaly for variable 'a' (lines '5'-'7'). - + + Foreach + 0 + + + While loop with continue + 0 + = 30) { + i = a + 1; // used by below + continue; + } + a = a + 3; + i = i + 1; // used by itself + } + } +} + ]]> + + + + While loop with continue 2 + 0 + = 30) { + a = i + 1; // used by loop condition + continue; + } + i++; // used by itself + } + } +} + ]]> + + + + While loop with break (control for continue test above) + 1 + 7 + + The value assigned to variable 'a' is never used + + = 30) { + a = i + 1; // unused + break; + } + i++; // used by itself + } + } +} + ]]> + + Do while 0 0 @@ -525,6 +587,29 @@ class Test{ ]]> + + Do while with continue + 0 + = 20) { + i = 4; // used by condition + a *= 5; + continue; + } + + a = i + 3; + i += 3; + } while (i < 30); + } +} + ]]> + + Switch statement 0 4 From 5cebe5d5ec4eae0f6f86854f16df2e525903732a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 20 Jun 2020 05:56:19 +0200 Subject: [PATCH 14/99] Consider inc/decrement expressions --- .../rule/errorprone/UnusedAssignmentRule.java | 37 ++++++++++- .../rule/errorprone/xml/UnusedAssignment.xml | 63 +++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 967e034db6..7c3bae47c2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -25,6 +25,9 @@ import net.sourceforge.pmd.lang.java.ast.ASTForUpdate; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTName; +import net.sourceforge.pmd.lang.java.ast.ASTPostfixExpression; +import net.sourceforge.pmd.lang.java.ast.ASTPreDecrementExpression; +import net.sourceforge.pmd.lang.java.ast.ASTPreIncrementExpression; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; @@ -39,6 +42,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; +import net.sourceforge.pmd.lang.java.ast.ASTYieldStatement; import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; @@ -51,8 +55,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { /* TODO named break targets - continue; - yield constructors + initializers */ @@ -262,6 +264,13 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } + @Override + public Object visit(ASTYieldStatement node, Object data) { + super.visit(node, data); // visit expression + // treat as break, ie abrupt completion + link live vars to outer context + return doBreak((ScopeData) data, unnamedBreakTargets); + } + public ScopeData doBreak(ScopeData data, Deque targets) { // basically, assignments that are live at the point of the break // are also live after the break (wherever it lands) @@ -335,6 +344,30 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } + @Override + public Object visit(ASTPreDecrementExpression node, Object data) { + return checkIncOrDecrement(node, (ScopeData) data); + } + + @Override + public Object visit(ASTPreIncrementExpression node, Object data) { + return checkIncOrDecrement(node, (ScopeData) data); + } + + @Override + public Object visit(ASTPostfixExpression node, Object data) { + return checkIncOrDecrement(node, (ScopeData) data); + } + + private ScopeData checkIncOrDecrement(JavaNode unary, ScopeData data) { + VariableNameDeclaration var = getLhsVar(unary.getChild(0), true); + if (var != null) { + data.use(var); + data.assign(var, unary); + } + return data; + } + // variable usage @Override diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 211ae98cc1..2429f4916d 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -756,6 +756,36 @@ class Test{ ]]> + + Switch non-fallthrough blocks + 1 + 9 + + The value assigned to variable 'i' is never used + + a = 1; + case 2 -> { + if (args.length > 0) { + i = 4; + break; + } + a = 2; + } + case 3 -> a = 3; + default -> a = a + 1; + } + System.out.println(a); + } +} + ]]> + + Switch expr non-fallthrough 4 @@ -784,6 +814,39 @@ class Test{ ]]> + + Switch expr with yield + 4 + 6,9,13,14 + + The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used + + a = 1; + case 2 -> { + if (a > 0) { + yield a++; + } + yield 4; + } + case 3 -> a = 3; + default -> a = a + 1; + }; + + System.out.println(a); + } +} + ]]> + + Usage as LHS of method 1 From 990b39c47219ed45ea9b86477dcd41001ed0067f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 20 Jun 2020 06:08:11 +0200 Subject: [PATCH 15/99] Handle labeled loops --- .../rule/errorprone/UnusedAssignmentRule.java | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 7c3bae47c2..6111af35da 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -23,6 +23,7 @@ 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.ASTIfStatement; +import net.sourceforge.pmd.lang.java.ast.ASTLabeledStatement; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTPostfixExpression; @@ -105,6 +106,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private final Deque unnamedContinueTargets = new ArrayDeque<>(); private final Map namedBreakTargets = new HashMap<>(); + private final Map namedContinueTargets = new HashMap<>(); // following deals with control flow @@ -168,12 +170,12 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTWhileStatement node, Object data) { - return handleLoop((ScopeData) data, null, node.getCondition(), null, node.getBody(), true); + return handleLoop(node, (ScopeData) data, null, node.getCondition(), null, node.getBody(), true); } @Override public Object visit(ASTDoStatement node, Object data) { - return handleLoop((ScopeData) data, null, node.getCondition(), null, node.getBody(), false); + return handleLoop(node, (ScopeData) data, null, node.getCondition(), null, node.getBody(), false); } @Override @@ -182,17 +184,18 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (node.isForeach()) { // the iterable expression JavaNode init = node.getChild(1); - return handleLoop((ScopeData) data, init, null, null, body, true); + return handleLoop(node, (ScopeData) data, init, null, null, body, true); } else { ASTForInit init = node.getFirstChildOfType(ASTForInit.class); ASTExpression cond = node.getCondition(); ASTForUpdate update = node.getFirstChildOfType(ASTForUpdate.class); - return handleLoop((ScopeData) data, init, cond, update, body, true); + return handleLoop(node, (ScopeData) data, init, cond, update, body, true); } } - private ScopeData handleLoop(ScopeData before, + private ScopeData handleLoop(JavaNode loop, + ScopeData before, JavaNode init, JavaNode cond, JavaNode update, @@ -211,8 +214,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { ScopeData breakTarget = before.forkEmpty(); ScopeData continueTarget = before.forkEmpty(); - unnamedBreakTargets.push(breakTarget); - unnamedContinueTargets.push(continueTarget); + Set labels = pushTargets(loop, breakTarget, continueTarget); ScopeData iter = acceptOpt(body, before.fork()); // make the body live in the other parts of the loop, @@ -221,9 +223,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { iter = acceptOpt(cond, iter); iter = acceptOpt(body, iter); - breakTarget = unnamedBreakTargets.pop(); - continueTarget = unnamedContinueTargets.pop(); + breakTarget = unnamedBreakTargets.getFirst(); + continueTarget = unnamedContinueTargets.getFirst(); if (!continueTarget.liveAssignments.isEmpty()) { // make assignments that are only live before a continue // live inside the other parts of the loop @@ -231,6 +233,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { continueTarget = acceptOpt(body, continueTarget); continueTarget = acceptOpt(update, continueTarget); } + + popTargets(labels); + ScopeData result = breakTarget.join(continueTarget).join(iter); if (checkFirstIter) { result = result.join(before); @@ -239,6 +244,36 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return result; } + private Set pushTargets(JavaNode loop, ScopeData breakTarget, ScopeData continueTarget) { + unnamedBreakTargets.push(breakTarget); + unnamedContinueTargets.push(continueTarget); + + JavaNode parent = loop.getNthParent(2); + Set labels; + if (parent instanceof ASTLabeledStatement) { + labels = new HashSet<>(1); + while (parent instanceof ASTLabeledStatement) { + String label = parent.getImage(); + namedBreakTargets.put(label, breakTarget); + namedContinueTargets.put(label, continueTarget); + labels.add(label); + parent = parent.getNthParent(2); + } + } else { + labels = Collections.emptySet(); + } + return labels; + } + + private void popTargets(Set labels) { + unnamedContinueTargets.pop(); + unnamedBreakTargets.pop(); + for (String label : labels) { + namedBreakTargets.remove(label); + namedContinueTargets.remove(label); + } + } + private ScopeData acceptOpt(JavaNode node, ScopeData before) { return node == null ? before : (ScopeData) node.jjtAccept(this, before); From fec7a3b14bb7fe5a51a6988f895a57d5debee9ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 20 Jun 2020 06:15:40 +0200 Subject: [PATCH 16/99] REVERT code to check for unused variables --- .../rule/errorprone/UnusedAssignmentRule.java | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 6111af35da..752af788d1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -63,13 +63,11 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTMethodDeclaration node, Object data) { + // This analysis can be used to check for unused variables easily + // See reverted commit somewhere in the PR ScopeData bodyData = new ScopeData(); - // for (ASTFormalParameter param : node.getFormalParameters()) { - // bodyData.varsThatMustBeUsed.add(param.getVariableDeclaratorId().getNameDeclaration()); - // } - ScopeData endData = (ScopeData) node.getBody().jjtAccept(new LivenessVisitor(), bodyData); if (endData.usedAssignments.size() < endData.allAssignments.size()) { @@ -81,20 +79,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { addViolationWithMessage(data, entry.rhs, "The value assigned to variable ''{0}'' is never used", new Object[] {entry.var.getImage()}); } } - // if (!endData.varsThatMustBeUsed.isEmpty()) { - // HashSet assignedVars = new HashSet<>(); - // for (AssignmentEntry assignment : endData.allAssignments) { - // assignedVars.add(assignment.var); - // } - - // for (VariableNameDeclaration var : endData.varsThatMustBeUsed) { - // if (assignedVars.contains(var)) { - // addViolationWithMessage(data, var.getNode(), "The variable ''{0}'' is assigned, but never accessed", new Object[] {var.getImage()}); - // } else { - // addViolationWithMessage(data, var.getNode(), "The variable ''{0}'' is never used", new Object[] {var.getImage()}); - // } - // } - // } return super.visit(node, data); } From b6dd5ad425a6ef8ce6c85e5669912117f63909e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 20 Jun 2020 06:15:45 +0200 Subject: [PATCH 17/99] Simplify --- .../rule/errorprone/UnusedAssignmentRule.java | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 752af788d1..9ea476470d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -198,7 +198,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { ScopeData breakTarget = before.forkEmpty(); ScopeData continueTarget = before.forkEmpty(); - Set labels = pushTargets(loop, breakTarget, continueTarget); + pushTargets(loop, breakTarget, continueTarget); ScopeData iter = acceptOpt(body, before.fork()); // make the body live in the other parts of the loop, @@ -218,7 +218,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { continueTarget = acceptOpt(update, continueTarget); } - popTargets(labels); + popTargets(loop); ScopeData result = breakTarget.join(continueTarget).join(iter); if (checkFirstIter) { @@ -228,37 +228,31 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return result; } - private Set pushTargets(JavaNode loop, ScopeData breakTarget, ScopeData continueTarget) { + private void pushTargets(JavaNode loop, ScopeData breakTarget, ScopeData continueTarget) { unnamedBreakTargets.push(breakTarget); unnamedContinueTargets.push(continueTarget); JavaNode parent = loop.getNthParent(2); - Set labels; - if (parent instanceof ASTLabeledStatement) { - labels = new HashSet<>(1); - while (parent instanceof ASTLabeledStatement) { - String label = parent.getImage(); - namedBreakTargets.put(label, breakTarget); - namedContinueTargets.put(label, continueTarget); - labels.add(label); - parent = parent.getNthParent(2); - } - } else { - labels = Collections.emptySet(); + while (parent instanceof ASTLabeledStatement) { + String label = parent.getImage(); + namedBreakTargets.put(label, breakTarget); + namedContinueTargets.put(label, continueTarget); + parent = parent.getNthParent(2); } - return labels; } - private void popTargets(Set labels) { + private void popTargets(JavaNode loop) { unnamedContinueTargets.pop(); unnamedBreakTargets.pop(); - for (String label : labels) { + JavaNode parent = loop.getNthParent(2); + while (parent instanceof ASTLabeledStatement) { + String label = parent.getImage(); namedBreakTargets.remove(label); namedContinueTargets.remove(label); + parent = parent.getNthParent(2); } } - private ScopeData acceptOpt(JavaNode node, ScopeData before) { return node == null ? before : (ScopeData) node.jjtAccept(this, before); } @@ -456,8 +450,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private static class ScopeData { - // final Set varsThatMustBeUsed = new HashSet<>(); - final Set allAssignments; final Set usedAssignments; From aee18faae3cf75144f284f48d9d6fc18181fc7b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 20 Jun 2020 06:18:20 +0200 Subject: [PATCH 18/99] Named labels --- .../rule/errorprone/UnusedAssignmentRule.java | 201 ++++++++++-------- .../rule/errorprone/xml/UnusedAssignment.xml | 99 ++++++++- 2 files changed, 204 insertions(+), 96 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 9ea476470d..72a56a45f6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -55,7 +55,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { /* TODO - named break targets constructors + initializers */ @@ -66,9 +65,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // This analysis can be used to check for unused variables easily // See reverted commit somewhere in the PR - ScopeData bodyData = new ScopeData(); + LivenessState bodyData = new LivenessState(); - ScopeData endData = (ScopeData) node.getBody().jjtAccept(new LivenessVisitor(), bodyData); + LivenessState endData = (LivenessState) node.getBody().jjtAccept(new LivenessVisitor(), bodyData); if (endData.usedAssignments.size() < endData.allAssignments.size()) { HashSet unused = new HashSet<>(endData.allAssignments); @@ -85,12 +84,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private static class LivenessVisitor extends JavaParserVisitorAdapter { - private final Deque unnamedBreakTargets = new ArrayDeque<>(); + private final TargetStack breakTargets = new TargetStack(); // continue jumps to the condition check, while break jumps to after the loop - private final Deque unnamedContinueTargets = new ArrayDeque<>(); - - private final Map namedBreakTargets = new HashMap<>(); - private final Map namedContinueTargets = new HashMap<>(); + private final TargetStack continueTargets = new TargetStack(); // following deals with control flow @@ -107,20 +103,20 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTSwitchStatement node, Object data) { - return processSwitch(node, (ScopeData) data, node.getTestedExpression()); + return processSwitch(node, (LivenessState) data, node.getTestedExpression()); } @Override public Object visit(ASTSwitchExpression node, Object data) { - return processSwitch(node, (ScopeData) data, node.getChild(0)); + return processSwitch(node, (LivenessState) data, node.getChild(0)); } - private ScopeData processSwitch(JavaNode switchLike, ScopeData data, JavaNode testedExpr) { - ScopeData before = acceptOpt(testedExpr, data); + private LivenessState processSwitch(JavaNode switchLike, LivenessState data, JavaNode testedExpr) { + LivenessState before = acceptOpt(testedExpr, data); - unnamedBreakTargets.push(before.fork()); + breakTargets.push(before.fork()); - ScopeData current = before; + LivenessState current = before; for (int i = 1; i < switchLike.getNumChildren(); i++) { JavaNode child = switchLike.getChild(i); if (child instanceof ASTSwitchLabel) { @@ -128,14 +124,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } else if (child instanceof ASTSwitchLabeledRule) { // 'current' stays == 'before' so that the final join does nothing current = acceptOpt(child.getChild(1), before.fork()); - current = doBreak(current, unnamedBreakTargets); // process this as if it was followed by a break + current = breakTargets.doBreak(current, null); // process this as if it was followed by a break } else { // statement in a regular fallthrough switch block current = acceptOpt(child, current); } } - before = unnamedBreakTargets.pop(); + before = breakTargets.pop(); // join with the last state, which is the exit point of the // switch, if it's not closed by a break; @@ -144,22 +140,22 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTIfStatement node, Object data) { - ScopeData before = acceptOpt(node.getCondition(), (ScopeData) data); + LivenessState before = acceptOpt(node.getCondition(), (LivenessState) data); - ScopeData thenData = acceptOpt(node.getThenBranch(), before.fork()); - ScopeData elseData = acceptOpt(node.getElseBranch(), before.fork()); + LivenessState thenData = acceptOpt(node.getThenBranch(), before.fork()); + LivenessState elseData = acceptOpt(node.getElseBranch(), before.fork()); return thenData.join(elseData); } @Override public Object visit(ASTWhileStatement node, Object data) { - return handleLoop(node, (ScopeData) data, null, node.getCondition(), null, node.getBody(), true); + return handleLoop(node, (LivenessState) data, null, node.getCondition(), null, node.getBody(), true); } @Override public Object visit(ASTDoStatement node, Object data) { - return handleLoop(node, (ScopeData) data, null, node.getCondition(), null, node.getBody(), false); + return handleLoop(node, (LivenessState) data, null, node.getCondition(), null, node.getBody(), false); } @Override @@ -168,23 +164,23 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (node.isForeach()) { // the iterable expression JavaNode init = node.getChild(1); - return handleLoop(node, (ScopeData) data, init, null, null, body, true); + return handleLoop(node, (LivenessState) data, init, null, null, body, true); } else { ASTForInit init = node.getFirstChildOfType(ASTForInit.class); ASTExpression cond = node.getCondition(); ASTForUpdate update = node.getFirstChildOfType(ASTForUpdate.class); - return handleLoop(node, (ScopeData) data, init, cond, update, body, true); + return handleLoop(node, (LivenessState) data, init, cond, update, body, true); } } - private ScopeData handleLoop(JavaNode loop, - ScopeData before, - JavaNode init, - JavaNode cond, - JavaNode update, - JavaNode body, - boolean checkFirstIter) { + private LivenessState handleLoop(JavaNode loop, + LivenessState before, + JavaNode init, + JavaNode cond, + JavaNode update, + JavaNode body, + boolean checkFirstIter) { // perform a few "iterations", to make sure that assignments in // the body can affect themselves in the next iteration, and @@ -195,12 +191,12 @@ public class UnusedAssignmentRule extends AbstractJavaRule { before = acceptOpt(cond, before); } - ScopeData breakTarget = before.forkEmpty(); - ScopeData continueTarget = before.forkEmpty(); + LivenessState breakTarget = before.forkEmpty(); + LivenessState continueTarget = before.forkEmpty(); pushTargets(loop, breakTarget, continueTarget); - ScopeData iter = acceptOpt(body, before.fork()); + LivenessState iter = acceptOpt(body, before.fork()); // make the body live in the other parts of the loop, // including itself iter = acceptOpt(update, iter); @@ -208,8 +204,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { iter = acceptOpt(body, iter); - breakTarget = unnamedBreakTargets.getFirst(); - continueTarget = unnamedContinueTargets.getFirst(); + breakTarget = breakTargets.peek(); + continueTarget = continueTargets.peek(); if (!continueTarget.liveAssignments.isEmpty()) { // make assignments that are only live before a continue // live inside the other parts of the loop @@ -218,9 +214,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { continueTarget = acceptOpt(update, continueTarget); } - popTargets(loop); - - ScopeData result = breakTarget.join(continueTarget).join(iter); + LivenessState result = popTargets(loop, breakTarget, continueTarget); + result = result.join(iter); if (checkFirstIter) { result = result.join(before); } @@ -228,81 +223,76 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return result; } - private void pushTargets(JavaNode loop, ScopeData breakTarget, ScopeData continueTarget) { - unnamedBreakTargets.push(breakTarget); - unnamedContinueTargets.push(continueTarget); + private void pushTargets(JavaNode loop, LivenessState breakTarget, LivenessState continueTarget) { + breakTargets.unnamedTargets.push(breakTarget); + continueTargets.unnamedTargets.push(continueTarget); JavaNode parent = loop.getNthParent(2); while (parent instanceof ASTLabeledStatement) { String label = parent.getImage(); - namedBreakTargets.put(label, breakTarget); - namedContinueTargets.put(label, continueTarget); + breakTargets.namedTargets.put(label, breakTarget); + continueTargets.namedTargets.put(label, continueTarget); parent = parent.getNthParent(2); } } - private void popTargets(JavaNode loop) { - unnamedContinueTargets.pop(); - unnamedBreakTargets.pop(); + private LivenessState popTargets(JavaNode loop, LivenessState breakTarget, LivenessState continueTarget) { + breakTargets.unnamedTargets.pop(); + continueTargets.unnamedTargets.pop(); + + LivenessState total = breakTarget.join(continueTarget); + JavaNode parent = loop.getNthParent(2); while (parent instanceof ASTLabeledStatement) { String label = parent.getImage(); - namedBreakTargets.remove(label); - namedContinueTargets.remove(label); + total = total.join(breakTargets.namedTargets.remove(label)); + total = total.join(continueTargets.namedTargets.remove(label)); parent = parent.getNthParent(2); } + return total; } - private ScopeData acceptOpt(JavaNode node, ScopeData before) { - return node == null ? before : (ScopeData) node.jjtAccept(this, before); + private LivenessState acceptOpt(JavaNode node, LivenessState before) { + return node == null ? before : (LivenessState) node.jjtAccept(this, before); } @Override public Object visit(ASTContinueStatement node, Object data) { - if (node.getImage() == null) { - return doBreak((ScopeData) data, unnamedContinueTargets); - } else { - // TODO - return ((ScopeData) data).abruptCompletion(); - } + return continueTargets.doBreak((LivenessState) data, node.getImage()); } @Override public Object visit(ASTBreakStatement node, Object data) { - if (node.getImage() == null) { - return doBreak((ScopeData) data, unnamedBreakTargets); - } else { - // TODO - return ((ScopeData) data).abruptCompletion(); - } + return breakTargets.doBreak((LivenessState) data, node.getImage()); } @Override public Object visit(ASTYieldStatement node, Object data) { super.visit(node, data); // visit expression // treat as break, ie abrupt completion + link live vars to outer context - return doBreak((ScopeData) data, unnamedBreakTargets); + return breakTargets.doBreak((LivenessState) data, null); } - public ScopeData doBreak(ScopeData data, Deque targets) { + private LivenessState doBreak(LivenessState data, Deque targets) { // basically, assignments that are live at the point of the break // are also live after the break (wherever it lands) targets.push(targets.getFirst().join(data)); return data.abruptCompletion(); } + // both of those exit the scope of the method/ctor, so their assignments go dead @Override public Object visit(ASTThrowStatement node, Object data) { super.visit(node, data); - return ((ScopeData) data).abruptCompletion(); + return ((LivenessState) data).abruptCompletion(); } @Override public Object visit(ASTReturnStatement node, Object data) { super.visit(node, data); - return ((ScopeData) data).abruptCompletion(); + return ((LivenessState) data).abruptCompletion(); } // following deals with assignment @@ -314,7 +304,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { ASTVariableInitializer rhs = node.getInitializer(); if (rhs != null) { rhs.jjtAccept(this, data); - ((ScopeData) data).assign(var, rhs); + ((LivenessState) data).assign(var, rhs); } return data; } @@ -331,7 +321,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } public Object checkAssignment(JavaNode node, Object data) { - ScopeData result = (ScopeData) data; + LivenessState result = (LivenessState) data; if (node.getNumChildren() == 3) { // assignment assert node.getChild(1) instanceof ASTAssignmentOperator; @@ -344,10 +334,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (lhsVar != null) { if (node.getChild(1).getImage().length() >= 2) { // compound assignment, to use BEFORE assigning - ((ScopeData) data).use(lhsVar); + ((LivenessState) data).use(lhsVar); } - ((ScopeData) data).assign(lhsVar, rhs); + ((LivenessState) data).assign(lhsVar, rhs); } else { result = acceptOpt(node.getChild(0), result); } @@ -359,20 +349,20 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTPreDecrementExpression node, Object data) { - return checkIncOrDecrement(node, (ScopeData) data); + return checkIncOrDecrement(node, (LivenessState) data); } @Override public Object visit(ASTPreIncrementExpression node, Object data) { - return checkIncOrDecrement(node, (ScopeData) data); + return checkIncOrDecrement(node, (LivenessState) data); } @Override public Object visit(ASTPostfixExpression node, Object data) { - return checkIncOrDecrement(node, (ScopeData) data); + return checkIncOrDecrement(node, (LivenessState) data); } - private ScopeData checkIncOrDecrement(JavaNode unary, ScopeData data) { + private LivenessState checkIncOrDecrement(JavaNode unary, LivenessState data) { VariableNameDeclaration var = getLhsVar(unary.getChild(0), true); if (var != null) { data.use(var); @@ -389,7 +379,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { VariableNameDeclaration var = getLhsVar(node, false); if (var != null) { - ((ScopeData) data).use(var); + ((LivenessState) data).use(var); } return data; } @@ -448,20 +438,20 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } - private static class ScopeData { + private static class LivenessState { final Set allAssignments; final Set usedAssignments; final Map> liveAssignments; - private ScopeData() { + private LivenessState() { this(new HashSet(), new HashSet(), new HashMap>()); } - private ScopeData(Set allAssignments, - Set usedAssignments, - Map> liveAssignments) { + private LivenessState(Set allAssignments, + Set usedAssignments, + Map> liveAssignments) { this.allAssignments = allAssignments; this.usedAssignments = usedAssignments; this.liveAssignments = liveAssignments; @@ -481,21 +471,21 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } - ScopeData fork() { - return new ScopeData(this.allAssignments, this.usedAssignments, new HashMap<>(this.liveAssignments)); + LivenessState fork() { + return new LivenessState(this.allAssignments, this.usedAssignments, new HashMap<>(this.liveAssignments)); } - ScopeData forkEmpty() { - return new ScopeData(this.allAssignments, this.usedAssignments, new HashMap<>()); + LivenessState forkEmpty() { + return new LivenessState(this.allAssignments, this.usedAssignments, new HashMap<>()); } - ScopeData abruptCompletion() { + LivenessState abruptCompletion() { this.liveAssignments.clear(); return this; } - ScopeData join(ScopeData sub) { + LivenessState join(LivenessState sub) { // Merge live assignments of forked scopes if (sub == this || sub.liveAssignments.isEmpty()) { return this; @@ -530,8 +520,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { this.liveAssignments.put(var, newLive); } - ScopeData join(Iterable scopes) { - for (ScopeData sub : scopes) { + LivenessState join(Iterable scopes) { + for (LivenessState sub : scopes) { this.join(sub); } return this; @@ -543,7 +533,42 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } + static class TargetStack { + + final Deque unnamedTargets = new ArrayDeque<>(); + final Map namedTargets = new HashMap<>(); + + + void push(LivenessState state) { + unnamedTargets.push(state); + } + + LivenessState pop() { + return unnamedTargets.pop(); + } + + LivenessState peek() { + return unnamedTargets.getFirst(); + } + + LivenessState doBreak(LivenessState data,/* nullable */ String label) { + // basically, assignments that are live at the point of the break + // are also live after the break (wherever it lands) + if (label == null) { + unnamedTargets.push(unnamedTargets.getFirst().join(data)); + } else { + LivenessState target = namedTargets.get(label); + if (target != null) { + // otherwise CT error + target.join(data); + } + } + return data.abruptCompletion(); + } + } + static class AssignmentEntry { + final VariableNameDeclaration var; final JavaNode rhs; diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 2429f4916d..c7cc3fe0a7 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -401,21 +401,22 @@ class Test{ } ]]> + While loop without break (control case) - 0 + 1 = 30) { - i = a + 1; // used by below + i += a + 1; // unused by below // break; // no break here } a = a + 3; - i = i + 1; // used by itself + i = a + 2; // used by above } } } @@ -423,7 +424,7 @@ class Test{ - While loop with break 2 + While loop without break 2 (control case) 1 12 @@ -433,7 +434,7 @@ class Test{ class Test{ public static void main(String[] args){ int a = 0; - int i = 0; // used now + int i = 0; // unused now outer: while (true) { @@ -441,12 +442,80 @@ class Test{ while (true) { if (a >= 30) { - i = i + 1; // used now + i += a + 1; // unused because of i = a + 2 + // break outer; + } + a = a + 3; + i = a + 2; // killed by below + } + + i = 2; // used by print + } + + System.out.println(i); // uses i = i + 1 + } +} + ]]> + + + + While loop without break 2 (control case) + 1 + 12 + + The value assigned to variable 'i' is never used + + = 30) { + i += a + 1; // unused because of i = 2 + break; + } + a = a + 3; + i = a + 2; // used by i += a + 1 + } + + i = 2; // used by print + } + + System.out.println(i); // uses i = i + 1 + } +} + ]]> + + + + While loop with named break 2 + 0 + = 30) { + i += a + 1; // used by print break outer; } a = a + 3; - i = i + 2; // unused (kills itself) + i = a + 2; // used by i += a + 1 } + + i = 2; // used by print } System.out.println(i); // uses i = i + 1 @@ -989,4 +1058,18 @@ public class Test { } ]]> + + + Var usage in lambda (#1304) + 0 + dummySet) { + captured = captured.trim(); + return dummySet.stream().noneMatch(value -> value.equalsIgnoreCase(captured)); + } + +} ]]> + From 069287e9e15d88263d49414e2470b8fd293eb1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 20 Jun 2020 08:03:51 +0200 Subject: [PATCH 19/99] Better messages --- .../rule/errorprone/UnusedAssignmentRule.java | 126 ++++++++++++++---- .../resources/category/java/errorprone.xml | 2 +- .../rule/errorprone/xml/UnusedAssignment.xml | 99 +++++++------- 3 files changed, 149 insertions(+), 78 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 72a56a45f6..7e54d98b4e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -6,7 +6,9 @@ package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; @@ -14,7 +16,10 @@ import java.util.Map; import java.util.Objects; 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.ASTBreakStatement; import net.sourceforge.pmd.lang.java.ast.ASTContinueStatement; import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; @@ -24,6 +29,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTForUpdate; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTLabeledStatement; +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.ASTPostfixExpression; @@ -41,6 +47,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabeledRule; import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; import net.sourceforge.pmd.lang.java.ast.ASTYieldStatement; @@ -56,6 +63,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { /* TODO constructors + initializers + try/catch */ @@ -75,13 +83,41 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // allAssignments is the unused assignments now for (AssignmentEntry entry : unused) { - addViolationWithMessage(data, entry.rhs, "The value assigned to variable ''{0}'' is never used", new Object[] {entry.var.getImage()}); + Set killers = endData.killRecord.get(entry); + if (killers == null || killers.isEmpty()) { + addViolation(data, entry.rhs, new Object[] {entry.var.getImage(), "goes out of scope"}); + } else if (killers.size() == 1) { + AssignmentEntry k = killers.iterator().next(); + addViolation(data, entry.rhs, new Object[] {entry.var.getImage(), + "overwritten on line " + k.rhs.getBeginLine()}); + } else { + addViolation(data, entry.rhs, new Object[] {entry.var.getImage(), joinLines("overwritten on lines ", killers)}); + } } } return super.visit(node, data); } + private static String joinLines(String prefix, Set killers) { + StringBuilder sb = new StringBuilder(prefix); + ArrayList sorted = new ArrayList<>(killers); + Collections.sort(sorted, new Comparator() { + @Override + public int compare(AssignmentEntry o1, AssignmentEntry o2) { + return Integer.compare(o1.rhs.getBeginLine(), o2.rhs.getBeginLine()); + } + }); + + sb.append(sorted.get(0).rhs.getBeginLine()); + for (int i = 1; i < sorted.size() - 1; i++) { + sb.append(", ").append(sorted.get(i).rhs.getBeginLine()); + } + sb.append(" and ").append(sorted.get(sorted.size() - 1).rhs.getBeginLine()); + + return sb.toString(); + } + private static class LivenessVisitor extends JavaParserVisitorAdapter { private final TargetStack breakTargets = new TargetStack(); @@ -101,6 +137,31 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return data; } + @Override + public Object visit(ASTBlock node, Object data) { + // variables local to a loop iteration must be killed before the + // next iteration + Set localsToKill = new HashSet<>(); + + for (JavaNode child : node.children()) { + // each output is passed as input to the next (most relevant for blocks) + data = child.jjtAccept(this, data); + if (child instanceof ASTBlockStatement + && child.getChild(0) instanceof ASTLocalVariableDeclaration) { + ASTLocalVariableDeclaration local = (ASTLocalVariableDeclaration) child.getChild(0); + for (ASTVariableDeclaratorId id : local) { + localsToKill.add(id.getNameDeclaration()); + } + } + } + + for (VariableNameDeclaration var : localsToKill) { + ((LivenessState) data).undef(var); + } + + return data; + } + @Override public Object visit(ASTSwitchStatement node, Object data) { return processSwitch(node, (LivenessState) data, node.getTestedExpression()); @@ -122,7 +183,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (child instanceof ASTSwitchLabel) { current = before.fork().join(current); } else if (child instanceof ASTSwitchLabeledRule) { - // 'current' stays == 'before' so that the final join does nothing current = acceptOpt(child.getChild(1), before.fork()); current = breakTargets.doBreak(current, null); // process this as if it was followed by a break } else { @@ -227,7 +287,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { breakTargets.unnamedTargets.push(breakTarget); continueTargets.unnamedTargets.push(continueTarget); - JavaNode parent = loop.getNthParent(2); + Node parent = loop.getNthParent(2); while (parent instanceof ASTLabeledStatement) { String label = parent.getImage(); breakTargets.namedTargets.put(label, breakTarget); @@ -242,7 +302,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { LivenessState total = breakTarget.join(continueTarget); - JavaNode parent = loop.getNthParent(2); + Node parent = loop.getNthParent(2); while (parent instanceof ASTLabeledStatement) { String label = parent.getImage(); total = total.join(breakTargets.namedTargets.remove(label)); @@ -273,13 +333,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return breakTargets.doBreak((LivenessState) data, null); } - private LivenessState doBreak(LivenessState data, Deque targets) { - // basically, assignments that are live at the point of the break - // are also live after the break (wherever it lands) - targets.push(targets.getFirst().join(data)); - return data.abruptCompletion(); - } - // both of those exit the scope of the method/ctor, so their assignments go dead @@ -337,13 +390,13 @@ public class UnusedAssignmentRule extends AbstractJavaRule { ((LivenessState) data).use(lhsVar); } - ((LivenessState) data).assign(lhsVar, rhs); + result.assign(lhsVar, rhs); } else { result = acceptOpt(node.getChild(0), result); } return result; } else { - return super.visit(node, data); + return visit(node, data); } } @@ -396,14 +449,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (suffix.isArguments() || suffix.isArrayDereference()) { return null; } - return findVar(primary.getScope(), true, substringBeforeFirst(suffix.getImage(), '.')); + return findVar(primary.getScope(), true, firstIdent(suffix.getImage())); } else { if (inLhs && primary.getNumChildren() > 1) { return null; } if (prefix.getChild(0) instanceof ASTName) { - return findVar(prefix.getScope(), false, substringBeforeFirst(prefix.getChild(0).getImage(), '.')); + return findVar(prefix.getScope(), false, firstIdent(prefix.getChild(0).getImage())); } } } @@ -411,8 +464,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return null; } - private static String substringBeforeFirst(String str, char delim) { - int i = str.indexOf(delim); + private static String firstIdent(String str) { + int i = str.indexOf('.'); return i < 0 ? str : str.substring(0, i); } @@ -443,23 +496,43 @@ public class UnusedAssignmentRule extends AbstractJavaRule { final Set allAssignments; final Set usedAssignments; + final Map> killRecord; + final Map> liveAssignments; + private LivenessState() { - this(new HashSet(), new HashSet(), new HashMap>()); + this(new HashSet(), + new HashSet(), + new HashMap>(), + new HashMap>()); } private LivenessState(Set allAssignments, Set usedAssignments, + Map> killRecord, Map> liveAssignments) { this.allAssignments = allAssignments; this.usedAssignments = usedAssignments; + this.killRecord = killRecord; this.liveAssignments = liveAssignments; } void assign(VariableNameDeclaration var, JavaNode rhs) { AssignmentEntry entry = new AssignmentEntry(var, rhs); - liveAssignments.put(var, Collections.singleton(entry)); // kills the previous value + // kills the previous value + Set killed = liveAssignments.put(var, Collections.singleton(entry)); + if (killed != null) { + for (AssignmentEntry k : killed) { + // computeIfAbsent + Set killers = killRecord.get(k); + if (killers == null) { + killers = new HashSet<>(1); + killRecord.put(k, killers); + } + killers.add(entry); + } + } allAssignments.add(entry); } @@ -471,12 +544,16 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } + void undef(VariableNameDeclaration var) { + liveAssignments.remove(var); + } + LivenessState fork() { - return new LivenessState(this.allAssignments, this.usedAssignments, new HashMap<>(this.liveAssignments)); + return new LivenessState(this.allAssignments, this.usedAssignments, this.killRecord, new HashMap<>(this.liveAssignments)); } LivenessState forkEmpty() { - return new LivenessState(this.allAssignments, this.usedAssignments, new HashMap<>()); + return new LivenessState(this.allAssignments, this.usedAssignments, this.killRecord, new HashMap>()); } LivenessState abruptCompletion() { @@ -520,13 +597,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { this.liveAssignments.put(var, newLive); } - LivenessState join(Iterable scopes) { - for (LivenessState sub : scopes) { - this.join(sub); - } - return this; - } - @Override public String toString() { return liveAssignments.toString(); diff --git a/pmd-java/src/main/resources/category/java/errorprone.xml b/pmd-java/src/main/resources/category/java/errorprone.xml index fb0559eef5..569af9fa6c 100644 --- a/pmd-java/src/main/resources/category/java/errorprone.xml +++ b/pmd-java/src/main/resources/category/java/errorprone.xml @@ -3280,7 +3280,7 @@ public String convert(int x) { diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index c7cc3fe0a7..9b364f8e10 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -23,7 +23,7 @@ public class Foo { 1 3 - The value assigned to variable 'i' is never used + The value assigned to variable 'i' is never used (overwritten on line 4) 3 3,4,6 - The value assigned to variable 'j' is never used - The value assigned to variable 'z' is never used - The value assigned to variable 'j' is never used + The value assigned to variable 'j' is never used (overwritten on line 6) + The value assigned to variable 'z' is never used (goes out of scope) + The value assigned to variable 'j' is never used (goes out of scope) 1 4 - The value assigned to variable 'z' is never used + The value assigned to variable 'z' is never used (goes out of scope) 1 3 - The value assigned to variable 'j' is never used + The value assigned to variable 'j' is never used (overwritten on lines 6 and 8) 2 3,6 - The value assigned to variable 'j' is never used - The value assigned to variable 'j' is never used + The value assigned to variable 'j' is never used (overwritten on lines 6 and 9) + The value assigned to variable 'j' is never used (goes out of scope) 2 3,6 - The value assigned to variable 'j' is never used - The value assigned to variable 'j' is never used + The value assigned to variable 'j' is never used (overwritten on lines 6 and 9) + The value assigned to variable 'j' is never used (goes out of scope) - #1393 PMD hanging during DataflowAnomalyAnalysis + Local variable in loop 2 10,19 - The value assigned to variable 'fail' is never used - The value assigned to variable 'fail' is never used + The value assigned to variable 'fail' is never used (overwritten on line 19) + The value assigned to variable 'fail' is never used (overwritten on line 19) 1 6 - The value assigned to variable 'b' is never used + The value assigned to variable 'b' is never used (goes out of scope) 2 3,5 - The value assigned to variable 'a' is never used - The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used (overwritten on line 5) + + The value assigned to variable 'a' is never used (overwritten on line 5) 2 4,7 - The value assigned to variable 'i' is never used - The value assigned to variable 'i' is never used + The value assigned to variable 'i' is never used (overwritten on line 7) + The value assigned to variable 'i' is never used (overwritten on line 7) 1 7 - The value assigned to variable 'i' is never used + The value assigned to variable 'i' is never used (goes out of scope) 1 12 - The value assigned to variable 'i' is never used + The value assigned to variable 'i' is never used (overwritten on line 16) 1 12 - The value assigned to variable 'i' is never used + The value assigned to variable 'i' is never used (overwritten on line 19) 1 7 - The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used (goes out of scope) 1 3 - The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used (overwritten on line 6) 2 7,8 - The value assigned to variable 'i' is never used - The value assigned to variable 'a' is never used + The value assigned to variable 'i' is never used (goes out of scope) + The value assigned to variable 'a' is never used (goes out of scope) 4 6,8,10,12 - The value assigned to variable 'a' is never used - The value assigned to variable 'a' is never used - The value assigned to variable 'a' is never used - The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used (goes out of scope) + The value assigned to variable 'a' is never used (goes out of scope) + The value assigned to variable 'a' is never used (goes out of scope) + The value assigned to variable 'a' is never used (goes out of scope) 1 6 - The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used (overwritten on line 8) 1 9 - The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used (goes out of scope) 1 9 - The value assigned to variable 'i' is never used + The value assigned to variable 'i' is never used (goes out of scope) 4 6,7,8,9 - The value assigned to variable 'a' is never used - The value assigned to variable 'a' is never used - The value assigned to variable 'a' is never used - The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used (overwritten on line 4) + The value assigned to variable 'a' is never used (overwritten on line 4) + The value assigned to variable 'a' is never used (overwritten on line 4) + The value assigned to variable 'a' is never used (overwritten on line 4) 4 6,9,13,14 - The value assigned to variable 'a' is never used - The value assigned to variable 'a' is never used - The value assigned to variable 'a' is never used - The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used (overwritten on line 4) + The value assigned to variable 'a' is never used (overwritten on line 4) + The value assigned to variable 'a' is never used (overwritten on line 4) + The value assigned to variable 'a' is never used (overwritten on line 4) 1 5 - The value assigned to variable 't1' is never used + The value assigned to variable 't1' is never used (goes out of scope) 1 6 - The value assigned to variable 't1' is never used + The value assigned to variable 't1' is never used (goes out of scope) 1 7 - The value assigned to variable 't1' is never used + The value assigned to variable 't1' is never used (goes out of scope) 2 4,6 - The value assigned to variable 't2' is never used - The value assigned to variable 't1' is never used + The value assigned to variable 't2' is never used (goes out of scope) + The value assigned to variable 't1' is never used (goes out of scope) 1 4 - The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used (goes out of scope) 1 4 - The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used (goes out of scope) 2 3,5 - The value assigned to variable 'iter' is never used - The value assigned to variable 'iter' is never used + The value assigned to variable 'iter' is never used (overwritten on line 4) + The value assigned to variable 'iter' is never used (goes out of scope) Date: Sat, 20 Jun 2020 09:22:46 +0200 Subject: [PATCH 20/99] Change terminology --- .../rule/errorprone/UnusedAssignmentRule.java | 211 +++++++++--------- 1 file changed, 106 insertions(+), 105 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 7e54d98b4e..153e64b70d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -70,20 +70,16 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTMethodDeclaration node, Object data) { - // This analysis can be used to check for unused variables easily - // See reverted commit somewhere in the PR - LivenessState bodyData = new LivenessState(); + AlgoState endState = (AlgoState) node.getBody().jjtAccept(new ReachingDefsVisitor(), new AlgoState()); - LivenessState endData = (LivenessState) node.getBody().jjtAccept(new LivenessVisitor(), bodyData); - - if (endData.usedAssignments.size() < endData.allAssignments.size()) { - HashSet unused = new HashSet<>(endData.allAssignments); - unused.removeAll(endData.usedAssignments); + if (endState.usedAssignments.size() < endState.allAssignments.size()) { + HashSet unused = new HashSet<>(endState.allAssignments); + unused.removeAll(endState.usedAssignments); // allAssignments is the unused assignments now for (AssignmentEntry entry : unused) { - Set killers = endData.killRecord.get(entry); + Set killers = endState.killRecord.get(entry); if (killers == null || killers.isEmpty()) { addViolation(data, entry.rhs, new Object[] {entry.var.getImage(), "goes out of scope"}); } else if (killers.size() == 1) { @@ -118,7 +114,12 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return sb.toString(); } - private static class LivenessVisitor extends JavaParserVisitorAdapter { + private static class ReachingDefsVisitor extends JavaParserVisitorAdapter { + + // This analysis can be trivially used to check for unused variables, + // in the absence of global variable usage pre-resolution (which in + // 7.0 is not implemented yet and maybe won't be). + // See reverted commit somewhere in the PR private final TargetStack breakTargets = new TargetStack(); // continue jumps to the condition check, while break jumps to after the loop @@ -156,7 +157,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } for (VariableNameDeclaration var : localsToKill) { - ((LivenessState) data).undef(var); + ((AlgoState) data).undef(var); } return data; @@ -164,20 +165,20 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTSwitchStatement node, Object data) { - return processSwitch(node, (LivenessState) data, node.getTestedExpression()); + return processSwitch(node, (AlgoState) data, node.getTestedExpression()); } @Override public Object visit(ASTSwitchExpression node, Object data) { - return processSwitch(node, (LivenessState) data, node.getChild(0)); + return processSwitch(node, (AlgoState) data, node.getChild(0)); } - private LivenessState processSwitch(JavaNode switchLike, LivenessState data, JavaNode testedExpr) { - LivenessState before = acceptOpt(testedExpr, data); + private AlgoState processSwitch(JavaNode switchLike, AlgoState data, JavaNode testedExpr) { + AlgoState before = acceptOpt(testedExpr, data); breakTargets.push(before.fork()); - LivenessState current = before; + AlgoState current = before; for (int i = 1; i < switchLike.getNumChildren(); i++) { JavaNode child = switchLike.getChild(i); if (child instanceof ASTSwitchLabel) { @@ -200,22 +201,22 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTIfStatement node, Object data) { - LivenessState before = acceptOpt(node.getCondition(), (LivenessState) data); + AlgoState before = acceptOpt(node.getCondition(), (AlgoState) data); - LivenessState thenData = acceptOpt(node.getThenBranch(), before.fork()); - LivenessState elseData = acceptOpt(node.getElseBranch(), before.fork()); + AlgoState thenState = acceptOpt(node.getThenBranch(), before.fork()); + AlgoState elseState = acceptOpt(node.getElseBranch(), before.fork()); - return thenData.join(elseData); + return thenState.join(elseState); } @Override public Object visit(ASTWhileStatement node, Object data) { - return handleLoop(node, (LivenessState) data, null, node.getCondition(), null, node.getBody(), true); + return handleLoop(node, (AlgoState) data, null, node.getCondition(), null, node.getBody(), true); } @Override public Object visit(ASTDoStatement node, Object data) { - return handleLoop(node, (LivenessState) data, null, node.getCondition(), null, node.getBody(), false); + return handleLoop(node, (AlgoState) data, null, node.getCondition(), null, node.getBody(), false); } @Override @@ -224,23 +225,23 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (node.isForeach()) { // the iterable expression JavaNode init = node.getChild(1); - return handleLoop(node, (LivenessState) data, init, null, null, body, true); + return handleLoop(node, (AlgoState) data, init, null, null, body, true); } else { ASTForInit init = node.getFirstChildOfType(ASTForInit.class); ASTExpression cond = node.getCondition(); ASTForUpdate update = node.getFirstChildOfType(ASTForUpdate.class); - return handleLoop(node, (LivenessState) data, init, cond, update, body, true); + return handleLoop(node, (AlgoState) data, init, cond, update, body, true); } } - private LivenessState handleLoop(JavaNode loop, - LivenessState before, - JavaNode init, - JavaNode cond, - JavaNode update, - JavaNode body, - boolean checkFirstIter) { + private AlgoState handleLoop(JavaNode loop, + AlgoState before, + JavaNode init, + JavaNode cond, + JavaNode update, + JavaNode body, + boolean checkFirstIter) { // perform a few "iterations", to make sure that assignments in // the body can affect themselves in the next iteration, and @@ -251,13 +252,13 @@ public class UnusedAssignmentRule extends AbstractJavaRule { before = acceptOpt(cond, before); } - LivenessState breakTarget = before.forkEmpty(); - LivenessState continueTarget = before.forkEmpty(); + AlgoState breakTarget = before.forkEmpty(); + AlgoState continueTarget = before.forkEmpty(); pushTargets(loop, breakTarget, continueTarget); - LivenessState iter = acceptOpt(body, before.fork()); - // make the body live in the other parts of the loop, + AlgoState iter = acceptOpt(body, before.fork()); + // make the defs of the body reach the other parts of the loop, // including itself iter = acceptOpt(update, iter); iter = acceptOpt(cond, iter); @@ -266,15 +267,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { breakTarget = breakTargets.peek(); continueTarget = continueTargets.peek(); - if (!continueTarget.liveAssignments.isEmpty()) { - // make assignments that are only live before a continue - // live inside the other parts of the loop + if (!continueTarget.reachingDefs.isEmpty()) { + // make assignments before a continue reach the other parts of the loop continueTarget = acceptOpt(cond, continueTarget); continueTarget = acceptOpt(body, continueTarget); continueTarget = acceptOpt(update, continueTarget); } - LivenessState result = popTargets(loop, breakTarget, continueTarget); + AlgoState result = popTargets(loop, breakTarget, continueTarget); result = result.join(iter); if (checkFirstIter) { result = result.join(before); @@ -283,7 +283,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return result; } - private void pushTargets(JavaNode loop, LivenessState breakTarget, LivenessState continueTarget) { + private void pushTargets(JavaNode loop, AlgoState breakTarget, AlgoState continueTarget) { breakTargets.unnamedTargets.push(breakTarget); continueTargets.unnamedTargets.push(continueTarget); @@ -296,11 +296,11 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } - private LivenessState popTargets(JavaNode loop, LivenessState breakTarget, LivenessState continueTarget) { + private AlgoState popTargets(JavaNode loop, AlgoState breakTarget, AlgoState continueTarget) { breakTargets.unnamedTargets.pop(); continueTargets.unnamedTargets.pop(); - LivenessState total = breakTarget.join(continueTarget); + AlgoState total = breakTarget.join(continueTarget); Node parent = loop.getNthParent(2); while (parent instanceof ASTLabeledStatement) { @@ -312,25 +312,25 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return total; } - private LivenessState acceptOpt(JavaNode node, LivenessState before) { - return node == null ? before : (LivenessState) node.jjtAccept(this, before); + private AlgoState acceptOpt(JavaNode node, AlgoState before) { + return node == null ? before : (AlgoState) node.jjtAccept(this, before); } @Override public Object visit(ASTContinueStatement node, Object data) { - return continueTargets.doBreak((LivenessState) data, node.getImage()); + return continueTargets.doBreak((AlgoState) data, node.getImage()); } @Override public Object visit(ASTBreakStatement node, Object data) { - return breakTargets.doBreak((LivenessState) data, node.getImage()); + return breakTargets.doBreak((AlgoState) data, node.getImage()); } @Override public Object visit(ASTYieldStatement node, Object data) { super.visit(node, data); // visit expression - // treat as break, ie abrupt completion + link live vars to outer context - return breakTargets.doBreak((LivenessState) data, null); + // treat as break, ie abrupt completion + link reaching defs to outer context + return breakTargets.doBreak((AlgoState) data, null); } @@ -339,13 +339,13 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTThrowStatement node, Object data) { super.visit(node, data); - return ((LivenessState) data).abruptCompletion(); + return ((AlgoState) data).abruptCompletion(); } @Override public Object visit(ASTReturnStatement node, Object data) { super.visit(node, data); - return ((LivenessState) data).abruptCompletion(); + return ((AlgoState) data).abruptCompletion(); } // following deals with assignment @@ -357,7 +357,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { ASTVariableInitializer rhs = node.getInitializer(); if (rhs != null) { rhs.jjtAccept(this, data); - ((LivenessState) data).assign(var, rhs); + ((AlgoState) data).assign(var, rhs); } return data; } @@ -374,7 +374,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } public Object checkAssignment(JavaNode node, Object data) { - LivenessState result = (LivenessState) data; + AlgoState result = (AlgoState) data; if (node.getNumChildren() == 3) { // assignment assert node.getChild(1) instanceof ASTAssignmentOperator; @@ -387,7 +387,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (lhsVar != null) { if (node.getChild(1).getImage().length() >= 2) { // compound assignment, to use BEFORE assigning - ((LivenessState) data).use(lhsVar); + ((AlgoState) data).use(lhsVar); } result.assign(lhsVar, rhs); @@ -402,20 +402,20 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTPreDecrementExpression node, Object data) { - return checkIncOrDecrement(node, (LivenessState) data); + return checkIncOrDecrement(node, (AlgoState) data); } @Override public Object visit(ASTPreIncrementExpression node, Object data) { - return checkIncOrDecrement(node, (LivenessState) data); + return checkIncOrDecrement(node, (AlgoState) data); } @Override public Object visit(ASTPostfixExpression node, Object data) { - return checkIncOrDecrement(node, (LivenessState) data); + return checkIncOrDecrement(node, (AlgoState) data); } - private LivenessState checkIncOrDecrement(JavaNode unary, LivenessState data) { + private AlgoState checkIncOrDecrement(JavaNode unary, AlgoState data) { VariableNameDeclaration var = getLhsVar(unary.getChild(0), true); if (var != null) { data.use(var); @@ -432,7 +432,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { VariableNameDeclaration var = getLhsVar(node, false); if (var != null) { - ((LivenessState) data).use(var); + ((AlgoState) data).use(var); } return data; } @@ -491,37 +491,38 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } - private static class LivenessState { + private static class AlgoState { + // Those 3 are shared between all the forked states final Set allAssignments; final Set usedAssignments; - final Map> killRecord; - final Map> liveAssignments; + // Map of var -> reaching(var) + final Map> reachingDefs; - private LivenessState() { + private AlgoState() { this(new HashSet(), new HashSet(), new HashMap>(), new HashMap>()); } - private LivenessState(Set allAssignments, - Set usedAssignments, - Map> killRecord, - Map> liveAssignments) { + private AlgoState(Set allAssignments, + Set usedAssignments, + Map> killRecord, + Map> reachingDefs) { this.allAssignments = allAssignments; this.usedAssignments = usedAssignments; this.killRecord = killRecord; - this.liveAssignments = liveAssignments; + this.reachingDefs = reachingDefs; } void assign(VariableNameDeclaration var, JavaNode rhs) { AssignmentEntry entry = new AssignmentEntry(var, rhs); // kills the previous value - Set killed = liveAssignments.put(var, Collections.singleton(entry)); + Set killed = reachingDefs.put(var, Collections.singleton(entry)); if (killed != null) { for (AssignmentEntry k : killed) { // computeIfAbsent @@ -537,97 +538,97 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } void use(VariableNameDeclaration var) { - Set live = liveAssignments.get(var); + Set reaching = reachingDefs.get(var); // may be null for implicit assignments, like method parameter - if (live != null) { - usedAssignments.addAll(live); + if (reaching != null) { + usedAssignments.addAll(reaching); } } void undef(VariableNameDeclaration var) { - liveAssignments.remove(var); + reachingDefs.remove(var); } - LivenessState fork() { - return new LivenessState(this.allAssignments, this.usedAssignments, this.killRecord, new HashMap<>(this.liveAssignments)); + AlgoState fork() { + return new AlgoState(this.allAssignments, this.usedAssignments, this.killRecord, new HashMap<>(this.reachingDefs)); } - LivenessState forkEmpty() { - return new LivenessState(this.allAssignments, this.usedAssignments, this.killRecord, new HashMap>()); + AlgoState forkEmpty() { + return new AlgoState(this.allAssignments, this.usedAssignments, this.killRecord, new HashMap>()); } - LivenessState abruptCompletion() { - this.liveAssignments.clear(); + AlgoState abruptCompletion() { + this.reachingDefs.clear(); return this; } - LivenessState join(LivenessState sub) { - // Merge live assignments of forked scopes - if (sub == this || sub.liveAssignments.isEmpty()) { + AlgoState join(AlgoState sub) { + // Merge reaching des of the other scope into this + if (sub == this || sub.reachingDefs.isEmpty()) { return this; } - for (VariableNameDeclaration var : this.liveAssignments.keySet()) { - Set myAssignments = this.liveAssignments.get(var); - Set subScopeAssignments = sub.liveAssignments.get(var); + for (VariableNameDeclaration var : this.reachingDefs.keySet()) { + Set myAssignments = this.reachingDefs.get(var); + Set subScopeAssignments = sub.reachingDefs.get(var); if (subScopeAssignments == null) { continue; } - joinLive(var, myAssignments, subScopeAssignments); + joinSets(var, myAssignments, subScopeAssignments); } - for (VariableNameDeclaration var : sub.liveAssignments.keySet()) { - Set subScopeAssignments = sub.liveAssignments.get(var); - Set myAssignments = this.liveAssignments.get(var); + for (VariableNameDeclaration var : sub.reachingDefs.keySet()) { + Set subScopeAssignments = sub.reachingDefs.get(var); + Set myAssignments = this.reachingDefs.get(var); if (myAssignments == null) { - this.liveAssignments.put(var, subScopeAssignments); + this.reachingDefs.put(var, subScopeAssignments); continue; } - joinLive(var, myAssignments, subScopeAssignments); + joinSets(var, myAssignments, subScopeAssignments); } return this; } - private void joinLive(VariableNameDeclaration var, Set live1, Set live2) { - Set newLive = new HashSet<>(live1.size() + live2.size()); - newLive.addAll(live2); - newLive.addAll(live1); - this.liveAssignments.put(var, newLive); + private void joinSets(VariableNameDeclaration var, Set set1, Set set2) { + Set newReaching = new HashSet<>(set1.size() + set2.size()); + newReaching.addAll(set2); + newReaching.addAll(set1); + this.reachingDefs.put(var, newReaching); } @Override public String toString() { - return liveAssignments.toString(); + return reachingDefs.toString(); } } static class TargetStack { - final Deque unnamedTargets = new ArrayDeque<>(); - final Map namedTargets = new HashMap<>(); + final Deque unnamedTargets = new ArrayDeque<>(); + final Map namedTargets = new HashMap<>(); - void push(LivenessState state) { + void push(AlgoState state) { unnamedTargets.push(state); } - LivenessState pop() { + AlgoState pop() { return unnamedTargets.pop(); } - LivenessState peek() { + AlgoState peek() { return unnamedTargets.getFirst(); } - LivenessState doBreak(LivenessState data,/* nullable */ String label) { - // basically, assignments that are live at the point of the break - // are also live after the break (wherever it lands) + AlgoState doBreak(AlgoState data,/* nullable */ String label) { + // basically, reaching defs at the point of the break + // also reach after the break (wherever it lands) if (label == null) { unnamedTargets.push(unnamedTargets.getFirst().join(data)); } else { - LivenessState target = namedTargets.get(label); + AlgoState target = namedTargets.get(label); if (target != null) { // otherwise CT error target.join(data); From 972610fe898c7c308f716ff423511bef295cf13c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 20 Jun 2020 10:39:32 +0200 Subject: [PATCH 21/99] Try/catch --- .../rule/errorprone/UnusedAssignmentRule.java | 84 ++++++++++++--- .../rule/errorprone/xml/UnusedAssignment.xml | 100 ++++++++++++++++++ 2 files changed, 169 insertions(+), 15 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 153e64b70d..e08645b28c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -21,9 +21,11 @@ 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.ASTBreakStatement; +import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement; import net.sourceforge.pmd.lang.java.ast.ASTContinueStatement; import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; import net.sourceforge.pmd.lang.java.ast.ASTExpression; +import net.sourceforge.pmd.lang.java.ast.ASTFinallyStatement; import net.sourceforge.pmd.lang.java.ast.ASTForInit; import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTForUpdate; @@ -38,6 +40,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTPreIncrementExpression; 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.ASTResourceSpecification; import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; import net.sourceforge.pmd.lang.java.ast.ASTStatement; import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; @@ -46,6 +49,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel; import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabeledRule; import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; +import net.sourceforge.pmd.lang.java.ast.ASTTryStatement; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; @@ -125,6 +129,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // continue jumps to the condition check, while break jumps to after the loop private final TargetStack continueTargets = new TargetStack(); + private final Deque normalFinallies = new ArrayDeque<>(); + // following deals with control flow @Override @@ -209,6 +215,37 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return thenState.join(elseState); } + @Override + public Object visit(ASTTryStatement node, Object data) { + final AlgoState before = (AlgoState) data; + ASTFinallyStatement finallyClause = node.getFinallyClause(); + + if (finallyClause != null) { + before.myFinally = before.forkEmpty(); + } + + ASTResourceSpecification resources = node.getFirstChildOfType(ASTResourceSpecification.class); + + AlgoState bodyState = acceptOpt(resources, before.fork()); + bodyState = acceptOpt(node.getBody(), bodyState); + + AlgoState exceptionalState = null; + for (ASTCatchStatement catchClause : node.getCatchClauses()) { + AlgoState current = acceptOpt(catchClause, before.fork()); + exceptionalState = current.join(exceptionalState); + } + + AlgoState finalState; + if (finallyClause != null) { + finalState = before.myFinally.join(bodyState).join(exceptionalState); + finalState = acceptOpt(finallyClause, finalState); + before.myFinally = null; + } else { + finalState = bodyState.join(exceptionalState); + } + return finalState; + } + @Override public Object visit(ASTWhileStatement node, Object data) { return handleLoop(node, (AlgoState) data, null, node.getCondition(), null, node.getBody(), true); @@ -339,13 +376,13 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTThrowStatement node, Object data) { super.visit(node, data); - return ((AlgoState) data).abruptCompletion(); + return ((AlgoState) data).abruptCompletion(null); } @Override public Object visit(ASTReturnStatement node, Object data) { super.visit(node, data); - return ((AlgoState) data).abruptCompletion(); + return ((AlgoState) data).abruptCompletion(null); } // following deals with assignment @@ -493,6 +530,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private static class AlgoState { + final AlgoState parent; + // Those 3 are shared between all the forked states final Set allAssignments; final Set usedAssignments; @@ -501,18 +540,23 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // Map of var -> reaching(var) final Map> reachingDefs; + AlgoState myFinally = null; + private AlgoState() { - this(new HashSet(), + this(null, + new HashSet(), new HashSet(), new HashMap>(), new HashMap>()); } - private AlgoState(Set allAssignments, + private AlgoState(AlgoState parent, + Set allAssignments, Set usedAssignments, Map> killRecord, Map> reachingDefs) { + this.parent = parent; this.allAssignments = allAssignments; this.usedAssignments = usedAssignments; this.killRecord = killRecord; @@ -550,14 +594,23 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } AlgoState fork() { - return new AlgoState(this.allAssignments, this.usedAssignments, this.killRecord, new HashMap<>(this.reachingDefs)); + return new AlgoState(this, this.allAssignments, this.usedAssignments, this.killRecord, new HashMap<>(this.reachingDefs)); } AlgoState forkEmpty() { - return new AlgoState(this.allAssignments, this.usedAssignments, this.killRecord, new HashMap>()); + return new AlgoState(this, this.allAssignments, this.usedAssignments, this.killRecord, new HashMap>()); } - AlgoState abruptCompletion() { + AlgoState abruptCompletion(AlgoState target) { + // if target == null then this will unwind all the parents + AlgoState parent = this; + while (parent != target && parent != null) { + if (parent.myFinally != null) { + parent.myFinally.join(this); + } + parent = parent.parent; + } + this.reachingDefs.clear(); return this; } @@ -565,7 +618,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { AlgoState join(AlgoState sub) { // Merge reaching des of the other scope into this - if (sub == this || sub.reachingDefs.isEmpty()) { + if (sub == this || sub == null || sub.reachingDefs.isEmpty()) { return this; } @@ -625,16 +678,17 @@ public class UnusedAssignmentRule extends AbstractJavaRule { AlgoState doBreak(AlgoState data,/* nullable */ String label) { // basically, reaching defs at the point of the break // also reach after the break (wherever it lands) + AlgoState target; if (label == null) { - unnamedTargets.push(unnamedTargets.getFirst().join(data)); + target = unnamedTargets.getFirst(); } else { - AlgoState target = namedTargets.get(label); - if (target != null) { - // otherwise CT error - target.join(data); - } + target = namedTargets.get(label); } - return data.abruptCompletion(); + + if (target != null) { // otherwise CT error + target.join(data); + } + return data.abruptCompletion(target); } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 9b364f8e10..44103f420e 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -1073,4 +1073,104 @@ public class Foo { } ]]> + + + Try/catch + 1 + 4 + + The value assigned to variable 'a' is never used (overwritten on lines 6 and 8) + + + + + + Try/catch finally + 1 + 4 + + The value assigned to variable 'a' is never used (overwritten on lines 6 and 8) + + + + + Try/catch finally 3 + 1 + 4 + + The value assigned to variable 'a' is never used (overwritten on lines 6 and 8) + + + + + Try/catch finally in loop + 0 + 10) { + try (Reader r = new StringReader("")) { + r.read(); + } catch (IOException e) { + a = -1; // used in finally even if break + break; + } finally { + a++; + } + } + return a; + } + +} + ]]> + From e228e5590fd0a9a029f74cd43e0a9f2f7b4997b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 20 Jun 2020 12:29:15 +0200 Subject: [PATCH 22/99] Improve finally --- .../pmd/lang/java/ast/ASTTryStatement.java | 2 +- .../rule/errorprone/UnusedAssignmentRule.java | 77 +++++++++++++------ .../rule/errorprone/xml/UnusedAssignment.xml | 41 ++++++++-- 3 files changed, 91 insertions(+), 29 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTryStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTryStatement.java index a304d3f9b6..6fad0e2722 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTryStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTryStatement.java @@ -49,7 +49,7 @@ public class ASTTryStatement extends AbstractJavaNode { * Returns the body of this try statement. */ public ASTBlock getBody() { - return (ASTBlock) getChild(1); + return (ASTBlock) getFirstChildOfType(ASTBlock.class); } /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index e08645b28c..aea87df5a5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -39,7 +39,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTPreDecrementExpression; import net.sourceforge.pmd.lang.java.ast.ASTPreIncrementExpression; 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.ASTResourceSpecification; import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; import net.sourceforge.pmd.lang.java.ast.ASTStatement; @@ -74,6 +73,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTMethodDeclaration node, Object data) { + if (node.isAbstract()) { + return data; + } AlgoState endState = (AlgoState) node.getBody().jjtAccept(new ReachingDefsVisitor(), new AlgoState()); @@ -220,6 +222,26 @@ public class UnusedAssignmentRule extends AbstractJavaRule { final AlgoState before = (AlgoState) data; ASTFinallyStatement finallyClause = node.getFinallyClause(); + /* + + try () { + + } catch (IOException e) { + + } finally { + + } + + + There is a path -> -> -> -> + and for each catch, -> -> -> + + Except that abrupt completion before the jumps + to the and completes abruptly for the same + reason (if the completes normally), which + means it doesn't go to + */ + if (finallyClause != null) { before.myFinally = before.forkEmpty(); } @@ -236,12 +258,17 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } AlgoState finalState; + finalState = bodyState.join(exceptionalState); if (finallyClause != null) { - finalState = before.myFinally.join(bodyState).join(exceptionalState); - finalState = acceptOpt(finallyClause, finalState); + // this represents the finally clause when it was entered + // because of abrupt completion + // since we don't know when it terminated we must join it with before + AlgoState abruptFinally = before.myFinally.join(before); + acceptOpt(finallyClause, abruptFinally); before.myFinally = null; - } else { - finalState = bodyState.join(exceptionalState); + + // this is the normal finally + finalState = acceptOpt(finallyClause, finalState); } return finalState; } @@ -478,22 +505,23 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (primary instanceof ASTPrimaryExpression) { ASTPrimaryPrefix prefix = (ASTPrimaryPrefix) primary.getChild(0); - if (prefix.usesThisModifier()) { - if (primary.getNumChildren() != 2 && inLhs) { - return null; - } - ASTPrimarySuffix suffix = (ASTPrimarySuffix) primary.getChild(1); - if (suffix.isArguments() || suffix.isArrayDereference()) { - return null; - } - return findVar(primary.getScope(), true, firstIdent(suffix.getImage())); - } else { + // if (prefix.usesThisModifier()) { + // if (primary.getNumChildren() < 2) { + // return null; + // } + // ASTPrimarySuffix suffix = (ASTPrimarySuffix) primary.getChild(1); + // if (suffix.isArguments() || suffix.isArrayDereference() || suffix.getImage() == null) { + // return null; + // } + // return findVar(primary.getScope(), true, firstIdent(suffix.getImage())); + // } else + { if (inLhs && primary.getNumChildren() > 1) { return null; } - if (prefix.getChild(0) instanceof ASTName) { - return findVar(prefix.getScope(), false, firstIdent(prefix.getChild(0).getImage())); + if (prefix.getNumChildren() > 0 && (prefix.getChild(0) instanceof ASTName)) { + return findVar(prefix.getScope(), identOf(inLhs, prefix.getChild(0).getImage())); } } } @@ -501,22 +529,25 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return null; } - private static String firstIdent(String str) { + private static String identOf(boolean inLhs, String str) { int i = str.indexOf('.'); - return i < 0 ? str : str.substring(0, i); + return i < 0 ? str + : inLhs ? null + : str.substring(0, i); } - private VariableNameDeclaration findVar(Scope scope, boolean isThis, String name) { + private VariableNameDeclaration findVar(Scope scope, String name) { if (name == null) { return null; } - if (isThis) { - scope = scope.getEnclosingScope(ClassScope.class); - } while (scope != null) { for (VariableNameDeclaration decl : scope.getDeclarations(VariableNameDeclaration.class).keySet()) { if (decl.getImage().equals(name)) { + if (scope instanceof ClassScope) { + // don't handle fields + return null; + } return decl; } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 44103f420e..09c686d865 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -1099,11 +1099,7 @@ public class Foo { Try/catch finally - 1 - 4 - - The value assigned to variable 'a' is never used (overwritten on lines 6 and 8) - + 0 + + + Abstract method NPE + 0 + + + + FP in finally + 0 + From 32392ed27e77669dbc1731eeeba39cf7bb1350f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 21 Jun 2020 15:30:41 +0200 Subject: [PATCH 23/99] Lambda bug --- .../rule/errorprone/UnusedAssignmentRule.java | 75 ++++++++++++++++--- .../rule/errorprone/xml/UnusedAssignment.xml | 68 ++++++++++++++++- 2 files changed, 130 insertions(+), 13 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index aea87df5a5..adc4a4e464 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -31,6 +31,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTForUpdate; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTLabeledStatement; +import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTName; @@ -39,6 +40,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTPreDecrementExpression; import net.sourceforge.pmd.lang.java.ast.ASTPreIncrementExpression; 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.ASTResourceSpecification; import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; import net.sourceforge.pmd.lang.java.ast.ASTStatement; @@ -65,8 +67,17 @@ public class UnusedAssignmentRule extends AbstractJavaRule { /* TODO - constructors + initializers - try/catch + * constructors + initializers + * labels on arbitrary statements + + DONE + * conditionals + * loops + * switch + * loop labels + * try/catch/finally + * lambdas + */ @@ -273,6 +284,24 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return finalState; } + @Override + public Object visit(ASTLambdaExpression node, Object data) { + // Lambda expression have control flow that is separate from the method + // So we fork the context, but don't join it + + // Reaching definitions of the enclosing context still reach in the lambda + // Since those definitions are [effectively] final, they actually can't be + // killed, but they can be used in the lambda + + AlgoState before = (AlgoState) data; + + JavaNode lambdaBody = node.getChild(node.getNumChildren() - 1); + // if it's an expression, then no assignments may occur in it, + // but it can still use some variables of the context + acceptOpt(lambdaBody, before.forkLambda()); + return before; + } + @Override public Object visit(ASTWhileStatement node, Object data) { return handleLoop(node, (AlgoState) data, null, node.getCondition(), null, node.getBody(), true); @@ -312,7 +341,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // that they affect the condition, etc before = acceptOpt(init, before); - if (checkFirstIter) { + if (checkFirstIter) { // false for do-while before = acceptOpt(cond, before); } @@ -341,6 +370,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { AlgoState result = popTargets(loop, breakTarget, continueTarget); result = result.join(iter); if (checkFirstIter) { + // if the first iteration is checked, + // then it could be false on the first try, meaning + // the definitions before the loop reach after too result = result.join(before); } @@ -449,6 +481,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { VariableNameDeclaration lhsVar = getLhsVar(node.getChild(0), true); if (lhsVar != null) { + // in that case lhs is a normal variable (array access not supported) + if (node.getChild(1).getImage().length() >= 2) { // compound assignment, to use BEFORE assigning ((AlgoState) data).use(lhsVar); @@ -516,12 +550,17 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // return findVar(primary.getScope(), true, firstIdent(suffix.getImage())); // } else { - if (inLhs && primary.getNumChildren() > 1) { - return null; - } - if (prefix.getNumChildren() > 0 && (prefix.getChild(0) instanceof ASTName)) { - return findVar(prefix.getScope(), identOf(inLhs, prefix.getChild(0).getImage())); + String prefixImage = prefix.getChild(0).getImage(); + String varname = identOf(inLhs, prefixImage); + if (primary.getNumChildren() > 1 && !inLhs) { + ASTPrimarySuffix suffix = (ASTPrimarySuffix) primary.getChild(1); + if (suffix.isArguments()) { + // then the prefix has the method name + varname = methodLhsName(prefixImage); + } + } + return findVar(prefix.getScope(), varname); } } } @@ -536,6 +575,12 @@ public class UnusedAssignmentRule extends AbstractJavaRule { : str.substring(0, i); } + private static String methodLhsName(String name) { + int i = name.indexOf('.'); + return i < 0 ? null // no lhs, the name is just the method name + : name.substring(0, i); + } + private VariableNameDeclaration findVar(Scope scope, String name) { if (name == null) { return null; @@ -625,11 +670,21 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } AlgoState fork() { - return new AlgoState(this, this.allAssignments, this.usedAssignments, this.killRecord, new HashMap<>(this.reachingDefs)); + return doFork(this, new HashMap<>(this.reachingDefs)); } AlgoState forkEmpty() { - return new AlgoState(this, this.allAssignments, this.usedAssignments, this.killRecord, new HashMap>()); + return doFork(this, new HashMap>()); + } + + AlgoState forkLambda() { + // has no parent, so that in case of abrupt completion (return inside a lambda), + // enclosing finallies are not called + return doFork(null, new HashMap<>(this.reachingDefs)); + } + + private AlgoState doFork(AlgoState parent, Map> reaching) { + return new AlgoState(parent, this.allAssignments, this.usedAssignments, this.killRecord, reaching); } AlgoState abruptCompletion(AlgoState target) { diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 09c686d865..a5b4771406 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -348,8 +348,8 @@ class Test{ public static void main(String[] args){ int a = 0; int i = 0; - while(i < 30){ - a = a+3; + while (i < 30) { + a = a + 3; i += 3; } } @@ -370,7 +370,7 @@ class Test{ public static void main(String[] args){ int a = 0; int i = 0; // unused - while(a < 30){ + while (a < 30) { a = a + 3; i = 5; // unused (kills itself) } @@ -1201,6 +1201,68 @@ class Foo { System.out.println(oldProxy); } } +} + ]]> + + + + Lambda captured var use + 0 + decode() { + Flux> splitEvents = splitEvts(); + + return map(events -> { + return unmarshal(events.append(splitEvents)); + }); + } + +} + ]]> + + + Lambda assignment + 2 + 5,6 + + The value assigned to variable 'k' is never used (overwritten on line 6) + The value assigned to variable 'k' is never used (goes out of scope) + + { + int k = 0; + return k = 2; + }); + } + +} + ]]> + + + Lambda returns 2 + 2 + 4,7 + + The value assigned to variable 'splitEvents' is never used (goes out of scope) + The value assigned to variable 'events' is never used (goes out of scope) + + decode() { + Flux> splitEvents = splitEvts(); + + return map(events -> { + events = events.normalize(); + return dontUseEvents(); + }); + } + } ]]> From b7e5cb0182fe5b9ccf9365728f5db7cfaf55b3c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 00:13:10 +0200 Subject: [PATCH 24/99] Refactor --- .../rule/errorprone/UnusedAssignmentRule.java | 166 +++++++++++------- .../rule/errorprone/xml/UnusedAssignment.xml | 74 ++++++-- 2 files changed, 158 insertions(+), 82 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index adc4a4e464..195720c789 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -81,6 +81,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { */ + public UnusedAssignmentRule() { + addRuleChainVisit(ASTMethodDeclaration.class); + } + @Override public Object visit(ASTMethodDeclaration node, Object data) { @@ -88,15 +92,15 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return data; } - AlgoState endState = (AlgoState) node.getBody().jjtAccept(new ReachingDefsVisitor(), new AlgoState()); + AlgoState endState = (AlgoState) node.getBody().jjtAccept(ReachingDefsVisitor.INSTANCE, new AlgoState()); + GlobalAlgoState result = endState.global; - if (endState.usedAssignments.size() < endState.allAssignments.size()) { - HashSet unused = new HashSet<>(endState.allAssignments); - unused.removeAll(endState.usedAssignments); - // allAssignments is the unused assignments now + if (result.usedAssignments.size() < result.allAssignments.size()) { + HashSet unused = new HashSet<>(result.allAssignments); + unused.removeAll(result.usedAssignments); for (AssignmentEntry entry : unused) { - Set killers = endState.killRecord.get(entry); + Set killers = result.killRecord.get(entry); if (killers == null || killers.isEmpty()) { addViolation(data, entry.rhs, new Object[] {entry.var.getImage(), "goes out of scope"}); } else if (killers.size() == 1) { @@ -138,14 +142,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // 7.0 is not implemented yet and maybe won't be). // See reverted commit somewhere in the PR - private final TargetStack breakTargets = new TargetStack(); - // continue jumps to the condition check, while break jumps to after the loop - private final TargetStack continueTargets = new TargetStack(); - - private final Deque normalFinallies = new ArrayDeque<>(); - // following deals with control flow + static final ReachingDefsVisitor INSTANCE = new ReachingDefsVisitor(); + @Override public Object visit(JavaNode node, Object data) { @@ -158,14 +158,16 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } @Override - public Object visit(ASTBlock node, Object data) { + public Object visit(ASTBlock node, final Object data) { // variables local to a loop iteration must be killed before the // next iteration + + AlgoState state = (AlgoState) data; Set localsToKill = new HashSet<>(); for (JavaNode child : node.children()) { // each output is passed as input to the next (most relevant for blocks) - data = child.jjtAccept(this, data); + state = acceptOpt(child, state); if (child instanceof ASTBlockStatement && child.getChild(0) instanceof ASTLocalVariableDeclaration) { ASTLocalVariableDeclaration local = (ASTLocalVariableDeclaration) child.getChild(0); @@ -176,10 +178,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } for (VariableNameDeclaration var : localsToKill) { - ((AlgoState) data).undef(var); + state.undef(var); } - return data; + return state; } @Override @@ -193,29 +195,30 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } private AlgoState processSwitch(JavaNode switchLike, AlgoState data, JavaNode testedExpr) { + GlobalAlgoState global = data.global; AlgoState before = acceptOpt(testedExpr, data); - breakTargets.push(before.fork()); + global.breakTargets.push(before.fork()); AlgoState current = before; for (int i = 1; i < switchLike.getNumChildren(); i++) { JavaNode child = switchLike.getChild(i); if (child instanceof ASTSwitchLabel) { - current = before.fork().join(current); + current = before.fork().absorb(current); } else if (child instanceof ASTSwitchLabeledRule) { current = acceptOpt(child.getChild(1), before.fork()); - current = breakTargets.doBreak(current, null); // process this as if it was followed by a break + current = global.breakTargets.doBreak(current, null); // process this as if it was followed by a break } else { // statement in a regular fallthrough switch block current = acceptOpt(child, current); } } - before = breakTargets.pop(); + before = global.breakTargets.pop(); // join with the last state, which is the exit point of the // switch, if it's not closed by a break; - return before.join(current); + return before.absorb(current); } @Override @@ -223,9 +226,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { AlgoState before = acceptOpt(node.getCondition(), (AlgoState) data); AlgoState thenState = acceptOpt(node.getThenBranch(), before.fork()); - AlgoState elseState = acceptOpt(node.getElseBranch(), before.fork()); + AlgoState elseState = node.hasElse() ? acceptOpt(node.getElseBranch(), before.fork()) + : before; - return thenState.join(elseState); + return thenState.absorb(elseState); } @Override @@ -265,16 +269,16 @@ public class UnusedAssignmentRule extends AbstractJavaRule { AlgoState exceptionalState = null; for (ASTCatchStatement catchClause : node.getCatchClauses()) { AlgoState current = acceptOpt(catchClause, before.fork()); - exceptionalState = current.join(exceptionalState); + exceptionalState = current.absorb(exceptionalState); } AlgoState finalState; - finalState = bodyState.join(exceptionalState); + finalState = bodyState.absorb(exceptionalState); if (finallyClause != null) { // this represents the finally clause when it was entered // because of abrupt completion // since we don't know when it terminated we must join it with before - AlgoState abruptFinally = before.myFinally.join(before); + AlgoState abruptFinally = before.myFinally.absorb(before); acceptOpt(finallyClause, abruptFinally); before.myFinally = null; @@ -335,6 +339,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { JavaNode update, JavaNode body, boolean checkFirstIter) { + final GlobalAlgoState globalState = before.global; // perform a few "iterations", to make sure that assignments in // the body can affect themselves in the next iteration, and @@ -358,8 +363,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { iter = acceptOpt(body, iter); - breakTarget = breakTargets.peek(); - continueTarget = continueTargets.peek(); + breakTarget = globalState.breakTargets.peek(); + continueTarget = globalState.continueTargets.peek(); if (!continueTarget.reachingDefs.isEmpty()) { // make assignments before a continue reach the other parts of the loop continueTarget = acceptOpt(cond, continueTarget); @@ -368,41 +373,43 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } AlgoState result = popTargets(loop, breakTarget, continueTarget); - result = result.join(iter); + result = result.absorb(iter); if (checkFirstIter) { // if the first iteration is checked, // then it could be false on the first try, meaning // the definitions before the loop reach after too - result = result.join(before); + result = result.absorb(before); } return result; } private void pushTargets(JavaNode loop, AlgoState breakTarget, AlgoState continueTarget) { - breakTargets.unnamedTargets.push(breakTarget); - continueTargets.unnamedTargets.push(continueTarget); + GlobalAlgoState globalState = breakTarget.global; + globalState.breakTargets.unnamedTargets.push(breakTarget); + globalState.continueTargets.unnamedTargets.push(continueTarget); Node parent = loop.getNthParent(2); while (parent instanceof ASTLabeledStatement) { String label = parent.getImage(); - breakTargets.namedTargets.put(label, breakTarget); - continueTargets.namedTargets.put(label, continueTarget); + globalState.breakTargets.namedTargets.put(label, breakTarget); + globalState.continueTargets.namedTargets.put(label, continueTarget); parent = parent.getNthParent(2); } } private AlgoState popTargets(JavaNode loop, AlgoState breakTarget, AlgoState continueTarget) { - breakTargets.unnamedTargets.pop(); - continueTargets.unnamedTargets.pop(); + GlobalAlgoState globalState = breakTarget.global; + globalState.breakTargets.unnamedTargets.pop(); + globalState.continueTargets.unnamedTargets.pop(); - AlgoState total = breakTarget.join(continueTarget); + AlgoState total = breakTarget.absorb(continueTarget); Node parent = loop.getNthParent(2); while (parent instanceof ASTLabeledStatement) { String label = parent.getImage(); - total = total.join(breakTargets.namedTargets.remove(label)); - total = total.join(continueTargets.namedTargets.remove(label)); + total = total.absorb(globalState.breakTargets.namedTargets.remove(label)); + total = total.absorb(globalState.continueTargets.namedTargets.remove(label)); parent = parent.getNthParent(2); } return total; @@ -414,19 +421,23 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTContinueStatement node, Object data) { - return continueTargets.doBreak((AlgoState) data, node.getImage()); + AlgoState state = (AlgoState) data; + return state.global.continueTargets.doBreak(state, node.getImage()); } @Override public Object visit(ASTBreakStatement node, Object data) { - return breakTargets.doBreak((AlgoState) data, node.getImage()); + AlgoState state = (AlgoState) data; + return state.global.breakTargets.doBreak(state, node.getImage()); } @Override public Object visit(ASTYieldStatement node, Object data) { super.visit(node, data); // visit expression + + AlgoState state = (AlgoState) data; // treat as break, ie abrupt completion + link reaching defs to outer context - return breakTargets.doBreak((AlgoState) data, null); + return state.global.breakTargets.doBreak(state, null); } @@ -485,7 +496,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (node.getChild(1).getImage().length() >= 2) { // compound assignment, to use BEFORE assigning - ((AlgoState) data).use(lhsVar); + result.use(lhsVar); } result.assign(lhsVar, rhs); @@ -570,9 +581,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private static String identOf(boolean inLhs, String str) { int i = str.indexOf('.'); - return i < 0 ? str - : inLhs ? null - : str.substring(0, i); + if (i < 0) { + return str; + } else if (inLhs) { + // a qualified name in LHS, so + // the assignment doesn't assign the variable but one of its fields + return null; + } + return str.substring(0, i); } private static String methodLhsName(String name) { @@ -604,14 +620,36 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } + private static class GlobalAlgoState { + + final Set allAssignments; + final Set usedAssignments; + final Map> killRecord; + + final TargetStack breakTargets = new TargetStack(); + // continue jumps to the condition check, while break jumps to after the loop + final TargetStack continueTargets = new TargetStack(); + + private GlobalAlgoState(Set allAssignments, + Set usedAssignments, + Map> killRecord) { + this.allAssignments = allAssignments; + this.usedAssignments = usedAssignments; + this.killRecord = killRecord; + } + + private GlobalAlgoState() { + this(new HashSet(), + new HashSet(), + new HashMap>()); + } + } + private static class AlgoState { final AlgoState parent; - // Those 3 are shared between all the forked states - final Set allAssignments; - final Set usedAssignments; - final Map> killRecord; + final GlobalAlgoState global; // Map of var -> reaching(var) final Map> reachingDefs; @@ -621,21 +659,15 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private AlgoState() { this(null, - new HashSet(), - new HashSet(), - new HashMap>(), + new GlobalAlgoState(), new HashMap>()); } private AlgoState(AlgoState parent, - Set allAssignments, - Set usedAssignments, - Map> killRecord, + GlobalAlgoState global, Map> reachingDefs) { this.parent = parent; - this.allAssignments = allAssignments; - this.usedAssignments = usedAssignments; - this.killRecord = killRecord; + this.global = global; this.reachingDefs = reachingDefs; } @@ -646,22 +678,22 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (killed != null) { for (AssignmentEntry k : killed) { // computeIfAbsent - Set killers = killRecord.get(k); + Set killers = global.killRecord.get(k); if (killers == null) { killers = new HashSet<>(1); - killRecord.put(k, killers); + global.killRecord.put(k, killers); } killers.add(entry); } } - allAssignments.add(entry); + global.allAssignments.add(entry); } void use(VariableNameDeclaration var) { Set reaching = reachingDefs.get(var); // may be null for implicit assignments, like method parameter if (reaching != null) { - usedAssignments.addAll(reaching); + global.usedAssignments.addAll(reaching); } } @@ -684,7 +716,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } private AlgoState doFork(AlgoState parent, Map> reaching) { - return new AlgoState(parent, this.allAssignments, this.usedAssignments, this.killRecord, reaching); + return new AlgoState(parent, this.global, reaching); } AlgoState abruptCompletion(AlgoState target) { @@ -692,7 +724,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { AlgoState parent = this; while (parent != target && parent != null) { if (parent.myFinally != null) { - parent.myFinally.join(this); + parent.myFinally.absorb(this); } parent = parent.parent; } @@ -702,7 +734,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } - AlgoState join(AlgoState sub) { + AlgoState absorb(AlgoState sub) { // Merge reaching des of the other scope into this if (sub == this || sub == null || sub.reachingDefs.isEmpty()) { return this; @@ -772,7 +804,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } if (target != null) { // otherwise CT error - target.join(data); + target.absorb(data); } return data.abruptCompletion(target); } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index a5b4771406..2d872294f5 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -1094,6 +1094,27 @@ public class Foo { return a; } +} ]]> + + + + Try with several catches + 0 + @@ -1211,13 +1232,13 @@ class Foo { decode() { - Flux> splitEvents = splitEvts(); + public Flux decode() { + Flux> splitEvents = splitEvts(); - return map(events -> { - return unmarshal(events.append(splitEvents)); - }); - } + return map(events -> { + return unmarshal(events.append(splitEvents)); + }); + } } ]]> @@ -1233,12 +1254,12 @@ class Foo { { int k = 0; return k = 2; }); - } + } } ]]> @@ -1254,16 +1275,39 @@ class Foo { decode() { - Flux> splitEvents = splitEvts(); + public Flux decode() { + Flux> splitEvents = splitEvts(); - return map(events -> { - events = events.normalize(); - return dontUseEvents(); - }); - } + return map(events -> { + events = events.normalize(); + return dontUseEvents(); + }); + } } ]]> + + FP in try + 0 + + + From d9a9a83caff0c857258854d350b29c53aa24b80d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 02:40:27 +0200 Subject: [PATCH 25/99] Handle field initializers and ctors --- .../rule/errorprone/UnusedAssignmentRule.java | 179 +++++++++++++++--- .../rule/errorprone/xml/UnusedAssignment.xml | 88 +++++++++ 2 files changed, 239 insertions(+), 28 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 195720c789..c4e158b3df 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -12,24 +12,35 @@ import java.util.Comparator; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; 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.ASTBreakStatement; import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTContinueStatement; import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; +import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTExpression; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFinallyStatement; 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.ASTIfStatement; +import net.sourceforge.pmd.lang.java.ast.ASTInitializer; import net.sourceforge.pmd.lang.java.ast.ASTLabeledStatement; import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; @@ -51,6 +62,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabeledRule; import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; import net.sourceforge.pmd.lang.java.ast.ASTTryStatement; +import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; @@ -69,6 +81,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { TODO * constructors + initializers * labels on arbitrary statements + * foreach var should be reassigned from one iter to another + * test local class/anonymous class DONE * conditionals @@ -81,39 +95,46 @@ public class UnusedAssignmentRule extends AbstractJavaRule { */ - public UnusedAssignmentRule() { - addRuleChainVisit(ASTMethodDeclaration.class); - } - - @Override - public Object visit(ASTMethodDeclaration node, Object data) { - if (node.isAbstract()) { - return data; + public Object visit(ASTCompilationUnit node, Object data) { + for (JavaNode child : node.children()) { + if (child instanceof ASTTypeDeclaration) { + + ASTAnyTypeDeclaration typeDecl = (ASTAnyTypeDeclaration) child.getChild(child.getNumChildren() - 1); + GlobalAlgoState result = new GlobalAlgoState(); + typeDecl.jjtAccept(ReachingDefsVisitor.ONLY_LOCALS, new AlgoState(result)); + + reportFinished(result, (RuleContext) data); + } } - AlgoState endState = (AlgoState) node.getBody().jjtAccept(ReachingDefsVisitor.INSTANCE, new AlgoState()); - GlobalAlgoState result = endState.global; + return data; + } + private void reportFinished(GlobalAlgoState result, RuleContext ruleCtx) { if (result.usedAssignments.size() < result.allAssignments.size()) { HashSet unused = new HashSet<>(result.allAssignments); unused.removeAll(result.usedAssignments); for (AssignmentEntry entry : unused) { + boolean isField = entry.var.getNode().getScope() instanceof ClassScope; + Set killers = result.killRecord.get(entry); if (killers == null || killers.isEmpty()) { - addViolation(data, entry.rhs, new Object[] {entry.var.getImage(), "goes out of scope"}); + if (isField) { + // assignments to fields don't really go out of scope + continue; + } + addViolation(ruleCtx, entry.rhs, new Object[] {entry.var.getImage(), "goes out of scope"}); } else if (killers.size() == 1) { AssignmentEntry k = killers.iterator().next(); - addViolation(data, entry.rhs, new Object[] {entry.var.getImage(), - "overwritten on line " + k.rhs.getBeginLine()}); + addViolation(ruleCtx, entry.rhs, new Object[] {entry.var.getImage(), + "overwritten on line " + k.rhs.getBeginLine()}); } else { - addViolation(data, entry.rhs, new Object[] {entry.var.getImage(), joinLines("overwritten on lines ", killers)}); + addViolation(ruleCtx, entry.rhs, new Object[] {entry.var.getImage(), joinLines("overwritten on lines ", killers)}); } } } - - return super.visit(node, data); } private static String joinLines(String prefix, Set killers) { @@ -137,14 +158,27 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private static class ReachingDefsVisitor extends JavaParserVisitorAdapter { + + static final ReachingDefsVisitor ONLY_LOCALS = new ReachingDefsVisitor(null); + // This analysis can be trivially used to check for unused variables, // in the absence of global variable usage pre-resolution (which in // 7.0 is not implemented yet and maybe won't be). // See reverted commit somewhere in the PR - // following deals with control flow + // The class scope for the "this" reference, used to find fields + // of this class + // null if we're not processing instance/static initializers, + // so in methods we don't care about fields + // If not null, fields are effectively treated as locals + private final ClassScope enclosingClassScope; - static final ReachingDefsVisitor INSTANCE = new ReachingDefsVisitor(); + private ReachingDefsVisitor(ClassScope scope) { + this.enclosingClassScope = scope; + } + + + // following deals with control flow structures @Override public Object visit(JavaNode node, Object data) { @@ -226,10 +260,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { AlgoState before = acceptOpt(node.getCondition(), (AlgoState) data); AlgoState thenState = acceptOpt(node.getThenBranch(), before.fork()); - AlgoState elseState = node.hasElse() ? acceptOpt(node.getElseBranch(), before.fork()) + AlgoState elseState = node.hasElse() ? acceptOpt(node.getElseBranch(), before) : before; - return thenState.absorb(elseState); + return elseState.absorb(thenState); } @Override @@ -302,7 +336,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { JavaNode lambdaBody = node.getChild(node.getNumChildren() - 1); // if it's an expression, then no assignments may occur in it, // but it can still use some variables of the context - acceptOpt(lambdaBody, before.forkLambda()); + acceptOpt(lambdaBody, before.forkCapturingNonLocal()); return before; } @@ -550,6 +584,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (primary instanceof ASTPrimaryExpression) { ASTPrimaryPrefix prefix = (ASTPrimaryPrefix) primary.getChild(0); + // todo + // this.x = 2; + // if (prefix.usesThisModifier()) { // if (primary.getNumChildren() < 2) { // return null; @@ -605,7 +642,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { while (scope != null) { for (VariableNameDeclaration decl : scope.getDeclarations(VariableNameDeclaration.class).keySet()) { if (decl.getImage().equals(name)) { - if (scope instanceof ClassScope) { + if (scope instanceof ClassScope && scope != enclosingClassScope) { // don't handle fields return null; } @@ -618,6 +655,91 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return null; } + + + // ctor/initializer handling + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + return visitType(node, data); + } + + @Override + public Object visit(ASTEnumDeclaration node, Object data) { + return visitType(node, data); + } + + @Override + public Object visit(ASTAnnotationTypeDeclaration node, Object data) { + return visitType(node, data); + } + + private Object visitType(ASTAnyTypeDeclaration type, Object data) { + AlgoState prev = (AlgoState) data; + processInitializers(type, prev); + + for (ASTAnyTypeBodyDeclaration decl : type.getDeclarations()) { + JavaNode d = decl.getDeclarationNode(); + if (d instanceof ASTMethodDeclaration) { + ONLY_LOCALS.acceptOpt(d, prev.forkCapturingNonLocal()); + } else if (d instanceof ASTAnyTypeDeclaration) { + visitType((ASTAnyTypeDeclaration) d, prev.forkEmptyNonLocal()); + } + } + + return data; // type doesn't contribute anything to the enclosing control flow + } + + // todo anon class + + + public static void processInitializers(ASTAnyTypeDeclaration klass, + AlgoState beforeLocal) { + + ReachingDefsVisitor visitor = new ReachingDefsVisitor((ClassScope) klass.getScope()); + + // All field initializers + instance initializers + AlgoState ctorHeader = beforeLocal.forkCapturingNonLocal(); + // All static field initializers + static initializers + AlgoState staticInit = beforeLocal.forkEmptyNonLocal(); + + List ctors = new ArrayList<>(); + + for (ASTAnyTypeBodyDeclaration declaration : klass.getDeclarations()) { + JavaNode node = declaration.getDeclarationNode(); + + final boolean isStatic; + if (node instanceof ASTFieldDeclaration) { + isStatic = ((ASTFieldDeclaration) node).isStatic(); + } else if (node instanceof ASTInitializer) { + isStatic = ((ASTInitializer) node).isStatic(); + } else if (node instanceof ASTConstructorDeclaration) { + ctors.add((ASTConstructorDeclaration) node); + continue; + } else { + continue; + } + + if (isStatic) { + staticInit = visitor.acceptOpt(node, staticInit); + } else { + ctorHeader = visitor.acceptOpt(node, ctorHeader); + } + } + + + AlgoState ctorEndState = ctors.isEmpty() ? ctorHeader : null; + for (ASTConstructorDeclaration ctor : ctors) { + AlgoState state = visitor.acceptOpt(ctor, ctorHeader.forkCapturingNonLocal()); + ctorEndState = ctorEndState == null ? state : ctorEndState.absorb(state); + } + + // assignments that reach the end of any constructor must + // be considered used + for (VariableNameDeclaration field : visitor.enclosingClassScope.getVariableDeclarations().keySet()) { + ctorEndState.use(field); + } + } } private static class GlobalAlgoState { @@ -656,11 +778,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { AlgoState myFinally = null; - - private AlgoState() { - this(null, - new GlobalAlgoState(), - new HashMap>()); + private AlgoState(GlobalAlgoState global) { + this(null, global, new HashMap>()); } private AlgoState(AlgoState parent, @@ -709,7 +828,11 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return doFork(this, new HashMap>()); } - AlgoState forkLambda() { + AlgoState forkEmptyNonLocal() { + return doFork(null, new HashMap>()); + } + + AlgoState forkCapturingNonLocal() { // has no parent, so that in case of abrupt completion (return inside a lambda), // enclosing finallies are not called return doFork(null, new HashMap<>(this.reachingDefs)); diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 2d872294f5..ca072e130b 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -1310,4 +1310,92 @@ class Foo { ]]> + + Field initializers 0 + 0 + + + + + Field initializers 1 + 1 + 3 + + The value assigned to variable 'f1' is never used (overwritten on line 4) + + + + + + Field initializers and ctor + 1 + 3 + + The value assigned to variable 'f1' is never used (overwritten on lines 7 and 11) + + + + + + Field initializers and ctor + 2 + 3,11 + + The value assigned to variable 'f1' is never used (overwritten on line 11) + The value assigned to variable 'f1' is never used (overwritten on lines 7 and 15) + + + + + From 46ae538b18ff625c752f598f8e3c6b407778a27a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 03:49:03 +0200 Subject: [PATCH 26/99] Fix some FPs --- .../rule/errorprone/UnusedAssignmentRule.java | 152 +- .../rule/errorprone/xml/UnusedAssignment.xml | 2976 +++++++++-------- 2 files changed, 1725 insertions(+), 1403 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index c4e158b3df..9c21f97fc9 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -19,7 +19,6 @@ import java.util.Set; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; @@ -27,12 +26,12 @@ import net.sourceforge.pmd.lang.java.ast.ASTBlock; import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement; import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTContinueStatement; import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; -import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTEnumBody; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFinallyStatement; @@ -79,10 +78,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { /* TODO - * constructors + initializers * labels on arbitrary statements * foreach var should be reassigned from one iter to another * test local class/anonymous class + * test this.field in ctors DONE * conditionals @@ -91,6 +90,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { * loop labels * try/catch/finally * lambdas + * constructors + initializers + * anon class */ @@ -212,7 +213,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } for (VariableNameDeclaration var : localsToKill) { - state.undef(var); + state.del(var); } return state; @@ -302,7 +303,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { AlgoState exceptionalState = null; for (ASTCatchStatement catchClause : node.getCatchClauses()) { - AlgoState current = acceptOpt(catchClause, before.fork()); + AlgoState current = acceptOpt(catchClause, before.fork().absorb(bodyState)); exceptionalState = current.absorb(exceptionalState); } @@ -580,35 +581,46 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return data; } + /** + * Get the variable accessed from a primary. + */ private VariableNameDeclaration getLhsVar(JavaNode primary, boolean inLhs) { + if (primary instanceof ASTPrimaryExpression) { ASTPrimaryPrefix prefix = (ASTPrimaryPrefix) primary.getChild(0); - // todo - // this.x = 2; - // if (prefix.usesThisModifier()) { - // if (primary.getNumChildren() < 2) { - // return null; - // } - // ASTPrimarySuffix suffix = (ASTPrimarySuffix) primary.getChild(1); - // if (suffix.isArguments() || suffix.isArrayDereference() || suffix.getImage() == null) { - // return null; - // } - // return findVar(primary.getScope(), true, firstIdent(suffix.getImage())); - // } else - { + // this.x = 2; + if (prefix.usesThisModifier() && this.enclosingClassScope != null) { + if (primary.getNumChildren() < 2 || primary.getNumChildren() > 2 && inLhs) { + return null; + } + + ASTPrimarySuffix suffix = (ASTPrimarySuffix) primary.getChild(1); + if (suffix.getImage() == null) { + // catches arrays and such + return null; + } + + return findVar(primary.getScope(), true, suffix.getImage()); + } else { if (prefix.getNumChildren() > 0 && (prefix.getChild(0) instanceof ASTName)) { String prefixImage = prefix.getChild(0).getImage(); String varname = identOf(inLhs, prefixImage); - if (primary.getNumChildren() > 1 && !inLhs) { + if (primary.getNumChildren() > 1) { + if (primary.getNumChildren() > 2 && inLhs) { + // this is for chains like `foo.m().field = 3` + return null; + } ASTPrimarySuffix suffix = (ASTPrimarySuffix) primary.getChild(1); if (suffix.isArguments()) { // then the prefix has the method name varname = methodLhsName(prefixImage); + } else if (suffix.isArrayDereference() && inLhs) { + return null; } } - return findVar(prefix.getScope(), varname); + return findVar(prefix.getScope(), false, varname); } } } @@ -634,20 +646,23 @@ public class UnusedAssignmentRule extends AbstractJavaRule { : name.substring(0, i); } - private VariableNameDeclaration findVar(Scope scope, String name) { + private VariableNameDeclaration findVar(Scope scope, boolean isField, String name) { if (name == null) { return null; } + if (isField) { + return getFromSingleScope(enclosingClassScope, name); + } + while (scope != null) { - for (VariableNameDeclaration decl : scope.getDeclarations(VariableNameDeclaration.class).keySet()) { - if (decl.getImage().equals(name)) { - if (scope instanceof ClassScope && scope != enclosingClassScope) { - // don't handle fields - return null; - } - return decl; + VariableNameDeclaration result = getFromSingleScope(scope, name); + if (result != null) { + if (scope instanceof ClassScope && scope != enclosingClassScope) { + // don't handle fields + return null; } + return result; } scope = scope.getParent(); @@ -656,47 +671,56 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return null; } + private VariableNameDeclaration getFromSingleScope(Scope scope, String name) { + if (scope != null) { + for (VariableNameDeclaration decl : scope.getDeclarations(VariableNameDeclaration.class).keySet()) { + if (decl.getImage().equals(name)) { + return decl; + } + } + } + return null; + } + // ctor/initializer handling - @Override - public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - return visitType(node, data); - } + // this is the common denominator between anonymous class & astAnyTypeDeclaration on master @Override - public Object visit(ASTEnumDeclaration node, Object data) { - return visitType(node, data); - } - - @Override - public Object visit(ASTAnnotationTypeDeclaration node, Object data) { - return visitType(node, data); - } - - private Object visitType(ASTAnyTypeDeclaration type, Object data) { - AlgoState prev = (AlgoState) data; - processInitializers(type, prev); - - for (ASTAnyTypeBodyDeclaration decl : type.getDeclarations()) { - JavaNode d = decl.getDeclarationNode(); - if (d instanceof ASTMethodDeclaration) { - ONLY_LOCALS.acceptOpt(d, prev.forkCapturingNonLocal()); - } else if (d instanceof ASTAnyTypeDeclaration) { - visitType((ASTAnyTypeDeclaration) d, prev.forkEmptyNonLocal()); - } - } - + public Object visit(ASTClassOrInterfaceBody node, Object data) { + visitTypeBody(node, (AlgoState) data); return data; // type doesn't contribute anything to the enclosing control flow } - // todo anon class + @Override + public Object visit(ASTEnumBody node, Object data) { + visitTypeBody(node, (AlgoState) data); + return data; // type doesn't contribute anything to the enclosing control flow + } - public static void processInitializers(ASTAnyTypeDeclaration klass, - AlgoState beforeLocal) { + public void visitTypeBody(JavaNode typeBody, AlgoState data) { + List declarations = typeBody.findChildrenOfType(ASTAnyTypeBodyDeclaration.class); + processInitializers(declarations, data, (ClassScope) typeBody.getScope()); - ReachingDefsVisitor visitor = new ReachingDefsVisitor((ClassScope) klass.getScope()); + for (ASTAnyTypeBodyDeclaration decl : declarations) { + JavaNode d = decl.getDeclarationNode(); + if (d instanceof ASTMethodDeclaration) { + ONLY_LOCALS.acceptOpt(d, data.forkCapturingNonLocal()); + } else if (d instanceof ASTAnyTypeDeclaration) { + JavaNode body = d.getChild(d.getNumChildren() - 1); + visitTypeBody(body, data.forkEmptyNonLocal()); + } + } + } + + + public static void processInitializers(List declarations, + AlgoState beforeLocal, + ClassScope scope) { + + ReachingDefsVisitor visitor = new ReachingDefsVisitor(scope); // All field initializers + instance initializers AlgoState ctorHeader = beforeLocal.forkCapturingNonLocal(); @@ -705,7 +729,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { List ctors = new ArrayList<>(); - for (ASTAnyTypeBodyDeclaration declaration : klass.getDeclarations()) { + for (ASTAnyTypeBodyDeclaration declaration : declarations) { JavaNode node = declaration.getDeclarationNode(); final boolean isStatic; @@ -737,6 +761,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // assignments that reach the end of any constructor must // be considered used for (VariableNameDeclaration field : visitor.enclosingClassScope.getVariableDeclarations().keySet()) { + if (field.getAccessNodeParent().isStatic()) { + staticInit.use(field); + } ctorEndState.use(field); } } @@ -816,7 +843,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } - void undef(VariableNameDeclaration var) { + void del(VariableNameDeclaration var) { reachingDefs.remove(var); } @@ -858,7 +885,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { AlgoState absorb(AlgoState sub) { - // Merge reaching des of the other scope into this + // Merge reaching defs of the other scope into this + // This is used to join paths after the control flow has forked if (sub == this || sub == null || sub.reachingDefs.isEmpty()) { return this; } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index ca072e130b..9ede002e1d 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -4,1344 +4,1408 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - - ok - 0 - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - DD anomaly - 1 - 3 - - The value assigned to variable 'i' is never used (overwritten on line 4) - - - - - - DU anomaly - 1 - - - - - UR anomaly - 0 - - - - - Conditional flow 0 - 3 - 3,4,6 - - The value assigned to variable 'j' is never used (overwritten on line 6) - The value assigned to variable 'z' is never used (goes out of scope) - The value assigned to variable 'j' is never used (goes out of scope) - - - - - - Conditional flow 1 - 1 - 4 - - The value assigned to variable 'z' is never used (goes out of scope) - - - - - - Conditional flow 2 - 1 - 3 - - The value assigned to variable 'j' is never used (overwritten on lines 6 and 8) - - - - - - Conditional flow with abrupt throw - 2 - 3,6 - - The value assigned to variable 'j' is never used (overwritten on lines 6 and 9) - The value assigned to variable 'j' is never used (goes out of scope) - - - - - - Conditional flow with abrupt return - 2 - 3,6 - - The value assigned to variable 'j' is never used (overwritten on lines 6 and 9) - The value assigned to variable 'j' is never used (goes out of scope) - - - - - - Local variable in loop - 2 - 10,19 - - The value assigned to variable 'fail' is never used (overwritten on line 19) - The value assigned to variable 'fail' is never used (overwritten on line 19) - - - - - - #408 Assert statements causing - 0 - - - - - #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 1. DU-Anomaly(b) - 1 - 6 - - The value assigned to variable 'b' is never used (goes out of scope) - - - - - - For loop - 0 - - - - - For loop 2 - 2 - 3,5 - - The value assigned to variable 'a' is never used (overwritten on line 5) - - The value assigned to variable 'a' is never used (overwritten on line 5) - - - - - - For loop 3 - 0 - - - - - For loop 4 - 0 - - - - - Foreach - 0 - - - - - While loop 1 - 0 - - - - - While loop 2 - 2 - 4,7 - - The value assigned to variable 'i' is never used (overwritten on line 7) - The value assigned to variable 'i' is never used (overwritten on line 7) - - - - - While loop with break - 1 - 7 - - The value assigned to variable 'i' is never used (goes out of scope) - - = 30) { - i = a + 1; // unused - break; - } - a = a + 3; - i = i + 1; // used by itself - } - } -} - ]]> - - - - While loop without break (control case) - 1 - = 30) { - i += a + 1; // unused by below - // break; // no break here - } - a = a + 3; - i = a + 2; // used by above - } - } -} - ]]> - - - - While loop without break 2 (control case) - 1 - 12 - - The value assigned to variable 'i' is never used (overwritten on line 16) - - = 30) { - i += a + 1; // unused because of i = a + 2 - // break outer; - } - a = a + 3; - i = a + 2; // killed by below - } - - i = 2; // used by print - } - - System.out.println(i); // uses i = i + 1 - } -} - ]]> - - - - While loop without break 2 (control case) - 1 - 12 - - The value assigned to variable 'i' is never used (overwritten on line 19) - - = 30) { - i += a + 1; // unused because of i = 2 - break; - } - a = a + 3; - i = a + 2; // used by i += a + 1 - } - - i = 2; // used by print - } - - System.out.println(i); // uses i = i + 1 - } -} - ]]> - - - - While loop with named break 2 - 0 - = 30) { - i += a + 1; // used by print - break outer; - } - a = a + 3; - i = a + 2; // used by i += a + 1 - } - - i = 2; // used by print - } - - System.out.println(i); // uses i = i + 1 - } -} - ]]> - - - - - While loop with continue - 0 - = 30) { - i = a + 1; // used by below - continue; - } - a = a + 3; - i = i + 1; // used by itself - } - } -} - ]]> - - - - While loop with continue 2 - 0 - = 30) { - a = i + 1; // used by loop condition - continue; - } - i++; // used by itself - } - } -} - ]]> - - - - While loop with break (control for continue test above) - 1 - 7 - - The value assigned to variable 'a' is never used (goes out of scope) - - = 30) { - a = i + 1; // unused - break; - } - i++; // used by itself - } - } -} - ]]> - - - - Do while 0 - 0 - - - - - Do while 1 - 1 - 3 - - The value assigned to variable 'a' is never used (overwritten on line 6) - - - - - - Do while with break - 2 - 7,8 - - The value assigned to variable 'i' is never used (goes out of scope) - The value assigned to variable 'a' is never used (goes out of scope) - - = 20) { - i = 4; - a *= 5; - break; - } - - a = i + 3; - i += 3; - } while (i < 30); - } -} - ]]> - - - - Do while with continue - 0 - = 20) { - i = 4; // used by condition - a *= 5; - continue; - } - - a = i + 3; - i += 3; - } while (i < 30); - } -} - ]]> - - - - Switch statement 0 - 4 - 6,8,10,12 - - The value assigned to variable 'a' is never used (goes out of scope) - The value assigned to variable 'a' is never used (goes out of scope) - The value assigned to variable 'a' is never used (goes out of scope) - The value assigned to variable 'a' is never used (goes out of scope) - - - - - - Switch statement 1 - 0 - - - - - Switch statement 2 - 0 - 0) break; // else fallthrough - case 2 : a = 2; break; - case 3 : a = 3; break; - default : a = a + 1; - } - - System.out.println(a); - } -} - ]]> - - - - Switch fallthrough - 1 - 6 - - The value assigned to variable 'a' is never used (overwritten on line 8) - - - - - - Switch fallthrough 2 - 1 - 9 - - The value assigned to variable 'a' is never used (goes out of scope) - - - - - - Switch non-fallthrough - 0 - a = 1; - case 2 -> a = 2; - case 3 -> a = 3; - default -> a = a + 1; - } - System.out.println(a); - } -} - ]]> - - - - Switch non-fallthrough blocks - 1 - 9 - - The value assigned to variable 'i' is never used (goes out of scope) - - a = 1; - case 2 -> { - if (args.length > 0) { - i = 4; - break; - } - a = 2; - } - case 3 -> a = 3; - default -> a = a + 1; - } - System.out.println(a); - } -} - ]]> - - - - Switch expr non-fallthrough - 4 - 6,7,8,9 - - The value assigned to variable 'a' is never used (overwritten on line 4) - The value assigned to variable 'a' is never used (overwritten on line 4) - The value assigned to variable 'a' is never used (overwritten on line 4) - The value assigned to variable 'a' is never used (overwritten on line 4) - - a = 1; - case 2 -> a = 2; - case 3 -> a = 3; - default -> a = a + 1; - }; - - System.out.println(a); - } -} - ]]> - - - - Switch expr with yield - 4 - 6,9,13,14 - - The value assigned to variable 'a' is never used (overwritten on line 4) - The value assigned to variable 'a' is never used (overwritten on line 4) - The value assigned to variable 'a' is never used (overwritten on line 4) - The value assigned to variable 'a' is never used (overwritten on line 4) - - a = 1; - case 2 -> { - if (a > 0) { - yield a++; - } - yield 4; - } - case 3 -> a = 3; - default -> a = a + 1; - }; - - System.out.println(a); - } -} - ]]> - - - - Usage as LHS of method - 1 - 5 - - The value assigned to variable 't1' is never used (goes out of scope) - - - - - - Assignment in operand - 1 - 6 - - The value assigned to variable 't1' is never used (goes out of scope) - - - - - - Assignment in operand 2 - 1 - 7 - - The value assigned to variable 't1' is never used (goes out of scope) - - - - - - Assignment in operand 3 - 0 - - - - - Assignment in operand 4 - 2 - 4,6 - - The value assigned to variable 't2' is never used (goes out of scope) - The value assigned to variable 't1' is never used (goes out of scope) - - - - - - #1749 DD False Positive in DataflowAnomalyAnalysis - 1 - 4 - - The value assigned to variable 'a' is never used (goes out of scope) - - - - - Compound assignment - 1 - 4 - - The value assigned to variable 'a' is never used (goes out of scope) - - - - - Another case - 2 - 3,5 - - The value assigned to variable 'iter' is never used (overwritten on line 4) - The value assigned to variable 'iter' is never used (goes out of scope) - - - - - - Var usage in lambda (#1304) - 0 - dummySet) { - captured = captured.trim(); - return dummySet.stream().noneMatch(value -> value.equalsIgnoreCase(captured)); - } - -} ]]> - - - - Try/catch - 1 - 4 - - The value assigned to variable 'a' is never used (overwritten on lines 6 and 8) - - - - - - Try with several catches - 0 - - - - - Try/catch finally - 0 - - - - Try/catch finally 3 - 1 - 4 - - The value assigned to variable 'a' is never used (overwritten on lines 6 and 8) - - - - - Try/catch finally in loop - 0 - 10) { - try (Reader r = new StringReader("")) { - r.read(); - } catch (IOException e) { - a = -1; // used in finally even if break - break; - } finally { - a++; - } - } - return a; - } - -} - ]]> - - - Abstract method NPE - 0 - - - - FP in finally - 0 - - - - - Lambda captured var use - 0 - decode() { - Flux> splitEvents = splitEvts(); - - return map(events -> { - return unmarshal(events.append(splitEvents)); - }); - } - -} - ]]> - - - Lambda assignment - 2 - 5,6 - - The value assigned to variable 'k' is never used (overwritten on line 6) - The value assigned to variable 'k' is never used (goes out of scope) - - { - int k = 0; - return k = 2; - }); - } - -} - ]]> - - - Lambda returns 2 - 2 - 4,7 - - The value assigned to variable 'splitEvents' is never used (goes out of scope) - The value assigned to variable 'events' is never used (goes out of scope) - - decode() { - Flux> splitEvents = splitEvts(); - - return map(events -> { - events = events.normalize(); - return dontUseEvents(); - }); - } - -} - ]]> - - - FP in try - 0 - - - - - Field initializers 0 - 0 - - - - - Field initializers 1 - 1 - 3 - - The value assigned to variable 'f1' is never used (overwritten on line 4) - - - - - - Field initializers and ctor + Field initializers and ctor with this, shadowing 1 3 @@ -1353,13 +1417,35 @@ class Foo { int f1 = 0; int f2 = 0; - Foo(int f, int g) { - f1 = f; + Foo(int f1, int g) { + this.f1 = f1; } - Foo(int f, int g) { - f1 = f; - f2 = f + g; + Foo(int f1, int g) { + this.f1 = f1; + this.f2 = f1 + g; + } + +} + ]]> + + + + Field initializers and ctor with this, field access + 0 + + + Static initializer + 0 + ejbRefClass; + + private static Class webServiceRefClass; + + static { + try { + Class clazz = (Class) Class.forName("javax.xml.ws.WebServiceRef"); + webServiceRefClass = clazz; + } catch (ClassNotFoundException ex) { + webServiceRefClass = null; + } + + try { + Class clazz = Class.forName("javax.ejb.EJB"); + ejbRefClass = clazz; + } catch (ClassNotFoundException ex) { + ejbRefClass = null; + } + } + + + private static Class other = webServiceRefClass; + +} + ]]> + + + + + FP with anonymous classes on the way + 0 + OBJECT_METHODS = new ArrayList(); + + static { + Method privateLookupIn; + Method lookupDefineClass; + Method classLoaderDefineClass; + ProtectionDomain protectionDomain; + Throwable throwable = null; + try { + privateLookupIn = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + try { + return MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class); + } + catch (NoSuchMethodException ex) { + return null; + } + } + }); + lookupDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + try { + return MethodHandles.Lookup.class.getMethod("defineClass", byte[].class); + } + catch (NoSuchMethodException ex) { + return null; + } + } + }); + classLoaderDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + return ClassLoader.class.getDeclaredMethod("defineClass", + String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class); + } + }); + protectionDomain = getProtectionDomain(ReflectUtils.class); + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + Method[] methods = Object.class.getDeclaredMethods(); + for (Method method : methods) { + if ("finalize".equals(method.getName()) + || (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) { + continue; + } + OBJECT_METHODS.add(method); + } + return null; + } + }); + } + catch (Throwable t) { + privateLookupIn = null; + lookupDefineClass = null; + classLoaderDefineClass = null; + protectionDomain = null; + throwable = t; + } + privateLookupInMethod = privateLookupIn; + lookupDefineClassMethod = lookupDefineClass; + classLoaderDefineClassMethod = classLoaderDefineClass; + PROTECTION_DOMAIN = protectionDomain; + THROWABLE = throwable; + } + +} + ]]> + + + + FP with array access + 0 + map, String name, int[] arr) { + Integer index = map.get(name); + arr[index] = 4; + } + +} + ]]> + + + + FP with array access + 0 + + + + + FP with long field access + 0 + + + + + FP with long access 2 + 0 + + + + FP with long access 2 + 0 + + + + + FN with casts + 1 + + From 3a036c4933e2485b60cc7d4eb2260f4719dbad4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 05:47:36 +0200 Subject: [PATCH 27/99] FN with ctor var shadowing --- .../rule/errorprone/UnusedAssignmentRule.java | 12 +- .../symboltable/VariableNameDeclaration.java | 4 +- .../rule/errorprone/xml/UnusedAssignment.xml | 2845 +++++++++-------- 3 files changed, 1456 insertions(+), 1405 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 9c21f97fc9..502c867ab7 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -81,7 +81,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { * labels on arbitrary statements * foreach var should be reassigned from one iter to another * test local class/anonymous class - * test this.field in ctors + * explicit ctor call (hard to impossible without type res) DONE * conditionals @@ -92,6 +92,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { * lambdas * constructors + initializers * anon class + * test this.field in ctors */ @@ -320,6 +321,12 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // this is the normal finally finalState = acceptOpt(finallyClause, finalState); } + + // In the 7.0 grammar, the resources should be explicitly + // used here. For now they don't trigger anything as their + // node is not a VariableDeclaratorId. There's a test to + // check that. + return finalState; } @@ -964,6 +971,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { static class AssignmentEntry { final VariableNameDeclaration var; + + // this is not necessarily an expression, it may be also the + // variable declarator of a foreach loop final JavaNode rhs; AssignmentEntry(VariableNameDeclaration var, JavaNode rhs) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclaration.java index e23a04b7d6..d2ba35112f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclaration.java @@ -141,12 +141,12 @@ public class VariableNameDeclaration extends AbstractNameDeclaration implements return false; } VariableNameDeclaration n = (VariableNameDeclaration) o; - return n.node.getImage().equals(node.getImage()); + return n.node.equals(this.node); } @Override public int hashCode() { - return node.getImage().hashCode(); + return node.hashCode(); } @Override diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 9ede002e1d..71f3bb49d3 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -4,1405 +4,1446 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + ok + 0 + + + + + DD anomaly + 1 + 3 + + The value assigned to variable 'i' is never used (overwritten on line 4) + + + + + + DU anomaly + 1 + + + + + UR anomaly + 0 + + + + + Conditional flow 0 + 3 + 3,4,6 + + The value assigned to variable 'j' is never used (overwritten on line 6) + The value assigned to variable 'z' is never used (goes out of scope) + The value assigned to variable 'j' is never used (goes out of scope) + + + + + + Conditional flow 1 + 1 + 4 + + The value assigned to variable 'z' is never used (goes out of scope) + + + + + + Conditional flow 2 + 1 + 3 + + The value assigned to variable 'j' is never used (overwritten on lines 6 and 8) + + + + + + Conditional flow with abrupt throw + 2 + 3,6 + + The value assigned to variable 'j' is never used (overwritten on lines 6 and 9) + The value assigned to variable 'j' is never used (goes out of scope) + + + + + + Conditional flow with abrupt return + 2 + 3,6 + + The value assigned to variable 'j' is never used (overwritten on lines 6 and 9) + The value assigned to variable 'j' is never used (goes out of scope) + + + + + + Local variable in loop + 2 + 10,19 + + The value assigned to variable 'fail' is never used (overwritten on line 19) + The value assigned to variable 'fail' is never used (overwritten on line 19) + + + + + + #408 Assert statements causing + 0 + + + + + #1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 1. DU-Anomaly(b) + 1 + 6 + + The value assigned to variable 'b' is never used (goes out of scope) + + + + + + For loop + 0 + + + + + For loop 2 + 2 + 3,5 + + The value assigned to variable 'a' is never used (overwritten on line 5) + + The value assigned to variable 'a' is never used (overwritten on line 5) + + + + + + For loop 3 + 0 + + + + + For loop 4 + 0 + + + + + Foreach + 0 + + + + + While loop 1 + 0 + + + + + While loop 2 + 2 + 4,7 + + The value assigned to variable 'i' is never used (overwritten on line 7) + The value assigned to variable 'i' is never used (overwritten on line 7) + + + + + While loop with break + 1 + 7 + + The value assigned to variable 'i' is never used (goes out of scope) + + = 30) { + i = a + 1; // unused + break; + } + a = a + 3; + i = i + 1; // used by itself + } + } +} + ]]> + + + + While loop without break (control case) + 1 + = 30) { + i += a + 1; // unused by below + // break; // no break here + } + a = a + 3; + i = a + 2; // used by above + } + } +} + ]]> + + + + While loop without break 2 (control case) + 1 + 12 + + The value assigned to variable 'i' is never used (overwritten on line 16) + + = 30) { + i += a + 1; // unused because of i = a + 2 + // break outer; + } + a = a + 3; + i = a + 2; // killed by below + } + + i = 2; // used by print + } + + System.out.println(i); // uses i = i + 1 + } +} + ]]> + + + + While loop without break 2 (control case) + 1 + 12 + + The value assigned to variable 'i' is never used (overwritten on line 19) + + = 30) { + i += a + 1; // unused because of i = 2 + break; + } + a = a + 3; + i = a + 2; // used by i += a + 1 + } + + i = 2; // used by print + } + + System.out.println(i); // uses i = i + 1 + } +} + ]]> + + + + While loop with named break 2 + 0 + = 30) { + i += a + 1; // used by print + break outer; + } + a = a + 3; + i = a + 2; // used by i += a + 1 + } + + i = 2; // used by print + } + + System.out.println(i); // uses i = i + 1 + } +} + ]]> + + + + + While loop with continue + 0 + = 30) { + i = a + 1; // used by below + continue; + } + a = a + 3; + i = i + 1; // used by itself + } + } +} + ]]> + + + + While loop with continue 2 + 0 + = 30) { + a = i + 1; // used by loop condition + continue; + } + i++; // used by itself + } + } +} + ]]> + + + + While loop with break (control for continue test above) + 1 + 7 + + The value assigned to variable 'a' is never used (goes out of scope) + + = 30) { + a = i + 1; // unused + break; + } + i++; // used by itself + } + } +} + ]]> + + + + Do while 0 + 0 + + + + + Do while 1 + 1 + 3 + + The value assigned to variable 'a' is never used (overwritten on line 6) + + + + + + Do while with break + 2 + 7,8 + + The value assigned to variable 'i' is never used (goes out of scope) + The value assigned to variable 'a' is never used (goes out of scope) + + = 20) { + i = 4; + a *= 5; + break; + } + + a = i + 3; + i += 3; + } while (i < 30); + } +} + ]]> + + + + Do while with continue + 0 + = 20) { + i = 4; // used by condition + a *= 5; + continue; + } + + a = i + 3; + i += 3; + } while (i < 30); + } +} + ]]> + + + + Switch statement 0 + 4 + 6,8,10,12 + + The value assigned to variable 'a' is never used (goes out of scope) + The value assigned to variable 'a' is never used (goes out of scope) + The value assigned to variable 'a' is never used (goes out of scope) + The value assigned to variable 'a' is never used (goes out of scope) + + + + + + Switch statement 1 + 0 + + + + + Switch statement 2 + 0 + 0) break; // else fallthrough + case 2 : a = 2; break; + case 3 : a = 3; break; + default : a = a + 1; + } + + System.out.println(a); + } +} + ]]> + + + + Switch fallthrough + 1 + 6 + + The value assigned to variable 'a' is never used (overwritten on line 8) + + + + + + Switch fallthrough 2 + 1 + 9 + + The value assigned to variable 'a' is never used (goes out of scope) + + + + + + Switch non-fallthrough + 0 + a = 1; + case 2 -> a = 2; + case 3 -> a = 3; + default -> a = a + 1; + } + System.out.println(a); + } +} + ]]> + + + + Switch non-fallthrough blocks + 1 + 9 + + The value assigned to variable 'i' is never used (goes out of scope) + + a = 1; + case 2 -> { + if (args.length > 0) { + i = 4; + break; + } + a = 2; + } + case 3 -> a = 3; + default -> a = a + 1; + } + System.out.println(a); + } +} + ]]> + + + + Switch expr non-fallthrough + 4 + 6,7,8,9 + + The value assigned to variable 'a' is never used (overwritten on line 4) + The value assigned to variable 'a' is never used (overwritten on line 4) + The value assigned to variable 'a' is never used (overwritten on line 4) + The value assigned to variable 'a' is never used (overwritten on line 4) + + a = 1; + case 2 -> a = 2; + case 3 -> a = 3; + default -> a = a + 1; + }; + + System.out.println(a); + } +} + ]]> + + + + Switch expr with yield + 4 + 6,9,13,14 + + The value assigned to variable 'a' is never used (overwritten on line 4) + The value assigned to variable 'a' is never used (overwritten on line 4) + The value assigned to variable 'a' is never used (overwritten on line 4) + The value assigned to variable 'a' is never used (overwritten on line 4) + + a = 1; + case 2 -> { + if (a > 0) { + yield a++; + } + yield 4; + } + case 3 -> a = 3; + default -> a = a + 1; + }; + + System.out.println(a); + } +} + ]]> + + + + Usage as LHS of method + 1 + 5 + + The value assigned to variable 't1' is never used (goes out of scope) + + + + + + Assignment in operand + 1 + 6 + + The value assigned to variable 't1' is never used (goes out of scope) + + + + + + Assignment in operand 2 + 1 + 7 + + The value assigned to variable 't1' is never used (goes out of scope) + + + + + + Assignment in operand 3 + 0 + + + + + Assignment in operand 4 + 2 + 4,6 + + The value assigned to variable 't2' is never used (goes out of scope) + The value assigned to variable 't1' is never used (goes out of scope) + + + + + + #1749 DD False Positive in DataflowAnomalyAnalysis + 1 + 4 + + The value assigned to variable 'a' is never used (goes out of scope) + + + + + Compound assignment + 1 + 4 + + The value assigned to variable 'a' is never used (goes out of scope) + + + + + Another case + 2 + 3,5 + + The value assigned to variable 'iter' is never used (overwritten on line 4) + The value assigned to variable 'iter' is never used (goes out of scope) + + + + + + Var usage in lambda (#1304) + 0 + dummySet) { + captured = captured.trim(); + return dummySet.stream().noneMatch(value -> value.equalsIgnoreCase(captured)); + } + +} ]]> + + + + Try/catch + 1 + 4 + + The value assigned to variable 'a' is never used (overwritten on lines 6 and 8) + + + + + + Try with several catches + 0 + + + + + Try with resources: resources should be used + 0 + + + + + Definitions in try block reach catch blocks + 0 + + + + + Try/catch finally + 0 + + + + Try/catch finally 3 + 1 + 4 + + The value assigned to variable 'a' is never used (overwritten on lines 6 and 8) + + + + + Try/catch finally in loop + 0 + 10) { + try (Reader r = new StringReader("")) { + r.read(); + } catch (IOException e) { + a = -1; // used in finally even if break + break; + } finally { + a++; + } + } + return a; + } + +} + ]]> + + + Abstract method NPE + 0 + + + + FP in finally + 0 + + + + + Lambda captured var use + 0 + decode() { + Flux> splitEvents = splitEvts(); + + return map(events -> { + return unmarshal(events.append(splitEvents)); + }); + } + +} + ]]> + + + Lambda assignment + 2 + 5,6 + + The value assigned to variable 'k' is never used (overwritten on line 6) + The value assigned to variable 'k' is never used (goes out of scope) + + { + int k = 0; + return k = 2; + }); + } + +} + ]]> + + + Lambda returns 2 + 2 + 4,7 + + The value assigned to variable 'splitEvents' is never used (goes out of scope) + The value assigned to variable 'events' is never used (goes out of scope) + + decode() { + Flux> splitEvents = splitEvts(); + + return map(events -> { + events = events.normalize(); + return dontUseEvents(); + }); + } + +} + ]]> + + + FP in try + 0 + + + + + Field initializers 0 + 0 + + + + + Field initializers 1 + 1 + 3 + + The value assigned to variable 'f1' is never used (overwritten on line 4) + + + + + + Field initializers 1 + 1 + 3 + + The value assigned to variable 'f1' is never used (overwritten on line 4) + + + + + + Field initializers and ctor + 1 + 3 + + The value assigned to variable 'f1' is never used (overwritten on lines 7 and 11) + + + + + + Field initializers and ctor with this + 1 + 3 + + The value assigned to variable 'f1' is never used (overwritten on lines 7 and 11) + + + Field initializers and ctor with this, shadowing @@ -1417,7 +1458,7 @@ class Foo { int f1 = 0; int f2 = 0; - Foo(int f1, int g) { + Foo(int f1) { this.f1 = f1; } @@ -1439,7 +1480,7 @@ class Foo { Bar f1 = 0; Bar f2 = 0; - Foo(Bar f1, Bar g) { + Foo(Bar f1) { this.f1.field = f1; } @@ -1466,7 +1507,7 @@ class Foo { int f1 = 0; int f3 = 0; - Foo(int f, int g) { + Foo(int f) { f1 = f; } From e0e2d8de6c032d902e62839679ecee8376f14841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 07:04:17 +0200 Subject: [PATCH 28/99] Fix foreach bug --- .../rule/errorprone/UnusedAssignmentRule.java | 29 ++++++++++++++----- .../rule/errorprone/xml/UnusedAssignment.xml | 6 +++- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 502c867ab7..c50f816015 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -79,7 +79,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { /* TODO * labels on arbitrary statements - * foreach var should be reassigned from one iter to another * test local class/anonymous class * explicit ctor call (hard to impossible without type res) @@ -93,6 +92,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { * constructors + initializers * anon class * test this.field in ctors + * foreach var should be reassigned from one iter to another */ @@ -350,12 +350,12 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTWhileStatement node, Object data) { - return handleLoop(node, (AlgoState) data, null, node.getCondition(), null, node.getBody(), true); + return handleLoop(node, (AlgoState) data, null, node.getCondition(), null, node.getBody(), true, null); } @Override public Object visit(ASTDoStatement node, Object data) { - return handleLoop(node, (AlgoState) data, null, node.getCondition(), null, node.getBody(), false); + return handleLoop(node, (AlgoState) data, null, node.getCondition(), null, node.getBody(), false, null); } @Override @@ -364,12 +364,13 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (node.isForeach()) { // the iterable expression JavaNode init = node.getChild(1); - return handleLoop(node, (AlgoState) data, init, null, null, body, true); + ASTVariableDeclaratorId foreachVar = (ASTVariableDeclaratorId) node.getChild(0).getChild(1).getChild(0); + return handleLoop(node, (AlgoState) data, init, null, null, body, true, foreachVar.getNameDeclaration()); } else { ASTForInit init = node.getFirstChildOfType(ASTForInit.class); ASTExpression cond = node.getCondition(); ASTForUpdate update = node.getFirstChildOfType(ASTForUpdate.class); - return handleLoop(node, (AlgoState) data, init, cond, update, body, true); + return handleLoop(node, (AlgoState) data, init, cond, update, body, true, null); } } @@ -380,7 +381,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { JavaNode cond, JavaNode update, JavaNode body, - boolean checkFirstIter) { + boolean checkFirstIter, + VariableNameDeclaration foreachVar) { final GlobalAlgoState globalState = before.global; // perform a few "iterations", to make sure that assignments in @@ -397,10 +399,17 @@ public class UnusedAssignmentRule extends AbstractJavaRule { pushTargets(loop, breakTarget, continueTarget); - AlgoState iter = acceptOpt(body, before.fork()); // make the defs of the body reach the other parts of the loop, // including itself - iter = acceptOpt(update, iter); + AlgoState iter = acceptOpt(body, before.fork()); + + if (foreachVar != null && iter.hasVar(foreachVar)) { + // in foreach loops, the loop variable is reassigned on each update + iter.assign(foreachVar, (JavaNode) foreachVar.getNode()); + } else { + iter = acceptOpt(update, iter); + } + iter = acceptOpt(cond, iter); iter = acceptOpt(body, iter); @@ -824,6 +833,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { this.reachingDefs = reachingDefs; } + boolean hasVar(VariableNameDeclaration var) { + return reachingDefs.containsKey(var); + } + void assign(VariableNameDeclaration var, JavaNode rhs) { AssignmentEntry entry = new AssignmentEntry(var, rhs); // kills the previous value diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 71f3bb49d3..202d6124f3 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -327,7 +327,11 @@ class Test{ Foreach - 0 + 1 + 5 + + The value assigned to variable 'a' is never used (overwritten on line 4) + Date: Mon, 22 Jun 2020 07:43:46 +0200 Subject: [PATCH 29/99] React to @SuppressWarnings("unused") --- .../pmd/lang/java/ast/ASTAnnotation.java | 2 +- .../rule/errorprone/UnusedAssignmentRule.java | 17 ++++-- .../rule/errorprone/xml/UnusedAssignment.xml | 57 +++++++++++++++++++ 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotation.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotation.java index 7f410b84c8..f98fd3295e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotation.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotation.java @@ -26,7 +26,7 @@ import net.sourceforge.pmd.annotation.InternalApi; public class ASTAnnotation extends AbstractJavaTypeNode { private static final List UNUSED_RULES - = Arrays.asList("UnusedPrivateField", "UnusedLocalVariable", "UnusedPrivateMethod", "UnusedFormalParameter"); + = Arrays.asList("UnusedPrivateField", "UnusedLocalVariable", "UnusedPrivateMethod", "UnusedFormalParameter", "UnusedAssignment"); private static final List SERIAL_RULES = Arrays.asList("BeanMembersShouldSerialize", "MissingSerialVersionUID"); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index c50f816015..df30c5ce60 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -78,7 +78,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { /* TODO - * labels on arbitrary statements + * labels on arbitrary statements (currently only loops) * test local class/anonymous class * explicit ctor call (hard to impossible without type res) @@ -716,7 +716,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } - public void visitTypeBody(JavaNode typeBody, AlgoState data) { + private void visitTypeBody(JavaNode typeBody, AlgoState data) { List declarations = typeBody.findChildrenOfType(ASTAnyTypeBodyDeclaration.class); processInitializers(declarations, data, (ClassScope) typeBody.getScope()); @@ -732,7 +732,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } - public static void processInitializers(List declarations, + private static void processInitializers(List declarations, AlgoState beforeLocal, ClassScope scope) { @@ -785,6 +785,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } + /** + * The shared state for all {@link AlgoState} instances in the same + * toplevel class. + */ private static class GlobalAlgoState { final Set allAssignments; @@ -875,13 +879,16 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return doFork(this, new HashMap>()); } + + // nonLocal forks have no parent, so that in case of abrupt + // completion (return inside a lambda), enclosing finallies are + // not called. This is used for lambdas, constructors, etc. + AlgoState forkEmptyNonLocal() { return doFork(null, new HashMap>()); } AlgoState forkCapturingNonLocal() { - // has no parent, so that in case of abrupt completion (return inside a lambda), - // enclosing finallies are not called return doFork(null, new HashMap<>(this.reachingDefs)); } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 202d6124f3..7845eedf16 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -1737,4 +1737,61 @@ class Foo { ]]> + + + SuppressWarnings test (local) + 0 + + + + + SuppressWarnings test (method) + 0 + + + + + + SuppressWarnings test (class) + 0 + + + + From 5fe97ee9ca339c2d95d2872ad56bb1a5c82dfecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 08:32:06 +0200 Subject: [PATCH 30/99] Add tests --- .../rule/errorprone/UnusedAssignmentRule.java | 1 + .../rule/errorprone/xml/UnusedAssignment.xml | 79 +++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index df30c5ce60..37e0f9ef35 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -127,6 +127,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // assignments to fields don't really go out of scope continue; } + // This is a "DU" anomaly, the others are "DD" addViolation(ruleCtx, entry.rhs, new Object[] {entry.var.getImage(), "goes out of scope"}); } else if (killers.size() == 1) { AssignmentEntry k = killers.iterator().next(); diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 7845eedf16..ee8aa5681f 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -1794,4 +1794,83 @@ public class Foo { + + Post-increment behavior + 1 + 5 + + The value assigned to variable 'i' is never used (goes out of scope) + + + + + + + Post-increment behavior 2 + 1 + 6 + + The value assigned to variable 'i' is never used (goes out of scope) + + + + + + + + Pre-increment behavior + 1 + 5 + + The value assigned to variable 'i' is never used (goes out of scope) + + + + + + + Pre-increment behavior 2 + 1 + 6 + + The value assigned to variable 'i' is never used (goes out of scope) + + + + + From 96e96406ff8942e8513119dd3dc46f93dc33af23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 10:45:58 +0200 Subject: [PATCH 31/99] Better messages --- .../rule/errorprone/UnusedAssignmentRule.java | 31 +++++++++--- .../rule/errorprone/xml/UnusedAssignment.xml | 48 +++++++++---------- 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 37e0f9ef35..02feeea620 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -122,24 +122,43 @@ public class UnusedAssignmentRule extends AbstractJavaRule { boolean isField = entry.var.getNode().getScope() instanceof ClassScope; Set killers = result.killRecord.get(entry); + final String reason; if (killers == null || killers.isEmpty()) { if (isField) { // assignments to fields don't really go out of scope continue; } // This is a "DU" anomaly, the others are "DD" - addViolation(ruleCtx, entry.rhs, new Object[] {entry.var.getImage(), "goes out of scope"}); + reason = "goes out of scope"; } else if (killers.size() == 1) { AssignmentEntry k = killers.iterator().next(); - addViolation(ruleCtx, entry.rhs, new Object[] {entry.var.getImage(), - "overwritten on line " + k.rhs.getBeginLine()}); + reason = "overwritten on line " + k.rhs.getBeginLine(); } else { - addViolation(ruleCtx, entry.rhs, new Object[] {entry.var.getImage(), joinLines("overwritten on lines ", killers)}); + reason = joinLines("overwritten on lines ", killers); } + addViolationWithMessage(ruleCtx, entry.rhs, makeMessage(entry, reason)); } } } + private static String makeMessage(AssignmentEntry assignment, String reason) { + // The X is never used (reason) + // ^ + boolean isField = assignment.var.getNode().getScope() instanceof ClassScope; + String varName = assignment.var.getName(); + StringBuilder format = new StringBuilder("The "); + if (assignment.rhs instanceof ASTVariableInitializer) { + format.append(isField ? "field initializer for" + : "initializer for variable"); + } else { + format.append("value assigned to "); + format.append(isField ? "field" : "variable"); + } + format.append(" ''").append(varName).append("''"); + format.append(" is never used (").append(reason).append(")"); + return format.toString(); + } + private static String joinLines(String prefix, Set killers) { StringBuilder sb = new StringBuilder(prefix); ArrayList sorted = new ArrayList<>(killers); @@ -215,7 +234,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } for (VariableNameDeclaration var : localsToKill) { - state.del(var); + state.deleteVar(var); } return state; @@ -868,7 +887,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } - void del(VariableNameDeclaration var) { + void deleteVar(VariableNameDeclaration var) { reachingDefs.remove(var); } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index ee8aa5681f..e94d2bf5d5 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -23,7 +23,7 @@ public class Foo { 1 3 - The value assigned to variable 'i' is never used (overwritten on line 4) + The initializer for variable 'i' is never used (overwritten on line 4) 3 3,4,6 - The value assigned to variable 'j' is never used (overwritten on line 6) - The value assigned to variable 'z' is never used (goes out of scope) + The initializer for variable 'j' is never used (overwritten on line 6) + The initializer for variable 'z' is never used (goes out of scope) The value assigned to variable 'j' is never used (goes out of scope) 1 4 - The value assigned to variable 'z' is never used (goes out of scope) + The initializer for variable 'z' is never used (goes out of scope) 1 3 - The value assigned to variable 'j' is never used (overwritten on lines 6 and 8) + The initializer for variable 'j' is never used (overwritten on lines 6 and 8) 2 3,6 - The value assigned to variable 'j' is never used (overwritten on lines 6 and 9) + The initializer for variable 'j' is never used (overwritten on lines 6 and 9) The value assigned to variable 'j' is never used (goes out of scope) 2 3,6 - The value assigned to variable 'j' is never used (overwritten on lines 6 and 9) + The initializer for variable 'j' is never used (overwritten on lines 6 and 9) The value assigned to variable 'j' is never used (goes out of scope) 2 10,19 - The value assigned to variable 'fail' is never used (overwritten on line 19) + The initializer for variable 'fail' is never used (overwritten on line 19) The value assigned to variable 'fail' is never used (overwritten on line 19) 2 3,5 - The value assigned to variable 'a' is never used (overwritten on line 5) + The initializer for variable 'a' is never used (overwritten on line 5) The value assigned to variable 'a' is never used (overwritten on line 5) @@ -366,7 +366,7 @@ class Test{ 2 4,7 - The value assigned to variable 'i' is never used (overwritten on line 7) + The initializer for variable 'i' is never used (overwritten on line 7) The value assigned to variable 'i' is never used (overwritten on line 7) 1 3 - The value assigned to variable 'a' is never used (overwritten on line 6) + The initializer for variable 'a' is never used (overwritten on line 6) 2 4,6 - The value assigned to variable 't2' is never used (goes out of scope) + The initializer for variable 't2' is never used (goes out of scope) The value assigned to variable 't1' is never used (goes out of scope) 2 3,5 - The value assigned to variable 'iter' is never used (overwritten on line 4) + The initializer for variable 'iter' is never used (overwritten on line 4) The value assigned to variable 'iter' is never used (goes out of scope) 1 4 - The value assigned to variable 'a' is never used (overwritten on lines 6 and 8) + The initializer for variable 'a' is never used (overwritten on lines 6 and 8) 1 4 - The value assigned to variable 'a' is never used (overwritten on lines 6 and 8) + The initializer for variable 'a' is never used (overwritten on lines 6 and 8) 2 5,6 - The value assigned to variable 'k' is never used (overwritten on line 6) + The initializer for variable 'k' is never used (overwritten on line 6) The value assigned to variable 'k' is never used (goes out of scope) 2 4,7 - The value assigned to variable 'splitEvents' is never used (goes out of scope) + The initializer for variable 'splitEvents' is never used (goes out of scope) The value assigned to variable 'events' is never used (goes out of scope) 1 3 - The value assigned to variable 'f1' is never used (overwritten on line 4) + The field initializer for 'f1' is never used (overwritten on line 4) 1 3 - The value assigned to variable 'f1' is never used (overwritten on line 4) + The field initializer for 'f1' is never used (overwritten on line 4) 1 3 - The value assigned to variable 'f1' is never used (overwritten on lines 7 and 11) + The field initializer for 'f1' is never used (overwritten on lines 7 and 11) 1 3 - The value assigned to variable 'f1' is never used (overwritten on lines 7 and 11) + The field initializer for 'f1' is never used (overwritten on lines 7 and 11) 1 3 - The value assigned to variable 'f1' is never used (overwritten on lines 7 and 11) + The field initializer for 'f1' is never used (overwritten on lines 7 and 11) 2 3,11 - The value assigned to variable 'f1' is never used (overwritten on line 11) - The value assigned to variable 'f1' is never used (overwritten on lines 7 and 15) + The field initializer for 'f1' is never used (overwritten on line 11) + The value assigned to field 'f1' is never used (overwritten on lines 7 and 15) Date: Mon, 22 Jun 2020 10:58:05 +0200 Subject: [PATCH 32/99] Special case pre-increment if value is used --- .../rule/errorprone/UnusedAssignmentRule.java | 44 +++++++++-- .../resources/category/java/errorprone.xml | 68 ++++++++++++++++- .../rule/errorprone/xml/UnusedAssignment.xml | 74 ++++++++++++++++--- 3 files changed, 166 insertions(+), 20 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 02feeea620..73812277eb 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -73,6 +73,8 @@ import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; import net.sourceforge.pmd.lang.java.symboltable.ClassScope; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; import net.sourceforge.pmd.lang.symboltable.Scope; +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.PropertyFactory; public class UnusedAssignmentRule extends AbstractJavaRule { @@ -80,7 +82,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { TODO * labels on arbitrary statements (currently only loops) * test local class/anonymous class - * explicit ctor call (hard to impossible without type res) + * explicit ctor call (hard to impossible without type res, or proper graph algorithms) DONE * conditionals @@ -97,6 +99,16 @@ public class UnusedAssignmentRule extends AbstractJavaRule { */ + private static final PropertyDescriptor CHECK_PREFIX_INCREMENT = + PropertyFactory.booleanProperty("checkUnusedPrefixIncrement") + .desc("Report expressions like ++i that may be replaced with (i + 1)") + .defaultValue(false) + .build(); + + public UnusedAssignmentRule() { + definePropertyDescriptor(CHECK_PREFIX_INCREMENT); + } + @Override public Object visit(ASTCompilationUnit node, Object data) { for (JavaNode child : node.children()) { @@ -115,10 +127,13 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private void reportFinished(GlobalAlgoState result, RuleContext ruleCtx) { if (result.usedAssignments.size() < result.allAssignments.size()) { - HashSet unused = new HashSet<>(result.allAssignments); + Set unused = result.allAssignments; unused.removeAll(result.usedAssignments); for (AssignmentEntry entry : unused) { + if (isIgnorablePrefixIncrement(entry.rhs)) { + continue; + } boolean isField = entry.var.getNode().getScope() instanceof ClassScope; Set killers = result.killRecord.get(entry); @@ -136,22 +151,35 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } else { reason = joinLines("overwritten on lines ", killers); } - addViolationWithMessage(ruleCtx, entry.rhs, makeMessage(entry, reason)); + addViolationWithMessage(ruleCtx, entry.rhs, makeMessage(entry, reason, isField)); } } } - private static String makeMessage(AssignmentEntry assignment, String reason) { - // The X is never used (reason) - // ^ - boolean isField = assignment.var.getNode().getScope() instanceof ClassScope; + private boolean isIgnorablePrefixIncrement(JavaNode assignment) { + if (assignment instanceof ASTPreIncrementExpression + || assignment instanceof ASTPreDecrementExpression) { + // the variable value is used if it was found somewhere else + // than in statement position + return !getProperty(CHECK_PREFIX_INCREMENT) && !(assignment.getParent() instanceof ASTStatementExpression); + } + return false; + } + + private static String makeMessage(AssignmentEntry assignment, String reason, boolean isField) { String varName = assignment.var.getName(); StringBuilder format = new StringBuilder("The "); if (assignment.rhs instanceof ASTVariableInitializer) { format.append(isField ? "field initializer for" : "initializer for variable"); } else { - format.append("value assigned to "); + if (assignment.rhs instanceof ASTPreIncrementExpression + || assignment.rhs instanceof ASTPreDecrementExpression + || assignment.rhs instanceof ASTPostfixExpression) { + format.append("updated value of "); + } else { + format.append("value assigned to "); + } format.append(isField ? "field" : "variable"); } format.append(" ''").append(varName).append("''"); diff --git a/pmd-java/src/main/resources/category/java/errorprone.xml b/pmd-java/src/main/resources/category/java/errorprone.xml index 569af9fa6c..dd81623fc1 100644 --- a/pmd-java/src/main/resources/category/java/errorprone.xml +++ b/pmd-java/src/main/resources/category/java/errorprone.xml @@ -3280,18 +3280,80 @@ public String convert(int x) { - TODO + Reports assignments to variables that are never used before the variable is overwritten, + or goes out of scope. Unused assignments are those for which + 1. The variable is never read after the assignment, or + 2. The assigned value is always overwritten by other assignments before the next read of + the variable. + + The rule doesn't consider assignments to fields except for those of `this` in a constructor, + or static fields of the current class in static initializers. + + The rule may be suppressed with the standard `@SuppressWarnings("unused")` tag. 3 + + + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index e94d2bf5d5..7c86faf9be 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -894,7 +894,7 @@ class Test{ 6,9,13,14 The value assigned to variable 'a' is never used (overwritten on line 4) - The value assigned to variable 'a' is never used (overwritten on line 4) + The updated value of variable 'a' is never used (overwritten on line 4) The value assigned to variable 'a' is never used (overwritten on line 4) The value assigned to variable 'a' is never used (overwritten on line 4) @@ -1799,7 +1799,7 @@ public class Foo { 1 5 - The value assigned to variable 'i' is never used (goes out of scope) + The updated value of variable 'i' is never used (goes out of scope) 1 6 - The value assigned to variable 'i' is never used (goes out of scope) + The updated value of variable 'i' is never used (goes out of scope) 1 5 - The value assigned to variable 'i' is never used (goes out of scope) + The updated value of variable 'i' is never used (goes out of scope) + + + + + + Pre-increment behavior + true + 1 + 5 + + The updated value of variable 'i' is never used (goes out of scope) Pre-increment behavior 2 - 1 - 6 - - The value assigned to variable 'i' is never used (goes out of scope) - + 0 + + Pre-increment behavior 2 + true + 1 + 6 + + The updated value of variable 'i' is never used (goes out of scope) + + + + + + Pre-increment behavior 2 + true + 1 + 6 + + The updated value of variable 'i' is never used (goes out of scope) + + + + + From cb09b6b9be067a670524c0345d99bbba01990aae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 12:11:03 +0200 Subject: [PATCH 33/99] Test local/anon class --- .../rule/errorprone/UnusedAssignmentRule.java | 62 ++++++++++++++----- .../rule/errorprone/xml/UnusedAssignment.xml | 50 +++++++++++++++ 2 files changed, 96 insertions(+), 16 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 73812277eb..13f292fae2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -79,10 +79,27 @@ import net.sourceforge.pmd.properties.PropertyFactory; public class UnusedAssignmentRule extends AbstractJavaRule { /* + Detects unused assignments. This performs a reaching definition + analysis. + + This DFA can be modified trivially to check for all + unused variables (just maintain a global set of variables that + must be used, adding them as you go, and on each AlgoState::use, + remove the var from this set). This would work even without variable + usage pre-resolution (which in 7.0 is not implemented yet and + maybe won't be). + + Since we have the reaching definitions at each variable usage, we + could also use that to detect other kinds of bug, eg conditions + that are always true, or dereferences that will always NPE. In + the general case though, this is complicated and better left to + an off-the-shelf data flow analyser, eg google Z3. + TODO * labels on arbitrary statements (currently only loops) - * test local class/anonymous class - * explicit ctor call (hard to impossible without type res, or proper graph algorithms) + * explicit ctor call (hard to impossible without type res, + or at least proper graph algorithms like toposort) + -> this is pretty invisible as it causes false negatives, not FPs DONE * conditionals @@ -95,7 +112,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { * anon class * test this.field in ctors * foreach var should be reassigned from one iter to another - + * test local class/anonymous class */ @@ -128,6 +145,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private void reportFinished(GlobalAlgoState result, RuleContext ruleCtx) { if (result.usedAssignments.size() < result.allAssignments.size()) { Set unused = result.allAssignments; + // note that this mutates allAssignments, so the global + // state is unusable after this unused.removeAll(result.usedAssignments); for (AssignmentEntry entry : unused) { @@ -211,11 +230,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { static final ReachingDefsVisitor ONLY_LOCALS = new ReachingDefsVisitor(null); - // This analysis can be trivially used to check for unused variables, - // in the absence of global variable usage pre-resolution (which in - // 7.0 is not implemented yet and maybe won't be). - // See reverted commit somewhere in the PR - // The class scope for the "this" reference, used to find fields // of this class // null if we're not processing instance/static initializers, @@ -841,6 +855,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { final Set allAssignments; final Set usedAssignments; + + // track which assignments kill which + // assignment -> killers(assignment) final Map> killRecord; final TargetStack breakTargets = new TargetStack(); @@ -864,13 +881,21 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private static class AlgoState { + // nodes are arranged in a tree, to look for enclosing finallies + // when abrupt completion occurs. Blocks that have non-local + // control-flow (lambda bodies, anonymous classes, etc) aren't + // linked to the outer parents. final AlgoState parent; final GlobalAlgoState global; // Map of var -> reaching(var) + // Implicit assignments, like parameter declarations, are not contained + // in this final Map> reachingDefs; + // If != null, then this node has a finally that all abrupt-completing + // statements must go through. AlgoState myFinally = null; private AlgoState(GlobalAlgoState global) { @@ -891,11 +916,11 @@ public class UnusedAssignmentRule extends AbstractJavaRule { void assign(VariableNameDeclaration var, JavaNode rhs) { AssignmentEntry entry = new AssignmentEntry(var, rhs); - // kills the previous value - Set killed = reachingDefs.put(var, Collections.singleton(entry)); + Set killed = reachingDefs.put(var, newSet(entry)); if (killed != null) { + // those assignments were overwritten ("killed") for (AssignmentEntry k : killed) { - // computeIfAbsent + // java8: computeIfAbsent Set killers = global.killRecord.get(k); if (killers == null) { killers = new HashSet<>(1); @@ -907,6 +932,12 @@ public class UnusedAssignmentRule extends AbstractJavaRule { global.allAssignments.add(entry); } + private Set newSet(AssignmentEntry member) { + HashSet set = new HashSet<>(1); + set.add(member); + return set; + } + void use(VariableNameDeclaration var) { Set reaching = reachingDefs.get(var); // may be null for implicit assignments, like method parameter @@ -919,6 +950,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { reachingDefs.remove(var); } + // fork duplicates this context, to preserve the reaching defs + // of the current context while analysing a sub-block + AlgoState fork() { return doFork(this, new HashMap<>(this.reachingDefs)); } @@ -928,10 +962,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } - // nonLocal forks have no parent, so that in case of abrupt - // completion (return inside a lambda), enclosing finallies are - // not called. This is used for lambdas, constructors, etc. - AlgoState forkEmptyNonLocal() { return doFork(null, new HashMap>()); } @@ -1068,7 +1098,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public int hashCode() { - return Objects.hash(rhs); + return rhs.hashCode(); } } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 7c86faf9be..066b1d78a9 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -1929,4 +1929,54 @@ public class Foo { + + Test local class + 2 + 4,6 + + The initializer for variable 'shadowed' is never used (goes out of scope) + The field initializer for 'f' is never used (overwritten on line 8) + + + + + + Test anonymous class + 3 + 4,5,6 + + The initializer for variable 'shadowed' is never used (overwritten on line 5) + The value assigned to variable 'shadowed' is never used (goes out of scope) + The field initializer for 'f' is never used (overwritten on line 8) + + + + + From c837e244e9106d07ddefba31a1fbf24d6509dd6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 15:51:07 +0200 Subject: [PATCH 34/99] Handle shortcut boolean expressions in if/ternary --- .../java/ast/ASTConditionalExpression.java | 2 +- .../rule/errorprone/UnusedAssignmentRule.java | 98 +++++++++-- .../rule/errorprone/xml/UnusedAssignment.xml | 166 ++++++++++++++++++ 3 files changed, 249 insertions(+), 17 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalExpression.java index 521b5828f0..1d4a84d645 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalExpression.java @@ -74,7 +74,7 @@ public class ASTConditionalExpression extends AbstractJavaTypeNode { * Returns the node that represents the guard of this conditional. * That is the expression before the '?'. */ - public Node getCondition() { + public JavaNode getCondition() { return getChild(0); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 13f292fae2..5c48228fa4 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -12,6 +12,7 @@ import java.util.Comparator; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -28,6 +29,9 @@ import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement; import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression; +import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression; +import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTContinueStatement; import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; @@ -93,13 +97,15 @@ public class UnusedAssignmentRule extends AbstractJavaRule { could also use that to detect other kinds of bug, eg conditions that are always true, or dereferences that will always NPE. In the general case though, this is complicated and better left to - an off-the-shelf data flow analyser, eg google Z3. + a DFA library, eg google Z3. TODO * labels on arbitrary statements (currently only loops) * explicit ctor call (hard to impossible without type res, or at least proper graph algorithms like toposort) -> this is pretty invisible as it causes false negatives, not FPs + * shortcut conditionals have their own control-flow + * test ternary expr DONE * conditionals @@ -212,7 +218,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { Collections.sort(sorted, new Comparator() { @Override public int compare(AssignmentEntry o1, AssignmentEntry o2) { - return Integer.compare(o1.rhs.getBeginLine(), o2.rhs.getBeginLine()); + int lineRes = Integer.compare(o1.rhs.getBeginLine(), o2.rhs.getBeginLine()); + return lineRes != 0 ? lineRes + : Integer.compare(o1.rhs.getBeginColumn(), o2.rhs.getBeginColumn()); } }); @@ -321,15 +329,78 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTIfStatement node, Object data) { - AlgoState before = acceptOpt(node.getCondition(), (AlgoState) data); + AlgoState before = (AlgoState) data; + return makeConditional(before, node.getCondition(), node.getThenBranch(), node.getElseBranch()); + } - AlgoState thenState = acceptOpt(node.getThenBranch(), before.fork()); - AlgoState elseState = node.hasElse() ? acceptOpt(node.getElseBranch(), before) - : before; + @Override + public Object visit(ASTConditionalExpression node, Object data) { + AlgoState before = (AlgoState) data; + return makeConditional(before, node.getCondition(), node.getChild(1), node.getChild(2)); + } + + AlgoState makeConditional(AlgoState before, JavaNode condition, JavaNode thenBranch, JavaNode elseBranch) { + AlgoState thenState = before.fork(); + AlgoState elseState = elseBranch != null ? before.fork() : before; + + linkConditional(before, condition, thenState, elseState); + + thenState = acceptOpt(thenBranch, thenState); + elseState = acceptOpt(elseBranch, elseState); return elseState.absorb(thenState); } + private AlgoState linkConditional(AlgoState prev, JavaNode condition, AlgoState thenState, AlgoState elseState) { + if (condition instanceof ASTConditionalOrExpression) { + return visitShortcutOrExpr(condition, prev, thenState, elseState); + } else if (condition instanceof ASTConditionalAndExpression) { + // To mimic a shortcut AND expr, swap the thenState and the elseState + // See explanations in method + return visitShortcutOrExpr(condition, prev, elseState, thenState); + } else if (condition instanceof ASTExpression && condition.getNumChildren() == 1) { + return linkConditional(prev, condition.getChild(0), thenState, elseState); + } else { + return acceptOpt(condition, prev); + } + // TODO parenthesized expression + } + + AlgoState visitShortcutOrExpr(JavaNode orExpr, + AlgoState before, + AlgoState thenState, + AlgoState elseState) { + + // + // if ( || || ... || ) + // else + // + // in , we are sure that at least was evaluated, + // but really any prefix of ... is possible so they're all merged + + // in , we are sure that all of ... were evaluated (to false) + + // If you replace || with &&, then the above holds if you swap and + // So this method handles the OR expr, the caller can swap the arguments to make an AND + + // --- + // This method side effects on thenState and elseState + + Iterator iterator = orExpr.children().iterator(); + + AlgoState cur = before; + do { + JavaNode cond = iterator.next(); + cur = linkConditional(cur, cond, thenState, elseState); + thenState.absorb(cur); + } while (iterator.hasNext()); + + elseState.absorb(cur); + + return cur; + } + + @Override public Object visit(ASTTryStatement node, Object data) { final AlgoState before = (AlgoState) data; @@ -795,8 +866,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private static void processInitializers(List declarations, - AlgoState beforeLocal, - ClassScope scope) { + AlgoState beforeLocal, + ClassScope scope) { ReachingDefsVisitor visitor = new ReachingDefsVisitor(scope); @@ -916,7 +987,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { void assign(VariableNameDeclaration var, JavaNode rhs) { AssignmentEntry entry = new AssignmentEntry(var, rhs); - Set killed = reachingDefs.put(var, newSet(entry)); + Set killed = reachingDefs.put(var, Collections.singleton(entry)); if (killed != null) { // those assignments were overwritten ("killed") for (AssignmentEntry k : killed) { @@ -932,12 +1003,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { global.allAssignments.add(entry); } - private Set newSet(AssignmentEntry member) { - HashSet set = new HashSet<>(1); - set.add(member); - return set; - } - void use(VariableNameDeclaration var) { Set reaching = reachingDefs.get(var); // may be null for implicit assignments, like method parameter @@ -950,8 +1015,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { reachingDefs.remove(var); } - // fork duplicates this context, to preserve the reaching defs + // Forks duplicate this context, to preserve the reaching defs // of the current context while analysing a sub-block + // Forks must be merged later if control flow merges again, see ::absorb AlgoState fork() { return doFork(this, new HashMap<>(this.reachingDefs)); diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 066b1d78a9..ff36c5c1c9 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -1978,5 +1978,171 @@ public class Foo { ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Test shortcut OR + 3 + 5,7,8 + + The initializer for variable 'i' is never used (overwritten on line 7) + The value assigned to variable 'j' is never used (overwritten on line 8) + The value assigned to variable 'j' is never used (goes out of scope) + + + + + + + Test shortcut OR 2 + 1 + + The initializer for variable 'i' is never used (overwritten on line 7) + + + + + + + Test shortcut AND + 2 + 5,7 + + The initializer for variable 'i' is never used (overwritten on line 7) + The value assigned to variable 'j' is never used (overwritten on line 8) + + + + + + Test shortcut AND 2 + 1 + + The initializer for variable 'i' is never used (overwritten on line 7) + + + + From 1d022d3d918a9de2b48caf255ef2d7cc9a11c29a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 18:00:17 +0200 Subject: [PATCH 35/99] Fix FP with side-effect in toplevel condition --- .../rule/errorprone/UnusedAssignmentRule.java | 33 +++++++------ .../rule/errorprone/xml/UnusedAssignment.xml | 49 ++++++++++++++----- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 5c48228fa4..90cf0c9c2c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -343,7 +343,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { AlgoState thenState = before.fork(); AlgoState elseState = elseBranch != null ? before.fork() : before; - linkConditional(before, condition, thenState, elseState); + linkConditional(before, condition, thenState, elseState, true); thenState = acceptOpt(thenBranch, thenState); elseState = acceptOpt(elseBranch, elseState); @@ -351,17 +351,22 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return elseState.absorb(thenState); } - private AlgoState linkConditional(AlgoState prev, JavaNode condition, AlgoState thenState, AlgoState elseState) { + private AlgoState linkConditional(AlgoState before, JavaNode condition, AlgoState thenState, AlgoState elseState, boolean isTopLevel) { if (condition instanceof ASTConditionalOrExpression) { - return visitShortcutOrExpr(condition, prev, thenState, elseState); + return visitShortcutOrExpr(condition, before, thenState, elseState); } else if (condition instanceof ASTConditionalAndExpression) { // To mimic a shortcut AND expr, swap the thenState and the elseState // See explanations in method - return visitShortcutOrExpr(condition, prev, elseState, thenState); + return visitShortcutOrExpr(condition, before, elseState, thenState); } else if (condition instanceof ASTExpression && condition.getNumChildren() == 1) { - return linkConditional(prev, condition.getChild(0), thenState, elseState); + return linkConditional(before, condition.getChild(0), thenState, elseState, isTopLevel); } else { - return acceptOpt(condition, prev); + AlgoState state = acceptOpt(condition, before); + if (isTopLevel) { + thenState.absorb(state); + elseState.absorb(state); + } + return state; } // TODO parenthesized expression } @@ -391,7 +396,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { AlgoState cur = before; do { JavaNode cond = iterator.next(); - cur = linkConditional(cur, cond, thenState, elseState); + cur = linkConditional(cur, cond, thenState, elseState, false); thenState.absorb(cur); } while (iterator.hasNext()); @@ -674,7 +679,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { JavaNode rhs = node.getChild(2); result = acceptOpt(rhs, result); - VariableNameDeclaration lhsVar = getLhsVar(node.getChild(0), true); + VariableNameDeclaration lhsVar = getVarFromExpression(node.getChild(0), true); if (lhsVar != null) { // in that case lhs is a normal variable (array access not supported) @@ -709,7 +714,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } private AlgoState checkIncOrDecrement(JavaNode unary, AlgoState data) { - VariableNameDeclaration var = getLhsVar(unary.getChild(0), true); + VariableNameDeclaration var = getVarFromExpression(unary.getChild(0), true); if (var != null) { data.use(var); data.assign(var, unary); @@ -721,19 +726,19 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTPrimaryExpression node, Object data) { - super.visit(node, data); // visit subexpressions + AlgoState state = (AlgoState) visit((JavaNode) node, data); // visit subexpressions - VariableNameDeclaration var = getLhsVar(node, false); + VariableNameDeclaration var = getVarFromExpression(node, false); if (var != null) { - ((AlgoState) data).use(var); + state.use(var); } - return data; + return state; } /** * Get the variable accessed from a primary. */ - private VariableNameDeclaration getLhsVar(JavaNode primary, boolean inLhs) { + private VariableNameDeclaration getVarFromExpression(JavaNode primary, boolean inLhs) { if (primary instanceof ASTPrimaryExpression) { ASTPrimaryPrefix prefix = (ASTPrimaryPrefix) primary.getChild(0); diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index ff36c5c1c9..ea451230ba 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -2124,25 +2124,50 @@ class Foo { The initializer for variable 'i' is never used (overwritten on line 7) + + FP with argument + 1 + + The initializer for variable 'i' is never used (overwritten on line 7) + + + + From 58adfca79ffa118860e6fdd7d9cfb77231267c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 18:26:44 +0200 Subject: [PATCH 36/99] Fix parenthesized expressions --- .../rule/codestyle/ConfusingTernaryRule.java | 49 +++++++++++------- .../rule/errorprone/UnusedAssignmentRule.java | 7 ++- .../rule/errorprone/xml/UnusedAssignment.xml | 51 +++++++++++++++++++ 3 files changed, 86 insertions(+), 21 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ConfusingTernaryRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ConfusingTernaryRule.java index 95e0c05fb6..b0ef3262e1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ConfusingTernaryRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ConfusingTernaryRule.java @@ -16,6 +16,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpressionNotPlusMinus; +import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; import net.sourceforge.pmd.properties.PropertyDescriptor; @@ -65,9 +66,9 @@ public class ConfusingTernaryRule extends AbstractJavaRule { public Object visit(ASTIfStatement node, Object data) { // look for "if (match) ..; else .." if (node.getNumChildren() == 3) { - Node inode = node.getChild(0); + JavaNode inode = node.getChild(0); if (inode instanceof ASTExpression && inode.getNumChildren() == 1) { - Node jnode = inode.getChild(0); + JavaNode jnode = inode.getChild(0); if (isMatch(jnode)) { if (!getProperty(ignoreElseIfProperty) @@ -85,7 +86,7 @@ public class ConfusingTernaryRule extends AbstractJavaRule { public Object visit(ASTConditionalExpression node, Object data) { // look for "match ? .. : .." if (node.getNumChildren() > 0) { - Node inode = node.getChild(0); + JavaNode inode = node.getChild(0); if (isMatch(inode)) { addViolation(data, node); } @@ -94,9 +95,9 @@ public class ConfusingTernaryRule extends AbstractJavaRule { } // recursive! - private static boolean isMatch(Node node) { - return isUnaryNot(node) || isNotEquals(node) || isConditionalWithAllMatches(node) - || isParenthesisAroundMatch(node); + private static boolean isMatch(JavaNode node) { + node = unwrapParentheses(node); + return isUnaryNot(node) || isNotEquals(node) || isConditionalWithAllMatches(node); } private static boolean isUnaryNot(Node node) { @@ -109,7 +110,7 @@ public class ConfusingTernaryRule extends AbstractJavaRule { return node instanceof ASTEqualityExpression && "!=".equals(node.getImage()); } - private static boolean isConditionalWithAllMatches(Node node) { + private static boolean isConditionalWithAllMatches(JavaNode node) { // look for "match && match" or "match || match" if (!(node instanceof ASTConditionalAndExpression) && !(node instanceof ASTConditionalOrExpression)) { return false; @@ -119,7 +120,7 @@ public class ConfusingTernaryRule extends AbstractJavaRule { return false; } for (int i = 0; i < n; i++) { - Node inode = node.getChild(i); + JavaNode inode = node.getChild(i); // recurse! if (!isMatch(inode)) { return false; @@ -129,21 +130,31 @@ public class ConfusingTernaryRule extends AbstractJavaRule { return true; } - private static boolean isParenthesisAroundMatch(Node node) { + /** + * Extracts the outermost node that is not a parenthesized + * expression. + * + * @deprecated This is internal API, because it will be removed in PMD 7. + * In PMD 7 there are no additional layers for parentheses in the Java tree. + */ + @Deprecated + public static JavaNode unwrapParentheses(final JavaNode top) { + JavaNode node = top; // look for "(match)" if (!(node instanceof ASTPrimaryExpression) || node.getNumChildren() != 1) { - return false; + return top; } - Node inode = node.getChild(0); - if (!(inode instanceof ASTPrimaryPrefix) || inode.getNumChildren() != 1) { - return false; + node = node.getChild(0); + if (!(node instanceof ASTPrimaryPrefix) || node.getNumChildren() != 1) { + return top; } - Node jnode = inode.getChild(0); - if (!(jnode instanceof ASTExpression) || jnode.getNumChildren() != 1) { - return false; + node = node.getChild(0); + if (!(node instanceof ASTExpression) || node.getNumChildren() != 1) { + return top; } - Node knode = jnode.getChild(0); - // recurse! - return isMatch(knode); + node = node.getChild(0); + + // recurse to unwrap another layer if possible + return unwrapParentheses(node); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 90cf0c9c2c..ac9de7e239 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -74,6 +74,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTYieldStatement; import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.rule.codestyle.ConfusingTernaryRule; import net.sourceforge.pmd.lang.java.symboltable.ClassScope; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; import net.sourceforge.pmd.lang.symboltable.Scope; @@ -104,7 +105,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { * explicit ctor call (hard to impossible without type res, or at least proper graph algorithms like toposort) -> this is pretty invisible as it causes false negatives, not FPs - * shortcut conditionals have their own control-flow * test ternary expr DONE @@ -119,6 +119,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { * test this.field in ctors * foreach var should be reassigned from one iter to another * test local class/anonymous class + * shortcut conditionals have their own control-flow + * parenthesized expressions */ @@ -352,6 +354,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } private AlgoState linkConditional(AlgoState before, JavaNode condition, AlgoState thenState, AlgoState elseState, boolean isTopLevel) { + condition = ConfusingTernaryRule.unwrapParentheses(condition); + if (condition instanceof ASTConditionalOrExpression) { return visitShortcutOrExpr(condition, before, thenState, elseState); } else if (condition instanceof ASTConditionalAndExpression) { @@ -368,7 +372,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } return state; } - // TODO parenthesized expression } AlgoState visitShortcutOrExpr(JavaNode orExpr, diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index ea451230ba..59cd87e6c1 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -2144,6 +2144,57 @@ class Foo { ]]> + + Nested boolean logic 1 + 1 + + The value assigned to variable 'i' is never used (overwritten on line 7) + + 0 || ((i = 2) < (j = i) && (j = k) == i) ) { + // reaching: i = 1, i = 2, j = k (not j = i) + } else { + // reaching: i = 2, j = k, j = i (not i = 1) + log(j); + log(i); + } + } + } + + ]]> + + + Nested boolean logic 2 + 1 + + The value assigned to variable 'i' is never used (overwritten on line 7) + + 0 && ((i = 2) < (j = i) || (j = k) == i) ) { + // reaching: i = 2, j = i, j = k (not i = 1) + log(i); + } else { + // reaching: i = 1, i = 2, j = k, j = i + log(j); + } + } + } + + ]]> + + FP with argument 1 From b7c09a955507c4c42e257ed4d02b0b956afcbf89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 18:39:45 +0200 Subject: [PATCH 37/99] Suppress some overlap with UnusedVariable --- .../rule/errorprone/UnusedAssignmentRule.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index ac9de7e239..711bfe62ff 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -130,8 +130,16 @@ public class UnusedAssignmentRule extends AbstractJavaRule { .defaultValue(false) .build(); + private static final PropertyDescriptor REPORT_UNUSED_VARS = + PropertyFactory.booleanProperty("reportUnusedVariables") + .desc("Report variables that are only initialized, and never read at all. " + + "The rule UnusedVariable already cares for that, so you can disable it if needed") + .defaultValue(false) + .build(); + public UnusedAssignmentRule() { definePropertyDescriptor(CHECK_PREFIX_INCREMENT); + definePropertyDescriptor(REPORT_UNUSED_VARS); } @Override @@ -169,6 +177,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (isField) { // assignments to fields don't really go out of scope continue; + } else if (suppressUnusedVariableRuleOverlap(entry)) { + // see REPORT_UNUSED_VARS property + continue; } // This is a "DU" anomaly, the others are "DD" reason = "goes out of scope"; @@ -183,6 +194,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } + private boolean suppressUnusedVariableRuleOverlap(AssignmentEntry entry) { + return !getProperty(REPORT_UNUSED_VARS) && entry.rhs instanceof ASTVariableInitializer; + } + private boolean isIgnorablePrefixIncrement(JavaNode assignment) { if (assignment instanceof ASTPreIncrementExpression || assignment instanceof ASTPreDecrementExpression) { From 2dc8675b8573b6d562eed398b2d2fd96bd8dd653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Wed, 24 Jun 2020 05:15:32 +0200 Subject: [PATCH 38/99] Doc --- .../rule/errorprone/UnusedAssignmentRule.java | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 711bfe62ff..b3bb599af8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -106,6 +106,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { or at least proper graph algorithms like toposort) -> this is pretty invisible as it causes false negatives, not FPs * test ternary expr + * conditional exprs in loops DONE * conditionals @@ -368,6 +369,28 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return elseState.absorb(thenState); } + /* + * This recursive procedure translates shortcut conditionals + * that occur in condition position in the following way: + * + * if (a || b) if (a) + * else ~> else + * if (b) + * else + * + * + * if (a && b) if (a) + * else ~> if (b) + * else + * else + * + * The new innermost `if` is recursively processed to translate + * bigger conditions, like `a || b && c` + * + * This is how it works, but the and branch are + * visited only once, because it's not done in this method, but + * in makeConditional. + */ private AlgoState linkConditional(AlgoState before, JavaNode condition, AlgoState thenState, AlgoState elseState, boolean isTopLevel) { condition = ConfusingTernaryRule.unwrapParentheses(condition); @@ -394,12 +417,11 @@ public class UnusedAssignmentRule extends AbstractJavaRule { AlgoState thenState, AlgoState elseState) { - // // if ( || || ... || ) // else // // in , we are sure that at least was evaluated, - // but really any prefix of ... is possible so they're all merged + // but really any prefix of ... is possible so they're all merged // in , we are sure that all of ... were evaluated (to false) @@ -407,7 +429,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // So this method handles the OR expr, the caller can swap the arguments to make an AND // --- - // This method side effects on thenState and elseState + // This method side effects on thenState and elseState to + // set the variables. Iterator iterator = orExpr.children().iterator(); @@ -539,6 +562,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { JavaNode body, boolean checkFirstIter, VariableNameDeclaration foreachVar) { + // TODO linkConditional final GlobalAlgoState globalState = before.global; // perform a few "iterations", to make sure that assignments in From b0891dfca34ddee707c209c9fbf66e7452aafe68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Wed, 24 Jun 2020 07:48:25 +0200 Subject: [PATCH 39/99] Refactor --- .../rule/errorprone/UnusedAssignmentRule.java | 337 ++++++++++-------- 1 file changed, 185 insertions(+), 152 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index b3bb599af8..4881169e6d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -135,7 +135,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { PropertyFactory.booleanProperty("reportUnusedVariables") .desc("Report variables that are only initialized, and never read at all. " + "The rule UnusedVariable already cares for that, so you can disable it if needed") - .defaultValue(false) + .defaultValue(true) .build(); public UnusedAssignmentRule() { @@ -150,7 +150,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { ASTAnyTypeDeclaration typeDecl = (ASTAnyTypeDeclaration) child.getChild(child.getNumChildren() - 1); GlobalAlgoState result = new GlobalAlgoState(); - typeDecl.jjtAccept(ReachingDefsVisitor.ONLY_LOCALS, new AlgoState(result)); + typeDecl.jjtAccept(ReachingDefsVisitor.ONLY_LOCALS, new SpanInfo(result)); reportFinished(result, (RuleContext) data); } @@ -286,7 +286,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // variables local to a loop iteration must be killed before the // next iteration - AlgoState state = (AlgoState) data; + SpanInfo state = (SpanInfo) data; Set localsToKill = new HashSet<>(); for (JavaNode child : node.children()) { @@ -310,21 +310,21 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTSwitchStatement node, Object data) { - return processSwitch(node, (AlgoState) data, node.getTestedExpression()); + return processSwitch(node, (SpanInfo) data, node.getTestedExpression()); } @Override public Object visit(ASTSwitchExpression node, Object data) { - return processSwitch(node, (AlgoState) data, node.getChild(0)); + return processSwitch(node, (SpanInfo) data, node.getChild(0)); } - private AlgoState processSwitch(JavaNode switchLike, AlgoState data, JavaNode testedExpr) { + private SpanInfo processSwitch(JavaNode switchLike, SpanInfo data, JavaNode testedExpr) { GlobalAlgoState global = data.global; - AlgoState before = acceptOpt(testedExpr, data); + SpanInfo before = acceptOpt(testedExpr, data); global.breakTargets.push(before.fork()); - AlgoState current = before; + SpanInfo current = before; for (int i = 1; i < switchLike.getNumChildren(); i++) { JavaNode child = switchLike.getChild(i); if (child instanceof ASTSwitchLabel) { @@ -347,19 +347,19 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTIfStatement node, Object data) { - AlgoState before = (AlgoState) data; + SpanInfo before = (SpanInfo) data; return makeConditional(before, node.getCondition(), node.getThenBranch(), node.getElseBranch()); } @Override public Object visit(ASTConditionalExpression node, Object data) { - AlgoState before = (AlgoState) data; + SpanInfo before = (SpanInfo) data; return makeConditional(before, node.getCondition(), node.getChild(1), node.getChild(2)); } - AlgoState makeConditional(AlgoState before, JavaNode condition, JavaNode thenBranch, JavaNode elseBranch) { - AlgoState thenState = before.fork(); - AlgoState elseState = elseBranch != null ? before.fork() : before; + SpanInfo makeConditional(SpanInfo before, JavaNode condition, JavaNode thenBranch, JavaNode elseBranch) { + SpanInfo thenState = before.fork(); + SpanInfo elseState = elseBranch != null ? before.fork() : before; linkConditional(before, condition, thenState, elseState, true); @@ -384,14 +384,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { * else * else * - * The new innermost `if` is recursively processed to translate + * The new conditions are recursively processed to translate * bigger conditions, like `a || b && c` * * This is how it works, but the and branch are * visited only once, because it's not done in this method, but * in makeConditional. */ - private AlgoState linkConditional(AlgoState before, JavaNode condition, AlgoState thenState, AlgoState elseState, boolean isTopLevel) { + private SpanInfo linkConditional(SpanInfo before, JavaNode condition, SpanInfo thenState, SpanInfo elseState, boolean isTopLevel) { condition = ConfusingTernaryRule.unwrapParentheses(condition); if (condition instanceof ASTConditionalOrExpression) { @@ -403,7 +403,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } else if (condition instanceof ASTExpression && condition.getNumChildren() == 1) { return linkConditional(before, condition.getChild(0), thenState, elseState, isTopLevel); } else { - AlgoState state = acceptOpt(condition, before); + SpanInfo state = acceptOpt(condition, before); if (isTopLevel) { thenState.absorb(state); elseState.absorb(state); @@ -412,10 +412,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } - AlgoState visitShortcutOrExpr(JavaNode orExpr, - AlgoState before, - AlgoState thenState, - AlgoState elseState) { + SpanInfo visitShortcutOrExpr(JavaNode orExpr, + SpanInfo before, + SpanInfo thenState, + SpanInfo elseState) { // if ( || || ... || ) // else @@ -434,7 +434,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { Iterator iterator = orExpr.children().iterator(); - AlgoState cur = before; + SpanInfo cur = before; do { JavaNode cond = iterator.next(); cur = linkConditional(cur, cond, thenState, elseState, false); @@ -449,7 +449,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTTryStatement node, Object data) { - final AlgoState before = (AlgoState) data; + final SpanInfo before = (SpanInfo) data; ASTFinallyStatement finallyClause = node.getFinallyClause(); /* @@ -478,22 +478,22 @@ public class UnusedAssignmentRule extends AbstractJavaRule { ASTResourceSpecification resources = node.getFirstChildOfType(ASTResourceSpecification.class); - AlgoState bodyState = acceptOpt(resources, before.fork()); + SpanInfo bodyState = acceptOpt(resources, before.fork()); bodyState = acceptOpt(node.getBody(), bodyState); - AlgoState exceptionalState = null; + SpanInfo exceptionalState = null; for (ASTCatchStatement catchClause : node.getCatchClauses()) { - AlgoState current = acceptOpt(catchClause, before.fork().absorb(bodyState)); + SpanInfo current = acceptOpt(catchClause, before.fork().absorb(bodyState)); exceptionalState = current.absorb(exceptionalState); } - AlgoState finalState; + SpanInfo finalState; finalState = bodyState.absorb(exceptionalState); if (finallyClause != null) { // this represents the finally clause when it was entered // because of abrupt completion // since we don't know when it terminated we must join it with before - AlgoState abruptFinally = before.myFinally.absorb(before); + SpanInfo abruptFinally = before.myFinally.absorb(before); acceptOpt(finallyClause, abruptFinally); before.myFinally = null; @@ -518,7 +518,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // Since those definitions are [effectively] final, they actually can't be // killed, but they can be used in the lambda - AlgoState before = (AlgoState) data; + SpanInfo before = (SpanInfo) data; JavaNode lambdaBody = node.getChild(node.getNumChildren() - 1); // if it's an expression, then no assignments may occur in it, @@ -529,12 +529,12 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTWhileStatement node, Object data) { - return handleLoop(node, (AlgoState) data, null, node.getCondition(), null, node.getBody(), true, null); + return handleLoop(node, (SpanInfo) data, null, node.getCondition(), null, node.getBody(), true, null); } @Override public Object visit(ASTDoStatement node, Object data) { - return handleLoop(node, (AlgoState) data, null, node.getCondition(), null, node.getBody(), false, null); + return handleLoop(node, (SpanInfo) data, null, node.getCondition(), null, node.getBody(), false, null); } @Override @@ -544,24 +544,24 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // the iterable expression JavaNode init = node.getChild(1); ASTVariableDeclaratorId foreachVar = (ASTVariableDeclaratorId) node.getChild(0).getChild(1).getChild(0); - return handleLoop(node, (AlgoState) data, init, null, null, body, true, foreachVar.getNameDeclaration()); + return handleLoop(node, (SpanInfo) data, init, null, null, body, true, foreachVar.getNameDeclaration()); } else { ASTForInit init = node.getFirstChildOfType(ASTForInit.class); ASTExpression cond = node.getCondition(); ASTForUpdate update = node.getFirstChildOfType(ASTForUpdate.class); - return handleLoop(node, (AlgoState) data, init, cond, update, body, true, null); + return handleLoop(node, (SpanInfo) data, init, cond, update, body, true, null); } } - private AlgoState handleLoop(JavaNode loop, - AlgoState before, - JavaNode init, - JavaNode cond, - JavaNode update, - JavaNode body, - boolean checkFirstIter, - VariableNameDeclaration foreachVar) { + private SpanInfo handleLoop(JavaNode loop, + SpanInfo before, + JavaNode init, + JavaNode cond, + JavaNode update, + JavaNode body, + boolean checkFirstIter, + VariableNameDeclaration foreachVar) { // TODO linkConditional final GlobalAlgoState globalState = before.global; @@ -574,14 +574,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { before = acceptOpt(cond, before); } - AlgoState breakTarget = before.forkEmpty(); - AlgoState continueTarget = before.forkEmpty(); + SpanInfo breakTarget = before.forkEmpty(); + SpanInfo continueTarget = before.forkEmpty(); pushTargets(loop, breakTarget, continueTarget); // make the defs of the body reach the other parts of the loop, // including itself - AlgoState iter = acceptOpt(body, before.fork()); + SpanInfo iter = acceptOpt(body, before.fork()); if (foreachVar != null && iter.hasVar(foreachVar)) { // in foreach loops, the loop variable is reassigned on each update @@ -596,14 +596,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { breakTarget = globalState.breakTargets.peek(); continueTarget = globalState.continueTargets.peek(); - if (!continueTarget.reachingDefs.isEmpty()) { + if (!continueTarget.symtable.isEmpty()) { // make assignments before a continue reach the other parts of the loop continueTarget = acceptOpt(cond, continueTarget); continueTarget = acceptOpt(body, continueTarget); continueTarget = acceptOpt(update, continueTarget); } - AlgoState result = popTargets(loop, breakTarget, continueTarget); + SpanInfo result = popTargets(loop, breakTarget, continueTarget); result = result.absorb(iter); if (checkFirstIter) { // if the first iteration is checked, @@ -615,7 +615,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return result; } - private void pushTargets(JavaNode loop, AlgoState breakTarget, AlgoState continueTarget) { + private void pushTargets(JavaNode loop, SpanInfo breakTarget, SpanInfo continueTarget) { GlobalAlgoState globalState = breakTarget.global; globalState.breakTargets.unnamedTargets.push(breakTarget); globalState.continueTargets.unnamedTargets.push(continueTarget); @@ -629,12 +629,12 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } - private AlgoState popTargets(JavaNode loop, AlgoState breakTarget, AlgoState continueTarget) { + private SpanInfo popTargets(JavaNode loop, SpanInfo breakTarget, SpanInfo continueTarget) { GlobalAlgoState globalState = breakTarget.global; globalState.breakTargets.unnamedTargets.pop(); globalState.continueTargets.unnamedTargets.pop(); - AlgoState total = breakTarget.absorb(continueTarget); + SpanInfo total = breakTarget.absorb(continueTarget); Node parent = loop.getNthParent(2); while (parent instanceof ASTLabeledStatement) { @@ -646,19 +646,19 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return total; } - private AlgoState acceptOpt(JavaNode node, AlgoState before) { - return node == null ? before : (AlgoState) node.jjtAccept(this, before); + private SpanInfo acceptOpt(JavaNode node, SpanInfo before) { + return node == null ? before : (SpanInfo) node.jjtAccept(this, before); } @Override public Object visit(ASTContinueStatement node, Object data) { - AlgoState state = (AlgoState) data; + SpanInfo state = (SpanInfo) data; return state.global.continueTargets.doBreak(state, node.getImage()); } @Override public Object visit(ASTBreakStatement node, Object data) { - AlgoState state = (AlgoState) data; + SpanInfo state = (SpanInfo) data; return state.global.breakTargets.doBreak(state, node.getImage()); } @@ -666,7 +666,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { public Object visit(ASTYieldStatement node, Object data) { super.visit(node, data); // visit expression - AlgoState state = (AlgoState) data; + SpanInfo state = (SpanInfo) data; // treat as break, ie abrupt completion + link reaching defs to outer context return state.global.breakTargets.doBreak(state, null); } @@ -677,13 +677,13 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTThrowStatement node, Object data) { super.visit(node, data); - return ((AlgoState) data).abruptCompletion(null); + return ((SpanInfo) data).abruptCompletion(null); } @Override public Object visit(ASTReturnStatement node, Object data) { super.visit(node, data); - return ((AlgoState) data).abruptCompletion(null); + return ((SpanInfo) data).abruptCompletion(null); } // following deals with assignment @@ -695,7 +695,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { ASTVariableInitializer rhs = node.getInitializer(); if (rhs != null) { rhs.jjtAccept(this, data); - ((AlgoState) data).assign(var, rhs); + ((SpanInfo) data).assign(var, rhs); } return data; } @@ -712,7 +712,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } public Object checkAssignment(JavaNode node, Object data) { - AlgoState result = (AlgoState) data; + SpanInfo result = (SpanInfo) data; if (node.getNumChildren() == 3) { // assignment assert node.getChild(1) instanceof ASTAssignmentOperator; @@ -742,20 +742,20 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTPreDecrementExpression node, Object data) { - return checkIncOrDecrement(node, (AlgoState) data); + return checkIncOrDecrement(node, (SpanInfo) data); } @Override public Object visit(ASTPreIncrementExpression node, Object data) { - return checkIncOrDecrement(node, (AlgoState) data); + return checkIncOrDecrement(node, (SpanInfo) data); } @Override public Object visit(ASTPostfixExpression node, Object data) { - return checkIncOrDecrement(node, (AlgoState) data); + return checkIncOrDecrement(node, (SpanInfo) data); } - private AlgoState checkIncOrDecrement(JavaNode unary, AlgoState data) { + private SpanInfo checkIncOrDecrement(JavaNode unary, SpanInfo data) { VariableNameDeclaration var = getVarFromExpression(unary.getChild(0), true); if (var != null) { data.use(var); @@ -768,7 +768,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTPrimaryExpression node, Object data) { - AlgoState state = (AlgoState) visit((JavaNode) node, data); // visit subexpressions + SpanInfo state = (SpanInfo) visit((JavaNode) node, data); // visit subexpressions VariableNameDeclaration var = getVarFromExpression(node, false); if (var != null) { @@ -885,18 +885,18 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTClassOrInterfaceBody node, Object data) { - visitTypeBody(node, (AlgoState) data); + visitTypeBody(node, (SpanInfo) data); return data; // type doesn't contribute anything to the enclosing control flow } @Override public Object visit(ASTEnumBody node, Object data) { - visitTypeBody(node, (AlgoState) data); + visitTypeBody(node, (SpanInfo) data); return data; // type doesn't contribute anything to the enclosing control flow } - private void visitTypeBody(JavaNode typeBody, AlgoState data) { + private void visitTypeBody(JavaNode typeBody, SpanInfo data) { List declarations = typeBody.findChildrenOfType(ASTAnyTypeBodyDeclaration.class); processInitializers(declarations, data, (ClassScope) typeBody.getScope()); @@ -913,15 +913,15 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private static void processInitializers(List declarations, - AlgoState beforeLocal, + SpanInfo beforeLocal, ClassScope scope) { ReachingDefsVisitor visitor = new ReachingDefsVisitor(scope); // All field initializers + instance initializers - AlgoState ctorHeader = beforeLocal.forkCapturingNonLocal(); + SpanInfo ctorHeader = beforeLocal.forkCapturingNonLocal(); // All static field initializers + static initializers - AlgoState staticInit = beforeLocal.forkEmptyNonLocal(); + SpanInfo staticInit = beforeLocal.forkEmptyNonLocal(); List ctors = new ArrayList<>(); @@ -948,9 +948,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } - AlgoState ctorEndState = ctors.isEmpty() ? ctorHeader : null; + SpanInfo ctorEndState = ctors.isEmpty() ? ctorHeader : null; for (ASTConstructorDeclaration ctor : ctors) { - AlgoState state = visitor.acceptOpt(ctor, ctorHeader.forkCapturingNonLocal()); + SpanInfo state = visitor.acceptOpt(ctor, ctorHeader.forkCapturingNonLocal()); ctorEndState = ctorEndState == null ? state : ctorEndState.absorb(state); } @@ -966,7 +966,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } /** - * The shared state for all {@link AlgoState} instances in the same + * The shared state for all {@link SpanInfo} instances in the same * toplevel class. */ private static class GlobalAlgoState { @@ -997,52 +997,83 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } - private static class AlgoState { + // Information about a variable in a code span. + static class VarLocalInfo { - // nodes are arranged in a tree, to look for enclosing finallies - // when abrupt completion occurs. Blocks that have non-local - // control-flow (lambda bodies, anonymous classes, etc) aren't - // linked to the outer parents. - final AlgoState parent; + Set reachingDefs; - final GlobalAlgoState global; - - // Map of var -> reaching(var) - // Implicit assignments, like parameter declarations, are not contained - // in this - final Map> reachingDefs; - - // If != null, then this node has a finally that all abrupt-completing - // statements must go through. - AlgoState myFinally = null; - - private AlgoState(GlobalAlgoState global) { - this(null, global, new HashMap>()); - } - - private AlgoState(AlgoState parent, - GlobalAlgoState global, - Map> reachingDefs) { - this.parent = parent; - this.global = global; + VarLocalInfo(Set reachingDefs) { this.reachingDefs = reachingDefs; } + VarLocalInfo absorb(VarLocalInfo other) { + if (other == this) { + return this; + } + Set merged = new HashSet<>(reachingDefs.size() + other.reachingDefs.size()); + merged.addAll(reachingDefs); + merged.addAll(other.reachingDefs); + return new VarLocalInfo(merged); + } + + @Override + public String toString() { + return "VarLocalInfo{" + + "reachingDefs=" + reachingDefs + + '}'; + } + + public VarLocalInfo copy() { + return new VarLocalInfo(this.reachingDefs); + } + } + + /** + * Information about a span of code. + */ + private static class SpanInfo { + + // spans are arranged in a tree, to look for enclosing finallies + // when abrupt completion occurs. Blocks that have non-local + // control-flow (lambda bodies, anonymous classes, etc) aren't + // linked to the outer parents. + final SpanInfo parent; + + // If != null, then abrupt completion in this span of code (and any descendant) + // needs to go through the finally span (the finally must absorb it) + SpanInfo myFinally = null; + + final GlobalAlgoState global; + + final Map symtable; + + private SpanInfo(GlobalAlgoState global) { + this(null, global, new HashMap()); + } + + private SpanInfo(SpanInfo parent, + GlobalAlgoState global, + Map symtable) { + this.parent = parent; + this.global = global; + this.symtable = symtable; + } + boolean hasVar(VariableNameDeclaration var) { - return reachingDefs.containsKey(var); + return symtable.containsKey(var); } void assign(VariableNameDeclaration var, JavaNode rhs) { AssignmentEntry entry = new AssignmentEntry(var, rhs); - Set killed = reachingDefs.put(var, Collections.singleton(entry)); - if (killed != null) { + VarLocalInfo previous = symtable.put(var, new VarLocalInfo(Collections.singleton(entry))); + if (previous != null) { // those assignments were overwritten ("killed") - for (AssignmentEntry k : killed) { + for (AssignmentEntry killed : previous.reachingDefs) { // java8: computeIfAbsent - Set killers = global.killRecord.get(k); + Set killers = global.killRecord.get(killed); if (killers == null) { killers = new HashSet<>(1); - global.killRecord.put(k, killers); + global.killRecord.put(killed, killers); } killers.add(entry); } @@ -1051,45 +1082,53 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } void use(VariableNameDeclaration var) { - Set reaching = reachingDefs.get(var); + VarLocalInfo info = symtable.get(var); // may be null for implicit assignments, like method parameter - if (reaching != null) { - global.usedAssignments.addAll(reaching); + if (info != null) { + global.usedAssignments.addAll(info.reachingDefs); } } void deleteVar(VariableNameDeclaration var) { - reachingDefs.remove(var); + symtable.remove(var); } // Forks duplicate this context, to preserve the reaching defs // of the current context while analysing a sub-block // Forks must be merged later if control flow merges again, see ::absorb - AlgoState fork() { - return doFork(this, new HashMap<>(this.reachingDefs)); + SpanInfo fork() { + return doFork(this, copyTable()); } - AlgoState forkEmpty() { - return doFork(this, new HashMap>()); + SpanInfo forkEmpty() { + return doFork(this, new HashMap()); } - AlgoState forkEmptyNonLocal() { - return doFork(null, new HashMap>()); + SpanInfo forkEmptyNonLocal() { + return doFork(null, new HashMap()); } - AlgoState forkCapturingNonLocal() { - return doFork(null, new HashMap<>(this.reachingDefs)); + SpanInfo forkCapturingNonLocal() { + return doFork(null, copyTable()); } - private AlgoState doFork(AlgoState parent, Map> reaching) { - return new AlgoState(parent, this.global, reaching); + private HashMap copyTable() { + HashMap copy = new HashMap<>(this.symtable.size()); + for (VariableNameDeclaration var : this.symtable.keySet()) { + copy.put(var, this.symtable.get(var).copy()); + } + return copy; } - AlgoState abruptCompletion(AlgoState target) { + private SpanInfo doFork(SpanInfo parent, Map reaching) { + return new SpanInfo(parent, this.global, reaching); + } + + SpanInfo abruptCompletion(SpanInfo target) { // if target == null then this will unwind all the parents - AlgoState parent = this; + SpanInfo parent = this; while (parent != target && parent != null) { if (parent.myFinally != null) { parent.myFinally.absorb(this); @@ -1097,75 +1136,69 @@ public class UnusedAssignmentRule extends AbstractJavaRule { parent = parent.parent; } - this.reachingDefs.clear(); + this.symtable.clear(); return this; } - AlgoState absorb(AlgoState sub) { + SpanInfo absorb(SpanInfo other) { // Merge reaching defs of the other scope into this // This is used to join paths after the control flow has forked - if (sub == this || sub == null || sub.reachingDefs.isEmpty()) { + + // a spanInfo may be absorbed several times so this method should not + // destroy the parameter + if (other == this || other == null || other.symtable.isEmpty()) { return this; } - for (VariableNameDeclaration var : this.reachingDefs.keySet()) { - Set myAssignments = this.reachingDefs.get(var); - Set subScopeAssignments = sub.reachingDefs.get(var); - if (subScopeAssignments == null) { + // we don't have to double the capacity since they're normally of the same size + // (vars are deleted when exiting a block) + Set keysUnion = new HashSet<>(this.symtable.keySet()); + keysUnion.addAll(other.symtable.keySet()); + + for (VariableNameDeclaration var : keysUnion) { + VarLocalInfo thisInfo = this.symtable.get(var); + VarLocalInfo otherInfo = other.symtable.get(var); + if (thisInfo == otherInfo) { continue; } - joinSets(var, myAssignments, subScopeAssignments); - } - - for (VariableNameDeclaration var : sub.reachingDefs.keySet()) { - Set subScopeAssignments = sub.reachingDefs.get(var); - Set myAssignments = this.reachingDefs.get(var); - if (myAssignments == null) { - this.reachingDefs.put(var, subScopeAssignments); - continue; + if (otherInfo != null && thisInfo != null) { + this.symtable.put(var, thisInfo.absorb(otherInfo)); + } else if (otherInfo != null) { + this.symtable.put(var, otherInfo.copy()); } - joinSets(var, myAssignments, subScopeAssignments); } - return this; } - private void joinSets(VariableNameDeclaration var, Set set1, Set set2) { - Set newReaching = new HashSet<>(set1.size() + set2.size()); - newReaching.addAll(set2); - newReaching.addAll(set1); - this.reachingDefs.put(var, newReaching); - } - @Override public String toString() { - return reachingDefs.toString(); + return symtable.toString(); } } static class TargetStack { - final Deque unnamedTargets = new ArrayDeque<>(); - final Map namedTargets = new HashMap<>(); + final Deque unnamedTargets = new ArrayDeque<>(); + final Map namedTargets = new HashMap<>(); - void push(AlgoState state) { + void push(SpanInfo state) { unnamedTargets.push(state); } - AlgoState pop() { + SpanInfo pop() { return unnamedTargets.pop(); } - AlgoState peek() { + SpanInfo peek() { return unnamedTargets.getFirst(); } - AlgoState doBreak(AlgoState data,/* nullable */ String label) { + SpanInfo doBreak(SpanInfo data,/* nullable */ String label) { // basically, reaching defs at the point of the break // also reach after the break (wherever it lands) - AlgoState target; + SpanInfo target; if (label == null) { target = unnamedTargets.getFirst(); } else { From 36b0f831587452210d95378e12584ddd13783ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 27 Jun 2020 19:02:09 +0200 Subject: [PATCH 40/99] Make sure DFA issues are fixed Refs #1304, #399, #400, #1107, #1251, #1606, #1675, #1682, #2131 --- .../rule/errorprone/xml/UnusedAssignment.xml | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 59cd87e6c1..b9cd5dd5bc 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -2220,5 +2220,226 @@ class Foo { ]]> + + DU anomaly false positive? #1304 + 0 + dummySet) { + final String trimmedValue = trimToNull(stringValue); + return dummySet.stream() + .noneMatch(value -> value.equalsIgnoreCase(trimmedValue)); + } + + } + ]]> + + + + DataflowAnomalyAnalysis DU false positive #399 + 0 + + + + + DataflowAnomalyAnalysis: DD false positive #400 + 0 + args) { + T res = zeroS();// + + + + DU Anomaly #1107 + 0 + + + + + + DataflowAnomalyAnalysis: DD false positive for arrays #1251 + 0 + + + + + DU false positive in DataflowAnomalyAnalysis #1606 + 0 + = 0); + + if (n < 2) { + return n; + } else { + int a = 0; + int b = 1; + final int m = n - 1; + + for (int i = 0; i < m; i++) { + final int c = a; + a = b; + b = c + b; + } + + return b; + } + } + } + ]]> + + + + false-positive in DD-part of DataflowAnomalyAnalysis #1675 + 0 + formatterClass = findClass(formatterClassName); + formatter = formatterClass.getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException ignored) { + } + } + setFormatter((formatter == null) ? new SimpleFormatter() : formatter); + } + } + ]]> + + + + DU false positive in DataflowAnomalyAnalysis #1682 + 0 + { + bufferedMessages.forEach(bufferedMessage -> sendMessage(discordManager, bufferedMessage)); + return null; + }); + sendMessage(discordManager, logMessage); + } + } + } + ]]> + + + + + DataflowAnomalyAnalysis has false positive for object initialised outside loop. #2131 + 0 + list = new ArrayList<>(); + + public void run() { + String str = Thread.currentThread().getName() + " Element : %d"; + for (int i = 0; i < 10_000; i++) { + list.add(String.format(str, i)); + } + } + + public void runAgain() { + String str = Thread.currentThread().getName() + " Element : %d"; + for (int i = 0; i < 10_000; i++) + list.add(String.format(str, i)); + } + + public void runOnceMore() { + String str = Thread.currentThread().getName() + " Element : %d"; + list = IntStream.range(0, 10_000) + .mapToObj(i -> String.format(str, i)) + .collect(Collectors.toList()); + } + } + ]]> + + From b04954112631c6ac820b2e8daec994be0475d19f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 27 Jun 2020 22:18:20 +0200 Subject: [PATCH 41/99] Fix pmd warnings --- .../rule/errorprone/UnusedAssignmentRule.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 4881169e6d..e965bf45b4 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -800,7 +800,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return findVar(primary.getScope(), true, suffix.getImage()); } else { - if (prefix.getNumChildren() > 0 && (prefix.getChild(0) instanceof ASTName)) { + if (prefix.getNumChildren() > 0 && prefix.getChild(0) instanceof ASTName) { String prefixImage = prefix.getChild(0).getImage(); String varname = identOf(inLhs, prefixImage); if (primary.getNumChildren() > 1) { @@ -854,7 +854,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { while (scope != null) { VariableNameDeclaration result = getFromSingleScope(scope, name); if (result != null) { - if (scope instanceof ClassScope && scope != enclosingClassScope) { + if (scope instanceof ClassScope && scope != enclosingClassScope) { // NOPMD CompareObjectsWithEqual this is what we want // don't handle fields return null; } @@ -1018,9 +1018,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public String toString() { - return "VarLocalInfo{" + - "reachingDefs=" + reachingDefs + - '}'; + return "VarLocalInfo{reachingDefs=" + reachingDefs + '}'; } public VarLocalInfo copy() { @@ -1114,7 +1112,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return doFork(null, copyTable()); } - private HashMap copyTable() { + private Map copyTable() { HashMap copy = new HashMap<>(this.symtable.size()); for (VariableNameDeclaration var : this.symtable.keySet()) { copy.put(var, this.symtable.get(var).copy()); @@ -1129,7 +1127,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { SpanInfo abruptCompletion(SpanInfo target) { // if target == null then this will unwind all the parents SpanInfo parent = this; - while (parent != target && parent != null) { + while (parent != target && parent != null) { // NOPMD CompareObjectsWithEqual this is what we want if (parent.myFinally != null) { parent.myFinally.absorb(this); } @@ -1159,7 +1157,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { for (VariableNameDeclaration var : keysUnion) { VarLocalInfo thisInfo = this.symtable.get(var); VarLocalInfo otherInfo = other.symtable.get(var); - if (thisInfo == otherInfo) { + if (thisInfo == otherInfo) { // NOPMD CompareObjectsWithEqual this is what we want continue; } if (otherInfo != null && thisInfo != null) { @@ -1195,7 +1193,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return unnamedTargets.getFirst(); } - SpanInfo doBreak(SpanInfo data,/* nullable */ String label) { + SpanInfo doBreak(SpanInfo data, /* nullable */ String label) { // basically, reaching defs at the point of the break // also reach after the break (wherever it lands) SpanInfo target; From 4207c367402119ef636a03f01e8a9be9a1d882c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 27 Jun 2020 23:34:29 +0200 Subject: [PATCH 42/99] Fix some bugs --- .../rule/errorprone/UnusedAssignmentRule.java | 19 ++- .../rule/errorprone/xml/UnusedAssignment.xml | 139 ++++++++++++------ 2 files changed, 111 insertions(+), 47 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index e965bf45b4..53ff8b572d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -186,7 +186,11 @@ public class UnusedAssignmentRule extends AbstractJavaRule { reason = "goes out of scope"; } else if (killers.size() == 1) { AssignmentEntry k = killers.iterator().next(); - reason = "overwritten on line " + k.rhs.getBeginLine(); + if (k.rhs.equals(entry.rhs)) { + reason = "reassigned every iteration"; + } else { + reason = "overwritten on line " + k.rhs.getBeginLine(); + } } else { reason = joinLines("overwritten on lines ", killers); } @@ -215,6 +219,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (assignment.rhs instanceof ASTVariableInitializer) { format.append(isField ? "field initializer for" : "initializer for variable"); + } else if (assignment.rhs instanceof ASTVariableDeclaratorId) { + format.append("value of parameter"); // method param/ctor param/foreach param/exception param } else { if (assignment.rhs instanceof ASTPreIncrementExpression || assignment.rhs instanceof ASTPreDecrementExpression @@ -543,7 +549,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (node.isForeach()) { // the iterable expression JavaNode init = node.getChild(1); - ASTVariableDeclaratorId foreachVar = (ASTVariableDeclaratorId) node.getChild(0).getChild(1).getChild(0); + ASTVariableDeclaratorId foreachVar = ((ASTLocalVariableDeclaration) node.getChild(0)).iterator().next(); return handleLoop(node, (SpanInfo) data, init, null, null, body, true, foreachVar.getNameDeclaration()); } else { ASTForInit init = node.getFirstChildOfType(ASTForInit.class); @@ -574,6 +580,11 @@ public class UnusedAssignmentRule extends AbstractJavaRule { before = acceptOpt(cond, before); } + if (foreachVar != null) { + // in foreach loops, the loop variable is assigned before the first iteration + before.assign(foreachVar, (JavaNode) foreachVar.getNode()); + } + SpanInfo breakTarget = before.forkEmpty(); SpanInfo continueTarget = before.forkEmpty(); @@ -612,6 +623,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { result = result.absorb(before); } + if (foreachVar != null) { + result.deleteVar(foreachVar); + } + return result; } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index b9cd5dd5bc..a8f1336ec6 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -179,11 +179,12 @@ public class Foo { Local variable in loop - 2 - 10,19 + 3 + 10,11,19 The initializer for variable 'fail' is never used (overwritten on line 19) - The value assigned to variable 'fail' is never used (overwritten on line 19) + The value of parameter 'j' is never used (reassigned every iteration) + The value assigned to variable 'fail' is never used (reassigned every iteration) 3,5 The initializer for variable 'a' is never used (overwritten on line 5) - - The value assigned to variable 'a' is never used (overwritten on line 5) + The value assigned to variable 'a' is never used (reassigned every iteration) + + Foreach unused + 1 + 4 + + The value of parameter 'a' is never used (reassigned every iteration) + + + + While loop 1 0 @@ -367,7 +385,7 @@ class Test{ 4,7 The initializer for variable 'i' is never used (overwritten on line 7) - The value assigned to variable 'i' is never used (overwritten on line 7) + The value assigned to variable 'i' is never used (reassigned every iteration) - - - - - - - - - + + Test shortcut AND + 1 + 5 + + The initializer for variable 'k' is never used (overwritten on line 9) + + + void main(int[] bufline, int start, int bufsize) { - + int i = 0, j, k = 0; - - - + while (i < bufline.length + // this is AND + && bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) { - - - - - + bufline[j] = bufline[k]; + i++; + } + } +} - - + ]]> + - - - - - + + Test shortcut OR + 0 + + void main(int[] bufline, int start, int bufsize) { - + int i = 0, j, k = 0; - - - + while (i < bufline.length + // this is OR + || bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) { - - - - - - + // here j, k might be their initializers + bufline[j] = bufline[k]; + i++; + } + } +} - - + ]]> + Test shortcut OR @@ -2442,4 +2460,35 @@ class Foo { + + ClassCastException with annotated foreach var + 4 + + + + From 49f775be83dd3a62a04eba8089dabab754bf6ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 27 Jun 2020 23:38:01 +0200 Subject: [PATCH 43/99] Disable reporting of unused vars by default --- .../rule/errorprone/UnusedAssignmentRule.java | 65 +++++++++--- .../rule/errorprone/xml/UnusedAssignment.xml | 100 +++++++++++------- 2 files changed, 110 insertions(+), 55 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 53ff8b572d..0a46154050 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -42,6 +42,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTFinallyStatement; 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.ASTFormalParameter; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTInitializer; import net.sourceforge.pmd.lang.java.ast.ASTLabeledStatement; @@ -134,8 +135,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private static final PropertyDescriptor REPORT_UNUSED_VARS = PropertyFactory.booleanProperty("reportUnusedVariables") .desc("Report variables that are only initialized, and never read at all. " - + "The rule UnusedVariable already cares for that, so you can disable it if needed") - .defaultValue(true) + + "The rule UnusedVariable already cares for that, but you can enable it if needed") + .defaultValue(false) .build(); public UnusedAssignmentRule() { @@ -175,6 +176,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { Set killers = result.killRecord.get(entry); final String reason; if (killers == null || killers.isEmpty()) { + // var went out of scope before being used (no assignment kills it, yet it's unused) + if (isField) { // assignments to fields don't really go out of scope continue; @@ -183,7 +186,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { continue; } // This is a "DU" anomaly, the others are "DD" - reason = "goes out of scope"; + reason = null; } else if (killers.size() == 1) { AssignmentEntry k = killers.iterator().next(); if (k.rhs.equals(entry.rhs)) { @@ -200,7 +203,19 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } private boolean suppressUnusedVariableRuleOverlap(AssignmentEntry entry) { - return !getProperty(REPORT_UNUSED_VARS) && entry.rhs instanceof ASTVariableInitializer; + return !getProperty(REPORT_UNUSED_VARS) && entry.rhs instanceof ASTVariableInitializer + || entry.rhs instanceof ASTVariableDeclaratorId; + } + + private static String getKind(VariableNameDeclaration var) { + ASTVariableDeclaratorId id = (ASTVariableDeclaratorId) var.getNode(); + return id.isField() + ? "field" + : id.isResourceDeclaration() + ? "resource" + : id.isExceptionBlockParameter() + ? "exception parameter" + : "variable"; } private boolean isIgnorablePrefixIncrement(JavaNode assignment) { @@ -213,27 +228,30 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return false; } - private static String makeMessage(AssignmentEntry assignment, String reason, boolean isField) { + private static String makeMessage(AssignmentEntry assignment, /* Nullable */ String reason, boolean isField) { String varName = assignment.var.getName(); - StringBuilder format = new StringBuilder("The "); + StringBuilder result = new StringBuilder("The "); if (assignment.rhs instanceof ASTVariableInitializer) { - format.append(isField ? "field initializer for" + result.append(isField ? "field initializer for" : "initializer for variable"); } else if (assignment.rhs instanceof ASTVariableDeclaratorId) { - format.append("value of parameter"); // method param/ctor param/foreach param/exception param + result.append("value of ").append(getKind(assignment.var)); } else { if (assignment.rhs instanceof ASTPreIncrementExpression || assignment.rhs instanceof ASTPreDecrementExpression || assignment.rhs instanceof ASTPostfixExpression) { - format.append("updated value of "); + result.append("updated value of "); } else { - format.append("value assigned to "); + result.append("value assigned to "); } - format.append(isField ? "field" : "variable"); + result.append(isField ? "field" : "variable"); } - format.append(" ''").append(varName).append("''"); - format.append(" is never used (").append(reason).append(")"); - return format.toString(); + result.append(" ''").append(varName).append("''"); + result.append(" is never used"); + if (reason != null) { + result.append(" (").append(reason).append(")"); + } + return result.toString(); } private static String joinLines(String prefix, Set killers) { @@ -703,6 +721,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // following deals with assignment + @Override + public Object visit(ASTFormalParameter node, Object data) { + if (!node.isExplicitReceiverParameter()) { + ASTVariableDeclaratorId id = node.getVariableDeclaratorId(); + ((SpanInfo) data).assign(id.getNameDeclaration(), id); + } + return data; + } @Override public Object visit(ASTVariableDeclarator node, Object data) { @@ -711,6 +737,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (rhs != null) { rhs.jjtAccept(this, data); ((SpanInfo) data).assign(var, rhs); + } else { + ((SpanInfo) data).assign(var, node.getVariableId()); } return data; } @@ -989,6 +1017,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule { final Set allAssignments; final Set usedAssignments; + final Set unusedVars; + // track which assignments kill which // assignment -> killers(assignment) final Map> killRecord; @@ -999,15 +1029,19 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private GlobalAlgoState(Set allAssignments, Set usedAssignments, + Set unusedVars, Map> killRecord) { this.allAssignments = allAssignments; this.usedAssignments = usedAssignments; + this.unusedVars = unusedVars; this.killRecord = killRecord; + } private GlobalAlgoState() { this(new HashSet(), new HashSet(), + new HashSet(), new HashMap>()); } } @@ -1082,6 +1116,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (previous != null) { // those assignments were overwritten ("killed") for (AssignmentEntry killed : previous.reachingDefs) { + if (killed.rhs instanceof ASTVariableDeclaratorId && killed.rhs != rhs) { + continue; + } // java8: computeIfAbsent Set killers = global.killRecord.get(killed); if (killers == null) { diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index a8f1336ec6..ed126cab8e 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -38,6 +38,18 @@ public class Foo { DU anomaly + 0 + + + + DU anomaly (reportUnusedVariables) + true 1 Conditional flow 0 - 3 - 3,4,6 + true + 2 + 3,4 The initializer for variable 'j' is never used (overwritten on line 6) - The initializer for variable 'z' is never used (goes out of scope) - The value assigned to variable 'j' is never used (goes out of scope) + The initializer for variable 'z' is never used Conditional flow 1 + true 1 4 - The initializer for variable 'z' is never used (goes out of scope) + The initializer for variable 'z' is never used 3,6 The initializer for variable 'j' is never used (overwritten on lines 6 and 9) - The value assigned to variable 'j' is never used (goes out of scope) + The value assigned to variable 'j' is never used 3,6 The initializer for variable 'j' is never used (overwritten on lines 6 and 9) - The value assigned to variable 'j' is never used (goes out of scope) + The value assigned to variable 'j' is never used 10,11,19 The initializer for variable 'fail' is never used (overwritten on line 19) - The value of parameter 'j' is never used (reassigned every iteration) + The value of variable 'j' is never used (reassigned every iteration) The value assigned to variable 'fail' is never used (reassigned every iteration) 1 6 - The value assigned to variable 'b' is never used (goes out of scope) + The value assigned to variable 'b' is never used 1 4 - The value of parameter 'a' is never used (reassigned every iteration) + The value of variable 'a' is never used (reassigned every iteration) 1 7 - The value assigned to variable 'i' is never used (goes out of scope) + The value assigned to variable 'i' is never used 1 7 - The value assigned to variable 'a' is never used (goes out of scope) + The value assigned to variable 'a' is never used 2 7,8 - The value assigned to variable 'i' is never used (goes out of scope) - The value assigned to variable 'a' is never used (goes out of scope) + The value assigned to variable 'i' is never used + The value assigned to variable 'a' is never used 4 6,8,10,12 - The value assigned to variable 'a' is never used (goes out of scope) - The value assigned to variable 'a' is never used (goes out of scope) - The value assigned to variable 'a' is never used (goes out of scope) - The value assigned to variable 'a' is never used (goes out of scope) + The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used + The value assigned to variable 'a' is never used 1 9 - The value assigned to variable 'a' is never used (goes out of scope) + The value assigned to variable 'a' is never used 1 9 - The value assigned to variable 'i' is never used (goes out of scope) + The value assigned to variable 'i' is never used 1 5 - The value assigned to variable 't1' is never used (goes out of scope) + The value assigned to variable 't1' is never used 1 6 - The value assigned to variable 't1' is never used (goes out of scope) + The value assigned to variable 't1' is never used 1 7 - The value assigned to variable 't1' is never used (goes out of scope) + The value assigned to variable 't1' is never used Assignment in operand 4 + true 2 4,6 - The initializer for variable 't2' is never used (goes out of scope) - The value assigned to variable 't1' is never used (goes out of scope) + The initializer for variable 't2' is never used + The value assigned to variable 't1' is never used 1 4 - The value assigned to variable 'a' is never used (goes out of scope) + The value assigned to variable 'a' is never used 1 4 - The value assigned to variable 'a' is never used (goes out of scope) + The value assigned to variable 'a' is never used 3,5 The initializer for variable 'iter' is never used (overwritten on line 4) - The value assigned to variable 'iter' is never used (goes out of scope) + The value assigned to variable 'iter' is never used 5,6 The initializer for variable 'k' is never used (overwritten on line 6) - The value assigned to variable 'k' is never used (goes out of scope) + The value assigned to variable 'k' is never used Lambda returns 2 + true 2 4,7 - The initializer for variable 'splitEvents' is never used (goes out of scope) - The value assigned to variable 'events' is never used (goes out of scope) + The initializer for variable 'splitEvents' is never used + The value assigned to variable 'events' is never used 1 5 - The updated value of variable 'i' is never used (goes out of scope) + The updated value of variable 'i' is never used 1 6 - The updated value of variable 'i' is never used (goes out of scope) + The updated value of variable 'i' is never used 1 5 - The updated value of variable 'i' is never used (goes out of scope) + The updated value of variable 'i' is never used 1 5 - The updated value of variable 'i' is never used (goes out of scope) + The updated value of variable 'i' is never used 1 6 - The updated value of variable 'i' is never used (goes out of scope) + The updated value of variable 'i' is never used 1 6 - The updated value of variable 'i' is never used (goes out of scope) + The updated value of variable 'i' is never used Test local class + true 2 4,6 - The initializer for variable 'shadowed' is never used (goes out of scope) + The initializer for variable 'shadowed' is never used The field initializer for 'f' is never used (overwritten on line 8) 4,5,6 The initializer for variable 'shadowed' is never used (overwritten on line 5) - The value assigned to variable 'shadowed' is never used (goes out of scope) + The value assigned to variable 'shadowed' is never used The field initializer for 'f' is never used (overwritten on line 8) Test shortcut OR + true 0 The initializer for variable 'i' is never used (overwritten on line 7) The value assigned to variable 'j' is never used (overwritten on line 8) - The value assigned to variable 'j' is never used (goes out of scope) + The value assigned to variable 'j' is never used Date: Sun, 28 Jun 2020 02:20:31 +0200 Subject: [PATCH 44/99] Implement loop condition flow --- .../rule/errorprone/UnusedAssignmentRule.java | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 0a46154050..04f0b878de 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -5,6 +5,8 @@ package net.sourceforge.pmd.lang.java.rule.errorprone; +import static net.sourceforge.pmd.lang.java.rule.codestyle.ConfusingTernaryRule.unwrapParentheses; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -75,7 +77,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTYieldStatement; import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.rule.codestyle.ConfusingTernaryRule; import net.sourceforge.pmd.lang.java.symboltable.ClassScope; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; import net.sourceforge.pmd.lang.symboltable.Scope; @@ -381,6 +382,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return makeConditional(before, node.getCondition(), node.getChild(1), node.getChild(2)); } + // This will be much easier with the 7.0 grammar..... SpanInfo makeConditional(SpanInfo before, JavaNode condition, JavaNode thenBranch, JavaNode elseBranch) { SpanInfo thenState = before.fork(); SpanInfo elseState = elseBranch != null ? before.fork() : before; @@ -414,9 +416,17 @@ public class UnusedAssignmentRule extends AbstractJavaRule { * This is how it works, but the and branch are * visited only once, because it's not done in this method, but * in makeConditional. + * + * @return the state in which all expressions have been evaluated + * Eg for `a || b`, this is the `else` state (all evaluated to false) + * Eg for `a && b`, this is the `then` state (all evaluated to true) + * */ private SpanInfo linkConditional(SpanInfo before, JavaNode condition, SpanInfo thenState, SpanInfo elseState, boolean isTopLevel) { - condition = ConfusingTernaryRule.unwrapParentheses(condition); + if (condition == null) { + return before; + } + condition = unwrapParentheses(condition); if (condition instanceof ASTConditionalOrExpression) { return visitShortcutOrExpr(condition, before, thenState, elseState); @@ -589,13 +599,19 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // TODO linkConditional final GlobalAlgoState globalState = before.global; + SpanInfo breakTarget = before.forkEmpty(); + SpanInfo continueTarget = before.forkEmpty(); + pushTargets(loop, breakTarget, continueTarget); + // perform a few "iterations", to make sure that assignments in // the body can affect themselves in the next iteration, and // that they affect the condition, etc before = acceptOpt(init, before); - if (checkFirstIter) { // false for do-while - before = acceptOpt(cond, before); + if (checkFirstIter && cond != null) { // false for do-while + SpanInfo ifcondTrue = before.forkEmpty(); + linkConditional(before, cond, ifcondTrue, breakTarget, true); + before = ifcondTrue; } if (foreachVar != null) { @@ -603,10 +619,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { before.assign(foreachVar, (JavaNode) foreachVar.getNode()); } - SpanInfo breakTarget = before.forkEmpty(); - SpanInfo continueTarget = before.forkEmpty(); - - pushTargets(loop, breakTarget, continueTarget); // make the defs of the body reach the other parts of the loop, // including itself @@ -619,7 +631,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { iter = acceptOpt(update, iter); } - iter = acceptOpt(cond, iter); + linkConditional(iter, cond, iter, breakTarget, true); iter = acceptOpt(body, iter); @@ -627,7 +639,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { continueTarget = globalState.continueTargets.peek(); if (!continueTarget.symtable.isEmpty()) { // make assignments before a continue reach the other parts of the loop - continueTarget = acceptOpt(cond, continueTarget); + + linkConditional(continueTarget, cond, continueTarget, breakTarget, true); + continueTarget = acceptOpt(body, continueTarget); continueTarget = acceptOpt(update, continueTarget); } From 388e29a9debba65ccba4af181e8f5309c31f30ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 28 Jun 2020 16:35:27 +0200 Subject: [PATCH 45/99] Fix unused exception in loop --- .../pmd/lang/java/ast/ASTCatchStatement.java | 6 +++++ .../rule/errorprone/UnusedAssignmentRule.java | 10 ++++++-- .../rule/errorprone/xml/UnusedAssignment.xml | 24 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCatchStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCatchStatement.java index f8df8abe83..98e8a30b78 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCatchStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCatchStatement.java @@ -101,4 +101,10 @@ public class ASTCatchStatement extends AbstractJavaNode { return getFirstDescendantOfType(ASTVariableDeclaratorId.class).getImage(); } + /** + * Returns the declarator id for the exception parameter. + */ + public ASTVariableDeclaratorId getExceptionId() { + return getFirstChildOfType(ASTFormalParameter.class).getVariableDeclaratorId(); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 04f0b878de..f8702d21f5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -108,7 +108,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { or at least proper graph algorithms like toposort) -> this is pretty invisible as it causes false negatives, not FPs * test ternary expr - * conditional exprs in loops DONE * conditionals @@ -124,6 +123,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { * test local class/anonymous class * shortcut conditionals have their own control-flow * parenthesized expressions + * conditional exprs in loops */ @@ -543,6 +543,13 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return finalState; } + @Override + public Object visit(ASTCatchStatement node, Object data) { + SpanInfo result = (SpanInfo) visit((JavaNode) node, data); + result.deleteVar(node.getExceptionId().getNameDeclaration()); + return result; + } + @Override public Object visit(ASTLambdaExpression node, Object data) { // Lambda expression have control flow that is separate from the method @@ -596,7 +603,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { JavaNode body, boolean checkFirstIter, VariableNameDeclaration foreachVar) { - // TODO linkConditional final GlobalAlgoState globalState = before.global; SpanInfo breakTarget = before.forkEmpty(); diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index ed126cab8e..0e3cd13a06 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -2509,4 +2509,28 @@ class Foo { + + Catch in loop + 0 + + + + From 3d7aaf7cda1f1e7fb09e2856e3e8de1fc5a85dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 28 Jun 2020 17:03:57 +0200 Subject: [PATCH 46/99] Fix FPs with reportUnusedVariables & foreach vars --- .../rule/errorprone/UnusedAssignmentRule.java | 35 +++++++++++++------ .../rule/errorprone/xml/UnusedAssignment.xml | 22 +++++++----- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index f8702d21f5..b2c1132db0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -191,7 +191,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } else if (killers.size() == 1) { AssignmentEntry k = killers.iterator().next(); if (k.rhs.equals(entry.rhs)) { - reason = "reassigned every iteration"; + // assignment reassigns itself, only possible in a loop + if (suppressUnusedVariableRuleOverlap(entry)) { + continue; + } else if (entry.rhs instanceof ASTVariableDeclaratorId) { + reason = null; // unused foreach variable + } else { + reason = "reassigned every iteration"; + } } else { reason = "overwritten on line " + k.rhs.getBeginLine(); } @@ -204,19 +211,24 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } private boolean suppressUnusedVariableRuleOverlap(AssignmentEntry entry) { - return !getProperty(REPORT_UNUSED_VARS) && entry.rhs instanceof ASTVariableInitializer - || entry.rhs instanceof ASTVariableDeclaratorId; + return !getProperty(REPORT_UNUSED_VARS) && (entry.rhs instanceof ASTVariableInitializer + || entry.rhs instanceof ASTVariableDeclaratorId); } private static String getKind(VariableNameDeclaration var) { ASTVariableDeclaratorId id = (ASTVariableDeclaratorId) var.getNode(); - return id.isField() - ? "field" - : id.isResourceDeclaration() - ? "resource" - : id.isExceptionBlockParameter() - ? "exception parameter" - : "variable"; + if (id.isField()) { + return "field"; + } else if (id.isResourceDeclaration()) { + return "resource"; + } else if (id.isExceptionBlockParameter()) { + return "exception parameter"; + } else if (id.getNthParent(3) instanceof ASTForStatement) { + return "loop variable"; + } else if (id.isFormalParameter()) { + return "parameter"; + } + return "variable"; } private boolean isIgnorablePrefixIncrement(JavaNode assignment) { @@ -236,7 +248,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { result.append(isField ? "field initializer for" : "initializer for variable"); } else if (assignment.rhs instanceof ASTVariableDeclaratorId) { - result.append("value of ").append(getKind(assignment.var)); + result.append(getKind(assignment.var)); } else { if (assignment.rhs instanceof ASTPreIncrementExpression || assignment.rhs instanceof ASTPreDecrementExpression @@ -252,6 +264,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (reason != null) { result.append(" (").append(reason).append(")"); } + result.setCharAt(0, Character.toUpperCase(result.charAt(0))); return result.toString(); } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 0e3cd13a06..bdc70fa4ce 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -193,11 +193,13 @@ public class Foo { Local variable in loop - 3 - 10,11,19 + true + 4 + 2,10,11,19 + The parameter 'args' is never used The initializer for variable 'fail' is never used (overwritten on line 19) - The value of variable 'j' is never used (reassigned every iteration) + The loop variable 'j' is never used The value assigned to variable 'fail' is never used (reassigned every iteration) Foreach unused - 1 - 4 + true + 2 + 2,4 - The value of variable 'a' is never used (reassigned every iteration) + The parameter 'args' is never used + The loop variable 'a' is never used Assignment in operand 4 true - 2 - 4,6 + 3 + 2,4,6 + The parameter 'args' is never used The initializer for variable 't2' is never used The value assigned to variable 't1' is never used @@ -2480,6 +2485,7 @@ class Foo { ClassCastException with annotated foreach var + true 4 Date: Sun, 28 Jun 2020 17:08:32 +0200 Subject: [PATCH 47/99] Shorten messages a bit --- .../rule/errorprone/UnusedAssignmentRule.java | 10 ++--- .../rule/errorprone/xml/UnusedAssignment.xml | 38 ++++++++++++++++--- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index b2c1132db0..5ba849c4a4 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -243,19 +243,19 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private static String makeMessage(AssignmentEntry assignment, /* Nullable */ String reason, boolean isField) { String varName = assignment.var.getName(); - StringBuilder result = new StringBuilder("The "); + StringBuilder result = new StringBuilder(64); if (assignment.rhs instanceof ASTVariableInitializer) { - result.append(isField ? "field initializer for" - : "initializer for variable"); + result.append(isField ? "the field initializer for" + : "the initializer for variable"); } else if (assignment.rhs instanceof ASTVariableDeclaratorId) { result.append(getKind(assignment.var)); } else { if (assignment.rhs instanceof ASTPreIncrementExpression || assignment.rhs instanceof ASTPreDecrementExpression || assignment.rhs instanceof ASTPostfixExpression) { - result.append("updated value of "); + result.append("the updated value of "); } else { - result.append("value assigned to "); + result.append("the value assigned to "); } result.append(isField ? "field" : "variable"); } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index bdc70fa4ce..5705056ad4 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -197,9 +197,9 @@ public class Foo { 4 2,10,11,19 - The parameter 'args' is never used + Parameter 'args' is never used The initializer for variable 'fail' is never used (overwritten on line 19) - The loop variable 'j' is never used + Loop variable 'j' is never used The value assigned to variable 'fail' is never used (reassigned every iteration) 2 2,4 - The parameter 'args' is never used - The loop variable 'a' is never used + Parameter 'args' is never used + Loop variable 'a' is never used 3 2,4,6 - The parameter 'args' is never used + Parameter 'args' is never used The initializer for variable 't2' is never used The value assigned to variable 't1' is never used @@ -2538,5 +2538,33 @@ class Foo { ]]> + + Catch in loop (reportUnusedVariables) + true + 1 + 8 + + Exception parameter 'e' is never used + + + + From a6d9d595ddaf9fe3efaffe07a7ec5936a06902fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 28 Jun 2020 17:26:23 +0200 Subject: [PATCH 48/99] Add test case for unused var --- .../rule/errorprone/UnusedAssignmentRule.java | 2 +- .../rule/errorprone/xml/UnusedAssignment.xml | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 5ba849c4a4..73750a6423 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -87,7 +87,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { /* Detects unused assignments. This performs a reaching definition - analysis. + analysis. This makes the assumption that there is no dead code. This DFA can be modified trivially to check for all unused variables (just maintain a global set of variables that diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index 5705056ad4..a3f53ec29b 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -2567,4 +2567,63 @@ class Foo { + + Catch in loop (reportUnusedVariables) + true + 6 + + + + From 82995fbe077bfa3358093f6776de227a9c77fddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 28 Jun 2020 17:57:45 +0200 Subject: [PATCH 49/99] Fix unused formal value FN --- .../rule/errorprone/UnusedAssignmentRule.java | 9 ++- .../rule/errorprone/xml/UnusedAssignment.xml | 63 +++++++++++++++++-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 73750a6423..9bf3e9fd1b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -242,12 +242,17 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } private static String makeMessage(AssignmentEntry assignment, /* Nullable */ String reason, boolean isField) { + // if reason is null, then the variable is unused (at most assigned to) + String varName = assignment.var.getName(); StringBuilder result = new StringBuilder(64); if (assignment.rhs instanceof ASTVariableInitializer) { result.append(isField ? "the field initializer for" : "the initializer for variable"); } else if (assignment.rhs instanceof ASTVariableDeclaratorId) { + if (reason != null) { + result.append("the initial value of "); + } result.append(getKind(assignment.var)); } else { if (assignment.rhs instanceof ASTPreIncrementExpression @@ -1149,7 +1154,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (previous != null) { // those assignments were overwritten ("killed") for (AssignmentEntry killed : previous.reachingDefs) { - if (killed.rhs instanceof ASTVariableDeclaratorId && killed.rhs != rhs) { + if (killed.rhs instanceof ASTVariableDeclaratorId + && killed.rhs.getParent() instanceof ASTVariableDeclarator + && killed.rhs != rhs) { continue; } // java8: computeIfAbsent diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index a3f53ec29b..e64ac01d58 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -1684,7 +1684,7 @@ class Foo { - FP with array access + FP with array access 0 0 - FP with array access - 0 + FP with array access 1 + 1 + 2 + + The initial value of parameter 'arr' is never used (overwritten on line 3) + FP with long access 2 - 0 + 1 + 2 + + The initial value of parameter 'arr' is never used (overwritten on line 3) + - FP with long access 2 - 0 + FP with long access 3 + 1 + 2 + + The initial value of parameter 'arr' is never used (overwritten on line 3) + + + + Unused formal value + true + 1 + 2 + + The initial value of parameter 'i' is never used (overwritten on line 3) + + + + + + Unused formal value + false + 1 + + The initial value of parameter 'i' is never used (overwritten on line 3) + + + + + From 734da43fbf971c10249460f5e1a16e50c1a4824d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 28 Jun 2020 18:10:01 +0200 Subject: [PATCH 50/99] Ignore vars that start with "ignore" --- .../rule/errorprone/UnusedAssignmentRule.java | 12 ++ .../resources/category/java/errorprone.xml | 35 ++++++ .../rule/errorprone/xml/UnusedAssignment.xml | 106 ++++++++++++++++++ 3 files changed, 153 insertions(+) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java index 9bf3e9fd1b..3822ce73f7 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java @@ -124,6 +124,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { * shortcut conditionals have their own control-flow * parenthesized expressions * conditional exprs in loops + * ignore variables that start with 'ignore' */ @@ -205,11 +206,22 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } else { reason = joinLines("overwritten on lines ", killers); } + if (reason == null && hasExplicitIgnorableName(entry.var.getName())) { + // Then the variable is never used (cf UnusedVariable) + // We ignore those that start with "ignored", as that is standard + // practice for exceptions, and may be useful for resources/foreach vars + continue; + } addViolationWithMessage(ruleCtx, entry.rhs, makeMessage(entry, reason, isField)); } } } + private boolean hasExplicitIgnorableName(String name) { + return name.startsWith("ignored") + || "_".equals(name); // before java 9 it's ok + } + private boolean suppressUnusedVariableRuleOverlap(AssignmentEntry entry) { return !getProperty(REPORT_UNUSED_VARS) && (entry.rhs instanceof ASTVariableInitializer || entry.rhs instanceof ASTVariableDeclaratorId); diff --git a/pmd-java/src/main/resources/category/java/errorprone.xml b/pmd-java/src/main/resources/category/java/errorprone.xml index dd81623fc1..94a7f83d25 100644 --- a/pmd-java/src/main/resources/category/java/errorprone.xml +++ b/pmd-java/src/main/resources/category/java/errorprone.xml @@ -3294,6 +3294,11 @@ public String convert(int x) { or static fields of the current class in static initializers. The rule may be suppressed with the standard `@SuppressWarnings("unused")` tag. + + The rule subsumes UnusedLocalVariable, and UnusedFormalParameter. Those violations are filtered + out by default, in case you already have enabled those rules, but may be enabled with the property + `reportUnusedVariable`. Variables whose name starts with `ignored` are filtered out, as + is standard practice for exceptions. 3 @@ -3350,6 +3355,36 @@ class C { // is not very important } +} + ]]> + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml index e64ac01d58..f637fb8f9d 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnusedAssignment.xml @@ -2676,5 +2676,111 @@ class Foo { ]]> + + Test ignored name 0 + true + 1 + + + + + Test ignored name 1 + true + 0 + + + + + Test ignored name 2 + true + 0 + + java 7 + + + + Test ignored name 2 + true + 1 + + + + + Test ignored name 2 + false + 0 + + + + + Test annot suppression + true + 0 + + + From 8c30440843f9ddd7663efcfbe2dbec0babcb8fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 28 Jun 2020 18:14:38 +0200 Subject: [PATCH 51/99] Move to bestpractices.xml --- .../UnusedAssignmentRule.java | 2 +- .../resources/category/java/bestpractices.xml | 114 +++++++++++++++++ .../resources/category/java/errorprone.xml | 116 ------------------ .../UnusedAssignmentTest.java | 4 +- .../xml/UnusedAssignment.xml | 0 5 files changed, 117 insertions(+), 119 deletions(-) rename pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/{errorprone => bestpractices}/UnusedAssignmentRule.java (99%) rename pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/{errorprone => bestpractices}/UnusedAssignmentTest.java (78%) rename pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/{errorprone => bestpractices}/xml/UnusedAssignment.xml (100%) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedAssignmentRule.java similarity index 99% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedAssignmentRule.java index 3822ce73f7..536d7d2816 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedAssignmentRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.errorprone; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import static net.sourceforge.pmd.lang.java.rule.codestyle.ConfusingTernaryRule.unwrapParentheses; diff --git a/pmd-java/src/main/resources/category/java/bestpractices.xml b/pmd-java/src/main/resources/category/java/bestpractices.xml index 9c2dbf7138..a295c1b48b 100644 --- a/pmd-java/src/main/resources/category/java/bestpractices.xml +++ b/pmd-java/src/main/resources/category/java/bestpractices.xml @@ -1298,6 +1298,120 @@ class Foo{ + + + Reports assignments to variables that are never used before the variable is overwritten, + or goes out of scope. Unused assignments are those for which + 1. The variable is never read after the assignment, or + 2. The assigned value is always overwritten by other assignments before the next read of + the variable. + + The rule doesn't consider assignments to fields except for those of `this` in a constructor, + or static fields of the current class in static initializers. + + The rule may be suppressed with the standard `@SuppressWarnings("unused")` tag. + + The rule subsumes UnusedLocalVariable, and UnusedFormalParameter. Those violations are filtered + out by default, in case you already have enabled those rules, but may be enabled with the property + `reportUnusedVariable`. Variables whose name starts with `ignored` are filtered out, as + is standard practice for exceptions. + + 3 + + + + + + + + + + + + + + - - - - Reports assignments to variables that are never used before the variable is overwritten, - or goes out of scope. Unused assignments are those for which - 1. The variable is never read after the assignment, or - 2. The assigned value is always overwritten by other assignments before the next read of - the variable. - - The rule doesn't consider assignments to fields except for those of `this` in a constructor, - or static fields of the current class in static initializers. - - The rule may be suppressed with the standard `@SuppressWarnings("unused")` tag. - - The rule subsumes UnusedLocalVariable, and UnusedFormalParameter. Those violations are filtered - out by default, in case you already have enabled those rules, but may be enabled with the property - `reportUnusedVariable`. Variables whose name starts with `ignored` are filtered out, as - is standard practice for exceptions. - - 3 - - - - - - - - - - - - - - - Date: Sun, 28 Jun 2020 18:41:33 +0200 Subject: [PATCH 52/99] Remove changes to VariableNameDeclaration Probably this is what causes DFAAR to change behavior. The current behavior is wrong (two variables are not equal just from their name, that doesn't account for whether they're declared independently or shadow a declaration). But whatever, this will be scrapped in 7.0 --- .../bestpractices/UnusedAssignmentRule.java | 101 ++++++++---------- .../symboltable/VariableNameDeclaration.java | 4 +- 2 files changed, 48 insertions(+), 57 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedAssignmentRule.java index 536d7d2816..75d31c4c4a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedAssignmentRule.java @@ -89,19 +89,16 @@ public class UnusedAssignmentRule extends AbstractJavaRule { Detects unused assignments. This performs a reaching definition analysis. This makes the assumption that there is no dead code. - This DFA can be modified trivially to check for all - unused variables (just maintain a global set of variables that - must be used, adding them as you go, and on each AlgoState::use, - remove the var from this set). This would work even without variable - usage pre-resolution (which in 7.0 is not implemented yet and - maybe won't be). - Since we have the reaching definitions at each variable usage, we could also use that to detect other kinds of bug, eg conditions that are always true, or dereferences that will always NPE. In the general case though, this is complicated and better left to a DFA library, eg google Z3. + This analysis may be used as-is to detect switch labels that + fall-through, which could be useful to improve accuracy of other + rules. + TODO * labels on arbitrary statements (currently only loops) * explicit ctor call (hard to impossible without type res, @@ -173,14 +170,13 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (isIgnorablePrefixIncrement(entry.rhs)) { continue; } - boolean isField = entry.var.getNode().getScope() instanceof ClassScope; Set killers = result.killRecord.get(entry); final String reason; if (killers == null || killers.isEmpty()) { // var went out of scope before being used (no assignment kills it, yet it's unused) - if (isField) { + if (entry.var.isField()) { // assignments to fields don't really go out of scope continue; } else if (suppressUnusedVariableRuleOverlap(entry)) { @@ -212,7 +208,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // practice for exceptions, and may be useful for resources/foreach vars continue; } - addViolationWithMessage(ruleCtx, entry.rhs, makeMessage(entry, reason, isField)); + addViolationWithMessage(ruleCtx, entry.rhs, makeMessage(entry, reason, entry.var.isField())); } } } @@ -227,8 +223,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { || entry.rhs instanceof ASTVariableDeclaratorId); } - private static String getKind(VariableNameDeclaration var) { - ASTVariableDeclaratorId id = (ASTVariableDeclaratorId) var.getNode(); + private static String getKind(ASTVariableDeclaratorId id) { if (id.isField()) { return "field"; } else if (id.isResourceDeclaration()) { @@ -342,7 +337,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // next iteration SpanInfo state = (SpanInfo) data; - Set localsToKill = new HashSet<>(); + Set localsToKill = new HashSet<>(); for (JavaNode child : node.children()) { // each output is passed as input to the next (most relevant for blocks) @@ -351,12 +346,12 @@ public class UnusedAssignmentRule extends AbstractJavaRule { && child.getChild(0) instanceof ASTLocalVariableDeclaration) { ASTLocalVariableDeclaration local = (ASTLocalVariableDeclaration) child.getChild(0); for (ASTVariableDeclaratorId id : local) { - localsToKill.add(id.getNameDeclaration()); + localsToKill.add(id); } } } - for (VariableNameDeclaration var : localsToKill) { + for (ASTVariableDeclaratorId var : localsToKill) { state.deleteVar(var); } @@ -576,7 +571,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { @Override public Object visit(ASTCatchStatement node, Object data) { SpanInfo result = (SpanInfo) visit((JavaNode) node, data); - result.deleteVar(node.getExceptionId().getNameDeclaration()); + result.deleteVar(node.getExceptionId()); return result; } @@ -615,7 +610,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // the iterable expression JavaNode init = node.getChild(1); ASTVariableDeclaratorId foreachVar = ((ASTLocalVariableDeclaration) node.getChild(0)).iterator().next(); - return handleLoop(node, (SpanInfo) data, init, null, null, body, true, foreachVar.getNameDeclaration()); + return handleLoop(node, (SpanInfo) data, init, null, null, body, true, foreachVar); } else { ASTForInit init = node.getFirstChildOfType(ASTForInit.class); ASTExpression cond = node.getCondition(); @@ -632,7 +627,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { JavaNode update, JavaNode body, boolean checkFirstIter, - VariableNameDeclaration foreachVar) { + ASTVariableDeclaratorId foreachVar) { final GlobalAlgoState globalState = before.global; SpanInfo breakTarget = before.forkEmpty(); @@ -652,7 +647,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (foreachVar != null) { // in foreach loops, the loop variable is assigned before the first iteration - before.assign(foreachVar, (JavaNode) foreachVar.getNode()); + before.assign(foreachVar, foreachVar); } @@ -662,7 +657,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { if (foreachVar != null && iter.hasVar(foreachVar)) { // in foreach loops, the loop variable is reassigned on each update - iter.assign(foreachVar, (JavaNode) foreachVar.getNode()); + iter.assign(foreachVar, foreachVar); } else { iter = acceptOpt(update, iter); } @@ -775,14 +770,14 @@ public class UnusedAssignmentRule extends AbstractJavaRule { public Object visit(ASTFormalParameter node, Object data) { if (!node.isExplicitReceiverParameter()) { ASTVariableDeclaratorId id = node.getVariableDeclaratorId(); - ((SpanInfo) data).assign(id.getNameDeclaration(), id); + ((SpanInfo) data).assign(id, id); } return data; } @Override public Object visit(ASTVariableDeclarator node, Object data) { - VariableNameDeclaration var = node.getVariableId().getNameDeclaration(); + ASTVariableDeclaratorId var = node.getVariableId(); ASTVariableInitializer rhs = node.getInitializer(); if (rhs != null) { rhs.jjtAccept(this, data); @@ -814,7 +809,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { JavaNode rhs = node.getChild(2); result = acceptOpt(rhs, result); - VariableNameDeclaration lhsVar = getVarFromExpression(node.getChild(0), true); + ASTVariableDeclaratorId lhsVar = getVarFromExpression(node.getChild(0), true); if (lhsVar != null) { // in that case lhs is a normal variable (array access not supported) @@ -849,7 +844,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } private SpanInfo checkIncOrDecrement(JavaNode unary, SpanInfo data) { - VariableNameDeclaration var = getVarFromExpression(unary.getChild(0), true); + ASTVariableDeclaratorId var = getVarFromExpression(unary.getChild(0), true); if (var != null) { data.use(var); data.assign(var, unary); @@ -863,7 +858,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { public Object visit(ASTPrimaryExpression node, Object data) { SpanInfo state = (SpanInfo) visit((JavaNode) node, data); // visit subexpressions - VariableNameDeclaration var = getVarFromExpression(node, false); + ASTVariableDeclaratorId var = getVarFromExpression(node, false); if (var != null) { state.use(var); } @@ -873,7 +868,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { /** * Get the variable accessed from a primary. */ - private VariableNameDeclaration getVarFromExpression(JavaNode primary, boolean inLhs) { + private ASTVariableDeclaratorId getVarFromExpression(JavaNode primary, boolean inLhs) { if (primary instanceof ASTPrimaryExpression) { ASTPrimaryPrefix prefix = (ASTPrimaryPrefix) primary.getChild(0); @@ -935,7 +930,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { : name.substring(0, i); } - private VariableNameDeclaration findVar(Scope scope, boolean isField, String name) { + private ASTVariableDeclaratorId findVar(Scope scope, boolean isField, String name) { if (name == null) { return null; } @@ -945,7 +940,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } while (scope != null) { - VariableNameDeclaration result = getFromSingleScope(scope, name); + ASTVariableDeclaratorId result = getFromSingleScope(scope, name); if (result != null) { if (scope instanceof ClassScope && scope != enclosingClassScope) { // NOPMD CompareObjectsWithEqual this is what we want // don't handle fields @@ -960,11 +955,11 @@ public class UnusedAssignmentRule extends AbstractJavaRule { return null; } - private VariableNameDeclaration getFromSingleScope(Scope scope, String name) { + private ASTVariableDeclaratorId getFromSingleScope(Scope scope, String name) { if (scope != null) { for (VariableNameDeclaration decl : scope.getDeclarations(VariableNameDeclaration.class).keySet()) { if (decl.getImage().equals(name)) { - return decl; + return (ASTVariableDeclaratorId) decl.getNode(); } } } @@ -1050,10 +1045,11 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // assignments that reach the end of any constructor must // be considered used for (VariableNameDeclaration field : visitor.enclosingClassScope.getVariableDeclarations().keySet()) { + ASTVariableDeclaratorId var = (ASTVariableDeclaratorId) field.getNode(); if (field.getAccessNodeParent().isStatic()) { - staticInit.use(field); + staticInit.use(var); } - ctorEndState.use(field); + ctorEndState.use(var); } } } @@ -1067,8 +1063,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { final Set allAssignments; final Set usedAssignments; - final Set unusedVars; - // track which assignments kill which // assignment -> killers(assignment) final Map> killRecord; @@ -1079,11 +1073,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private GlobalAlgoState(Set allAssignments, Set usedAssignments, - Set unusedVars, Map> killRecord) { this.allAssignments = allAssignments; this.usedAssignments = usedAssignments; - this.unusedVars = unusedVars; this.killRecord = killRecord; } @@ -1091,7 +1083,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule { private GlobalAlgoState() { this(new HashSet(), new HashSet(), - new HashSet(), new HashMap>()); } } @@ -1142,25 +1133,25 @@ public class UnusedAssignmentRule extends AbstractJavaRule { final GlobalAlgoState global; - final Map symtable; + final Map symtable; private SpanInfo(GlobalAlgoState global) { - this(null, global, new HashMap()); + this(null, global, new HashMap()); } private SpanInfo(SpanInfo parent, GlobalAlgoState global, - Map symtable) { + Map symtable) { this.parent = parent; this.global = global; this.symtable = symtable; } - boolean hasVar(VariableNameDeclaration var) { + boolean hasVar(ASTVariableDeclaratorId var) { return symtable.containsKey(var); } - void assign(VariableNameDeclaration var, JavaNode rhs) { + void assign(ASTVariableDeclaratorId var, JavaNode rhs) { AssignmentEntry entry = new AssignmentEntry(var, rhs); VarLocalInfo previous = symtable.put(var, new VarLocalInfo(Collections.singleton(entry))); if (previous != null) { @@ -1183,7 +1174,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { global.allAssignments.add(entry); } - void use(VariableNameDeclaration var) { + void use(ASTVariableDeclaratorId var) { VarLocalInfo info = symtable.get(var); // may be null for implicit assignments, like method parameter if (info != null) { @@ -1191,7 +1182,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } } - void deleteVar(VariableNameDeclaration var) { + void deleteVar(ASTVariableDeclaratorId var) { symtable.remove(var); } @@ -1204,27 +1195,27 @@ public class UnusedAssignmentRule extends AbstractJavaRule { } SpanInfo forkEmpty() { - return doFork(this, new HashMap()); + return doFork(this, new HashMap()); } SpanInfo forkEmptyNonLocal() { - return doFork(null, new HashMap()); + return doFork(null, new HashMap()); } SpanInfo forkCapturingNonLocal() { return doFork(null, copyTable()); } - private Map copyTable() { - HashMap copy = new HashMap<>(this.symtable.size()); - for (VariableNameDeclaration var : this.symtable.keySet()) { + private Map copyTable() { + HashMap copy = new HashMap<>(this.symtable.size()); + for (ASTVariableDeclaratorId var : this.symtable.keySet()) { copy.put(var, this.symtable.get(var).copy()); } return copy; } - private SpanInfo doFork(SpanInfo parent, Map reaching) { + private SpanInfo doFork(SpanInfo parent, Map reaching) { return new SpanInfo(parent, this.global, reaching); } @@ -1255,10 +1246,10 @@ public class UnusedAssignmentRule extends AbstractJavaRule { // we don't have to double the capacity since they're normally of the same size // (vars are deleted when exiting a block) - Set keysUnion = new HashSet<>(this.symtable.keySet()); + Set keysUnion = new HashSet<>(this.symtable.keySet()); keysUnion.addAll(other.symtable.keySet()); - for (VariableNameDeclaration var : keysUnion) { + for (ASTVariableDeclaratorId var : keysUnion) { VarLocalInfo thisInfo = this.symtable.get(var); VarLocalInfo otherInfo = other.symtable.get(var); if (thisInfo == otherInfo) { // NOPMD CompareObjectsWithEqual this is what we want @@ -1316,20 +1307,20 @@ public class UnusedAssignmentRule extends AbstractJavaRule { static class AssignmentEntry { - final VariableNameDeclaration var; + final ASTVariableDeclaratorId var; // this is not necessarily an expression, it may be also the // variable declarator of a foreach loop final JavaNode rhs; - AssignmentEntry(VariableNameDeclaration var, JavaNode rhs) { + AssignmentEntry(ASTVariableDeclaratorId var, JavaNode rhs) { this.var = var; this.rhs = rhs; } @Override public String toString() { - return var.getImage() + " := " + rhs; + return var.getName() + " := " + rhs; } @Override diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclaration.java index d2ba35112f..e23a04b7d6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclaration.java @@ -141,12 +141,12 @@ public class VariableNameDeclaration extends AbstractNameDeclaration implements return false; } VariableNameDeclaration n = (VariableNameDeclaration) o; - return n.node.equals(this.node); + return n.node.getImage().equals(node.getImage()); } @Override public int hashCode() { - return node.hashCode(); + return node.getImage().hashCode(); } @Override From d7f96e08d93513d8eced95c18de6a09d04f20c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 18:51:28 +0200 Subject: [PATCH 53/99] Uniformize apply across languages --- .../pmd/lang/apex/rule/AbstractApexRule.java | 8 ++----- .../pmd/lang/BaseLanguageModule.java | 21 +++++++++++++++++++ .../pmd/lang/java/rule/AbstractJavaRule.java | 10 ++------- .../rule/AbstractEcmascriptRule.java | 5 +++-- .../pmd/lang/jsp/rule/AbstractJspRule.java | 5 +++-- .../modelica/rule/AbstractModelicaRule.java | 5 +++-- .../lang/plsql/rule/AbstractPLSQLRule.java | 5 +++-- .../pmd/lang/scala/rule/ScalaRule.java | 4 ++-- .../pmd/lang/vf/rule/AbstractVfRule.java | 8 ++----- .../pmd/lang/vm/rule/AbstractVmRule.java | 5 +++-- .../pmd/lang/xml/rule/AbstractXmlRule.java | 5 +++-- 11 files changed, 47 insertions(+), 34 deletions(-) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRule.java index 8f1a29b57d..a5015c806f 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRule.java @@ -137,12 +137,8 @@ public abstract class AbstractApexRule extends AbstractRule protected void visitAll(List nodes, RuleContext ctx) { for (Object element : nodes) { - if (element instanceof ASTUserClass) { - visit((ASTUserClass) element, ctx); - } else if (element instanceof ASTUserInterface) { - visit((ASTUserInterface) element, ctx); - } else if (element instanceof ASTUserTrigger) { - visit((ASTUserTrigger) element, ctx); + if (element instanceof ApexNode) { + ((ApexNode) element).jjtAccept(this, ctx); } } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java index 633f6ac038..e5e1e53619 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java @@ -11,6 +11,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; + /** * Created by christoferdutz on 21.09.14. */ @@ -177,4 +182,20 @@ public abstract class BaseLanguageModule implements Language { public int compareTo(Language o) { return getName().compareTo(o.getName()); } + + + + private static class DefaultRulechainVisitor extends AbstractRuleChainVisitor { + + @Override + protected void visit(Rule rule, Node node, RuleContext ctx) { + rule.apply(Collections.singletonList(node), ctx); + } + + @Override + protected void indexNodes(List nodes, RuleContext ctx) { + + } + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java index 2c243390d3..867192a5cf 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java @@ -158,14 +158,8 @@ public abstract class AbstractJavaRule extends AbstractRule implements JavaParse protected void visitAll(List nodes, RuleContext ctx) { for (Object element : nodes) { - /* - It is important to note that we are assuming that all nodes here are of type Compilation Unit, - but our caller method may be called with any type of node, and that's why we need to check the kind - of instance of each element - */ - if (element instanceof ASTCompilationUnit) { - ASTCompilationUnit node = (ASTCompilationUnit) element; - visit(node, ctx); + if (element instanceof JavaNode) { + ((JavaNode) element).jjtAccept(this, ctx); } } } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/AbstractEcmascriptRule.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/AbstractEcmascriptRule.java index 61ab2aa106..30e5754cd6 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/AbstractEcmascriptRule.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/AbstractEcmascriptRule.java @@ -96,8 +96,9 @@ public abstract class AbstractEcmascriptRule extends AbstractRule protected void visitAll(List nodes, RuleContext ctx) { for (Object element : nodes) { - ASTAstRoot node = (ASTAstRoot) element; - visit(node, ctx); + if (element instanceof EcmascriptNode) { + ((EcmascriptNode) element).jjtAccept(this, ctx); + } } } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/AbstractJspRule.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/AbstractJspRule.java index 489f3e33fb..5867fe4057 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/AbstractJspRule.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/AbstractJspRule.java @@ -55,8 +55,9 @@ public abstract class AbstractJspRule extends AbstractRule implements JspParserV protected void visitAll(List nodes, RuleContext ctx) { for (Object element : nodes) { - JspNode node = (JspNode) element; - visit(node, ctx); + if (element instanceof JspNode) { + ((JspNode) element).jjtAccept(this, ctx); + } } } diff --git a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/AbstractModelicaRule.java b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/AbstractModelicaRule.java index 2f81acc675..aa876712e1 100644 --- a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/AbstractModelicaRule.java +++ b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/AbstractModelicaRule.java @@ -172,8 +172,9 @@ public abstract class AbstractModelicaRule extends AbstractRule implements Model protected void visitAll(final List nodes, final RuleContext ctx) { for (final Object element : nodes) { - final ASTStoredDefinition node = (ASTStoredDefinition) element; - visit(node, ctx); + if (element instanceof ModelicaNode) { + ((ModelicaNode) element).jjtAccept(this, ctx); + } } } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java index f1c776c8dc..274897c1fd 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java @@ -256,8 +256,9 @@ public abstract class AbstractPLSQLRule extends AbstractRule implements PLSQLPar protected void visitAll(List nodes, RuleContext ctx) { LOGGER.entering(CLASS_NAME, "visitAll"); for (Object element : nodes) { - ASTInput node = (ASTInput) element; - visit(node, ctx); + if (element instanceof PLSQLNode) { + ((PLSQLNode) element).jjtAccept(this, ctx); + } } LOGGER.exiting(CLASS_NAME, "visitAll"); } diff --git a/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/rule/ScalaRule.java b/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/rule/ScalaRule.java index 438c84b1f1..c13c0c1a9a 100644 --- a/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/rule/ScalaRule.java +++ b/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/rule/ScalaRule.java @@ -178,8 +178,8 @@ public class ScalaRule extends AbstractRule implements ScalaParserVisitor nodes, RuleContext ctx) { for (Node node : nodes) { - if (node instanceof ASTSource) { - visit((ASTSource) node, ctx); + if (node instanceof ScalaNode) { + ((ScalaNode) node).accept(this, ctx); } } } diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/AbstractVfRule.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/AbstractVfRule.java index 2329824e18..7eb1eb01c6 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/AbstractVfRule.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/AbstractVfRule.java @@ -46,12 +46,8 @@ public abstract class AbstractVfRule extends AbstractRule implements VfParserVis protected void visitAll(List nodes, RuleContext ctx) { for (Object element : nodes) { - if (element instanceof ASTCompilationUnit) { - ASTCompilationUnit node = (ASTCompilationUnit) element; - visit(node, ctx); - } else { - VfNode node = (VfNode) element; - visit(node, ctx); + if (element instanceof VfNode) { + ((VfNode) element).jjtAccept(this, ctx); } } } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/AbstractVmRule.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/AbstractVmRule.java index 2a50b7d114..24390a68b3 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/AbstractVmRule.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/AbstractVmRule.java @@ -75,8 +75,9 @@ public abstract class AbstractVmRule extends AbstractRule implements VmParserVis protected void visitAll(final List nodes, final RuleContext ctx) { for (final Object element : nodes) { - final ASTprocess node = (ASTprocess) element; - visit(node, ctx); + if (element instanceof VmNode) { + ((VmNode) element).jjtAccept(this, ctx); + } } } diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/AbstractXmlRule.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/AbstractXmlRule.java index de7d31b8af..c144118c6d 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/AbstractXmlRule.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/AbstractXmlRule.java @@ -65,8 +65,9 @@ public class AbstractXmlRule extends AbstractRule implements ImmutableLanguage { protected void visitAll(List nodes, RuleContext ctx) { for (Object element : nodes) { - XmlNode node = (XmlNode) element; - visit(node, ctx); + if (element instanceof XmlNode) { + visit((XmlNode) element, ctx); + } } } From 659066ee02498891b0d8bc1aa13260393c709257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 22 Jun 2020 18:58:30 +0200 Subject: [PATCH 54/99] Deprecate BaseLanguageModule constructor --- .../pmd/lang/apex/ApexLanguageModule.java | 3 +- .../pmd/lang/BaseLanguageModule.java | 28 +++++++++++++++++++ .../sourceforge/pmd/util/CollectionUtil.java | 10 +++++++ .../pmd/lang/java/JavaLanguageModule.java | 3 +- .../ecmascript/EcmascriptLanguageModule.java | 3 +- .../pmd/lang/jsp/JspLanguageModule.java | 3 +- .../pmd/lang/plsql/PLSQLLanguageModule.java | 3 +- .../pmd/lang/scala/ScalaLanguageModule.java | 3 +- .../pmd/test/lang/DummyLanguageModule.java | 2 +- .../pmd/lang/vf/VfLanguageModule.java | 3 +- .../pmd/lang/vm/VmLanguageModule.java | 3 +- .../pmd/lang/pom/PomLanguageModule.java | 3 +- .../pmd/lang/wsdl/WsdlLanguageModule.java | 3 +- .../pmd/lang/xml/XmlLanguageModule.java | 3 +- .../pmd/lang/xsl/XslLanguageModule.java | 3 +- 15 files changed, 51 insertions(+), 25 deletions(-) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexLanguageModule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexLanguageModule.java index 2ec8899f8a..949800ceab 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexLanguageModule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexLanguageModule.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.apex; import net.sourceforge.pmd.lang.BaseLanguageModule; -import net.sourceforge.pmd.lang.apex.rule.ApexRuleChainVisitor; import apex.jorje.services.Version; @@ -16,7 +15,7 @@ public class ApexLanguageModule extends BaseLanguageModule { public static final String[] EXTENSIONS = { "cls", "trigger" }; public ApexLanguageModule() { - super(NAME, null, TERSE_NAME, ApexRuleChainVisitor.class, EXTENSIONS); + super(NAME, null, TERSE_NAME, "cls", "trigger"); addVersion(String.valueOf((int) Version.CURRENT.getExternal()), new ApexHandler(), true); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java index e5e1e53619..d8e6c13697 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java @@ -15,6 +15,7 @@ import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.util.CollectionUtil; /** * Created by christoferdutz on 21.09.14. @@ -30,6 +31,12 @@ public abstract class BaseLanguageModule implements Language { protected Map versions; protected LanguageVersion defaultVersion; + /** + * @deprecated Use the other constructor. It doesn't require a + * rulechain visitor class, but forces you to mention at least + * one file extension. + */ + @Deprecated public BaseLanguageModule(String name, String shortName, String terseName, Class ruleChainVisitorClass, String... extensions) { this.name = name; @@ -39,6 +46,18 @@ public abstract class BaseLanguageModule implements Language { this.extensions = Arrays.asList(extensions); } + public BaseLanguageModule(String name, + String shortName, + String terseName, + String firstExtension, + String... otherExtensions) { + this.name = name; + this.shortName = shortName; + this.terseName = terseName; + this.ruleChainVisitorClass = DefaultRulechainVisitor.class; + this.extensions = CollectionUtil.listOf(firstExtension, otherExtensions); + } + private void addVersion(String version, LanguageVersionHandler languageVersionHandler, boolean isDefault, String... versionAliases) { if (versions == null) { versions = new HashMap<>(); @@ -194,7 +213,16 @@ public abstract class BaseLanguageModule implements Language { @Override protected void indexNodes(List nodes, RuleContext ctx) { + for (Node node : nodes) { + indexNodeRec(node); + } + } + protected void indexNodeRec(Node top) { + indexNode(top); + for (Node child : top.children()) { + indexNodeRec(child); + } } } 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 9c2497c690..8e6c008677 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 @@ -8,6 +8,7 @@ import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -187,6 +188,15 @@ public final class CollectionUtil { return list; } + @SafeVarargs + public static List listOf(T first, T... rest) { + // note: 7.0.x already has a better version of that + ArrayList result = new ArrayList<>(rest.length + 1); + result.add(first); + Collections.addAll(result, rest); + return result; + } + /** * Returns true if the objects are array instances and each of their diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java index 9a9bbf2d82..be1f603a5c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.java; import net.sourceforge.pmd.lang.BaseLanguageModule; -import net.sourceforge.pmd.lang.java.rule.JavaRuleChainVisitor; /** * Created by christoferdutz on 20.09.14. @@ -16,7 +15,7 @@ public class JavaLanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "java"; public JavaLanguageModule() { - super(NAME, null, TERSE_NAME, JavaRuleChainVisitor.class, "java"); + super(NAME, null, TERSE_NAME, "java"); addVersion("1.3", new JavaLanguageHandler(3)); addVersion("1.4", new JavaLanguageHandler(4)); addVersion("1.5", new JavaLanguageHandler(5), "5"); diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/EcmascriptLanguageModule.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/EcmascriptLanguageModule.java index 219ed2577b..7614fa35fd 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/EcmascriptLanguageModule.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/EcmascriptLanguageModule.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.ecmascript; import net.sourceforge.pmd.lang.BaseLanguageModule; -import net.sourceforge.pmd.lang.ecmascript.rule.EcmascriptRuleChainVisitor; /** * Created by christoferdutz on 20.09.14. @@ -16,7 +15,7 @@ public class EcmascriptLanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "ecmascript"; public EcmascriptLanguageModule() { - super(NAME, null, TERSE_NAME, EcmascriptRuleChainVisitor.class, "js"); + super(NAME, null, TERSE_NAME,"js"); addVersion("3", new Ecmascript3Handler(), true); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspLanguageModule.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspLanguageModule.java index 2ad43f34be..6128f034bb 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspLanguageModule.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspLanguageModule.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.jsp; import net.sourceforge.pmd.lang.BaseLanguageModule; -import net.sourceforge.pmd.lang.jsp.rule.JspRuleChainVisitor; /** * Created by christoferdutz on 20.09.14. @@ -16,7 +15,7 @@ public class JspLanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "jsp"; public JspLanguageModule() { - super(NAME, "JSP", TERSE_NAME, JspRuleChainVisitor.class, "jsp", "jspx", "jspf", "tag"); + super(NAME, "JSP", TERSE_NAME, "jsp", "jspx", "jspf", "tag"); addVersion("", new JspHandler(), true); } } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLLanguageModule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLLanguageModule.java index 2f165815af..d754799cdb 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLLanguageModule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLLanguageModule.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.plsql; import net.sourceforge.pmd.lang.BaseLanguageModule; -import net.sourceforge.pmd.lang.plsql.rule.PLSQLRuleChainVisitor; /** * Created by christoferdutz on 20.09.14. @@ -16,7 +15,7 @@ public class PLSQLLanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "plsql"; public PLSQLLanguageModule() { - super(NAME, null, TERSE_NAME, PLSQLRuleChainVisitor.class, + super(NAME, null, TERSE_NAME, "sql", "trg", // Triggers "prc", "fnc", // Standalone Procedures and Functions diff --git a/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/ScalaLanguageModule.java b/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/ScalaLanguageModule.java index 0e845ded66..a121da801d 100644 --- a/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/ScalaLanguageModule.java +++ b/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/ScalaLanguageModule.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.scala; import net.sourceforge.pmd.lang.BaseLanguageModule; -import net.sourceforge.pmd.lang.scala.rule.ScalaRuleChainVisitor; /** * Language Module for Scala. @@ -22,7 +21,7 @@ public class ScalaLanguageModule extends BaseLanguageModule { * Create a new instance of Scala Language Module. */ public ScalaLanguageModule() { - super(NAME, null, TERSE_NAME, ScalaRuleChainVisitor.class, "scala"); + super(NAME, null, TERSE_NAME, "scala"); addVersion("2.13", new ScalaLanguageHandler(scala.meta.dialects.package$.MODULE$.Scala213()), true); addVersion("2.12", new ScalaLanguageHandler(scala.meta.dialects.package$.MODULE$.Scala212()), false); addVersion("2.11", new ScalaLanguageHandler(scala.meta.dialects.package$.MODULE$.Scala211()), false); diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java index e0c354bde7..24f35b802e 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java @@ -36,7 +36,7 @@ public class DummyLanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "dummy"; public DummyLanguageModule() { - super(NAME, null, TERSE_NAME, DummyRuleChainVisitor.class, "dummy"); + super(NAME, null, TERSE_NAME, "dummy"); addVersion("1.0", new Handler(), false); addVersion("1.1", new Handler(), false); addVersion("1.2", new Handler(), false); diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfLanguageModule.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfLanguageModule.java index 6e782b7308..a367428f5f 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfLanguageModule.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfLanguageModule.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.vf; import net.sourceforge.pmd.lang.BaseLanguageModule; -import net.sourceforge.pmd.lang.vf.rule.VfRuleChainVisitor; /** @@ -18,7 +17,7 @@ public class VfLanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "vf"; public VfLanguageModule() { - super(NAME, "VisualForce", TERSE_NAME, VfRuleChainVisitor.class, "page", "component"); + super(NAME, "VisualForce", TERSE_NAME, "page", "component"); addVersion("", new VfHandler(), true); } } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmLanguageModule.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmLanguageModule.java index bfcf727c70..a8c29f86ec 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmLanguageModule.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmLanguageModule.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.vm; import net.sourceforge.pmd.lang.BaseLanguageModule; -import net.sourceforge.pmd.lang.vm.rule.VmRuleChainVisitor; /** * Created by christoferdutz on 20.09.14. @@ -16,7 +15,7 @@ public class VmLanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "vm"; public VmLanguageModule() { - super(NAME, null, TERSE_NAME, VmRuleChainVisitor.class, "vm"); + super(NAME, null, TERSE_NAME, "vm"); addVersion("", new VmHandler(), true); } diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/pom/PomLanguageModule.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/pom/PomLanguageModule.java index 24f6c86249..b8495734c3 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/pom/PomLanguageModule.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/pom/PomLanguageModule.java @@ -6,14 +6,13 @@ package net.sourceforge.pmd.lang.pom; import net.sourceforge.pmd.lang.BaseLanguageModule; import net.sourceforge.pmd.lang.xml.XmlHandler; -import net.sourceforge.pmd.lang.xml.rule.XmlRuleChainVisitor; public class PomLanguageModule extends BaseLanguageModule { public static final String NAME = "Maven POM"; public static final String TERSE_NAME = "pom"; public PomLanguageModule() { - super(NAME, null, TERSE_NAME, XmlRuleChainVisitor.class, "pom"); + super(NAME, null, TERSE_NAME, "pom"); addVersion("", new XmlHandler(), true); } } diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/wsdl/WsdlLanguageModule.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/wsdl/WsdlLanguageModule.java index 555a713353..4430b65986 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/wsdl/WsdlLanguageModule.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/wsdl/WsdlLanguageModule.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.wsdl; import net.sourceforge.pmd.lang.BaseLanguageModule; import net.sourceforge.pmd.lang.xml.XmlHandler; -import net.sourceforge.pmd.lang.xml.rule.XmlRuleChainVisitor; /** * Created by bernardo-macedo on 24.06.15. @@ -16,7 +15,7 @@ public class WsdlLanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "wsdl"; public WsdlLanguageModule() { - super(NAME, null, TERSE_NAME, XmlRuleChainVisitor.class, "wsdl"); + super(NAME, null, TERSE_NAME, "wsdl"); addVersion("", new XmlHandler(), true); } diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlLanguageModule.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlLanguageModule.java index d6db2e732c..1b6d7694c7 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlLanguageModule.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlLanguageModule.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.xml; import net.sourceforge.pmd.lang.BaseLanguageModule; -import net.sourceforge.pmd.lang.xml.rule.XmlRuleChainVisitor; /** * Created by christoferdutz on 20.09.14. @@ -16,7 +15,7 @@ public class XmlLanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "xml"; public XmlLanguageModule() { - super(NAME, null, TERSE_NAME, XmlRuleChainVisitor.class, "xml"); + super(NAME, null, TERSE_NAME, "xml"); addVersion("", new XmlHandler(), true); } } diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xsl/XslLanguageModule.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xsl/XslLanguageModule.java index 272d3c274f..9a5b484658 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xsl/XslLanguageModule.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xsl/XslLanguageModule.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.xsl; import net.sourceforge.pmd.lang.BaseLanguageModule; import net.sourceforge.pmd.lang.xml.XmlHandler; -import net.sourceforge.pmd.lang.xml.rule.XmlRuleChainVisitor; /** * Created by christoferdutz on 20.09.14. @@ -17,7 +16,7 @@ public class XslLanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "xsl"; public XslLanguageModule() { - super(NAME, null, TERSE_NAME, XmlRuleChainVisitor.class, "xsl", "xslt"); + super(NAME, null, TERSE_NAME, "xsl", "xslt"); addVersion("", new XmlHandler(), true); } } From 49635c2a6609b69d014f83601374ff924b9929d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 29 Jun 2020 06:30:22 +0200 Subject: [PATCH 55/99] Deprecate Rulechain visitors --- .../pmd/lang/apex/rule/ApexRuleChainVisitor.java | 1 + .../src/main/java/net/sourceforge/pmd/lang/Language.java | 3 +++ .../sourceforge/pmd/lang/rule/AbstractRuleChainVisitor.java | 3 +++ .../net/sourceforge/pmd/lang/rule/RuleChainVisitor.java | 6 ++++++ .../pmd/lang/java/rule/JavaRuleChainVisitor.java | 1 + .../lang/ecmascript/rule/EcmascriptRuleChainVisitor.java | 1 + .../sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java | 1 + .../pmd/lang/modelica/rule/ModelicaRuleChainVisitor.java | 1 + .../pmd/lang/plsql/rule/PLSQLRuleChainVisitor.java | 1 + .../pmd/lang/scala/rule/ScalaRuleChainVisitor.java | 1 + .../net/sourceforge/pmd/test/lang/DummyLanguageModule.java | 1 + .../sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java | 1 + .../sourceforge/pmd/lang/vm/rule/VmRuleChainVisitor.java | 1 + .../sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java | 1 + 14 files changed, 23 insertions(+) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleChainVisitor.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleChainVisitor.java index f08298d699..062c3e8d3a 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleChainVisitor.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleChainVisitor.java @@ -16,6 +16,7 @@ import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +@Deprecated public class ApexRuleChainVisitor extends AbstractRuleChainVisitor { @Override diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/Language.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/Language.java index b638af8998..eb60d5aade 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/Language.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/Language.java @@ -77,7 +77,10 @@ public interface Language extends Comparable { * * @return The RuleChainVisitor class. * @see net.sourceforge.pmd.lang.rule.RuleChainVisitor + * + * @deprecated Will be removed in PMD 7, avoid using this */ + @Deprecated Class getRuleChainVisitorClass(); /** diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractRuleChainVisitor.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractRuleChainVisitor.java index 56bf684808..099b38728f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractRuleChainVisitor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractRuleChainVisitor.java @@ -28,7 +28,10 @@ import net.sourceforge.pmd.lang.ast.Node; * This is a base class for RuleChainVisitor implementations which extracts * interesting nodes from an AST, and lets each Rule visit the nodes it has * expressed interest in. + * + * @deprecated See {@link RuleChainVisitor} */ +@Deprecated public abstract class AbstractRuleChainVisitor implements RuleChainVisitor { private static final Logger LOG = Logger.getLogger(AbstractRuleChainVisitor.class.getName()); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleChainVisitor.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleChainVisitor.java index f2425dbbcc..9b1c41d9c1 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleChainVisitor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleChainVisitor.java @@ -9,12 +9,18 @@ import java.util.List; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RuleSet; +import net.sourceforge.pmd.lang.BaseLanguageModule; import net.sourceforge.pmd.lang.ast.Node; /** * The RuleChainVisitor understands how to visit an AST for a particular * Language. + * + * @deprecated This interface will be removed. It's only used in internal + * code. Language implementors no longer need to register a rulechain + * visitor implementation in the {@link BaseLanguageModule} constructor. */ +@Deprecated public interface RuleChainVisitor { /** * Add the given rule to the visitor. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleChainVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleChainVisitor.java index 386335289a..40fd701e69 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleChainVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleChainVisitor.java @@ -16,6 +16,7 @@ import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +@Deprecated public class JavaRuleChainVisitor extends AbstractRuleChainVisitor { @Override diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleChainVisitor.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleChainVisitor.java index d86e3f664a..2c30f8a51b 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleChainVisitor.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleChainVisitor.java @@ -16,6 +16,7 @@ import net.sourceforge.pmd.lang.ecmascript.ast.EcmascriptParserVisitor; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +@Deprecated public class EcmascriptRuleChainVisitor extends AbstractRuleChainVisitor { @Override diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java index a9f76d1c32..0d289da074 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java @@ -16,6 +16,7 @@ import net.sourceforge.pmd.lang.jsp.ast.JspParserVisitorAdapter; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +@Deprecated public class JspRuleChainVisitor extends AbstractRuleChainVisitor { @Override diff --git a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/ModelicaRuleChainVisitor.java b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/ModelicaRuleChainVisitor.java index f999a79fb7..24ee97f68d 100644 --- a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/ModelicaRuleChainVisitor.java +++ b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/ModelicaRuleChainVisitor.java @@ -16,6 +16,7 @@ import net.sourceforge.pmd.lang.modelica.ast.ModelicaParserVisitorAdapter; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +@Deprecated public class ModelicaRuleChainVisitor extends AbstractRuleChainVisitor { @Override protected void visit(Rule rule, Node node, RuleContext ctx) { diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/PLSQLRuleChainVisitor.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/PLSQLRuleChainVisitor.java index e12bdf5d4d..cb66c26269 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/PLSQLRuleChainVisitor.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/PLSQLRuleChainVisitor.java @@ -18,6 +18,7 @@ import net.sourceforge.pmd.lang.plsql.ast.PLSQLParserVisitorAdapter; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +@Deprecated public class PLSQLRuleChainVisitor extends AbstractRuleChainVisitor { private static final Logger LOGGER = Logger.getLogger(PLSQLRuleChainVisitor.class.getName()); private static final String CLASS_NAME = PLSQLRuleChainVisitor.class.getName(); diff --git a/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/rule/ScalaRuleChainVisitor.java b/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/rule/ScalaRuleChainVisitor.java index 02191b7077..317809ce79 100644 --- a/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/rule/ScalaRuleChainVisitor.java +++ b/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/rule/ScalaRuleChainVisitor.java @@ -19,6 +19,7 @@ import net.sourceforge.pmd.lang.scala.ast.ScalaParserVisitorAdapter; /** * A Rule Chain visitor for Scala. */ +@Deprecated public class ScalaRuleChainVisitor extends AbstractRuleChainVisitor { @SuppressWarnings("unchecked") diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java index 24f35b802e..a2bb003882 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java @@ -48,6 +48,7 @@ public class DummyLanguageModule extends BaseLanguageModule { addVersion("1.8", new Handler(), false); } + @Deprecated public static class DummyRuleChainVisitor extends AbstractRuleChainVisitor { @Override protected void visit(Rule rule, Node node, RuleContext ctx) { diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java index 3f47672901..71514756ac 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java @@ -16,6 +16,7 @@ import net.sourceforge.pmd.lang.vf.ast.VfNode; import net.sourceforge.pmd.lang.vf.ast.VfParserVisitor; import net.sourceforge.pmd.lang.vf.ast.VfParserVisitorAdapter; +@Deprecated public class VfRuleChainVisitor extends AbstractRuleChainVisitor { @Override diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/VmRuleChainVisitor.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/VmRuleChainVisitor.java index 8a7853bf8d..8f5070cbf1 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/VmRuleChainVisitor.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/VmRuleChainVisitor.java @@ -17,6 +17,7 @@ import net.sourceforge.pmd.lang.vm.ast.VmNode; import net.sourceforge.pmd.lang.vm.ast.VmParserVisitor; import net.sourceforge.pmd.lang.vm.ast.VmParserVisitorAdapter; +@Deprecated public class VmRuleChainVisitor extends AbstractRuleChainVisitor { @Override diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java index 17df0c0026..a1f97cc043 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java @@ -14,6 +14,7 @@ import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +@Deprecated public class XmlRuleChainVisitor extends AbstractRuleChainVisitor { @Override From 835b9c7e5b18ecce62c5e2997b78527f0def5a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Wed, 1 Jul 2020 11:45:11 +0200 Subject: [PATCH 56/99] Update release notes Adjust the jdoc liquid tag to handle varargs and constructors --- docs/_plugins/javadoc_tag.rb | 7 +++++-- docs/pages/release_notes.md | 10 ++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/_plugins/javadoc_tag.rb b/docs/_plugins/javadoc_tag.rb index 0182729dc5..56b6e78561 100644 --- a/docs/_plugins/javadoc_tag.rb +++ b/docs/_plugins/javadoc_tag.rb @@ -36,6 +36,7 @@ require_relative 'jdoc_namespace_tag' # * The (erased) types of method arguments must be fully qualified. This is the same # convention as in javadoc {@link} tags, so you can use you're IDE's javadoc auto- # complete and copy-paste. Namespaces also can be used for method arguments if they're from PMD. +# * Use the name to reference a constructor # # # * Defining custom namespaces @@ -90,13 +91,15 @@ require_relative 'jdoc_namespace_tag' # - Include spaces in any part of the reference # - Use double or single quotes around the arguments # - Use the "#" suffix to reference a nested type, instead, use a dot "." and reference it like a normal type name +# - Use `[]` instead of `...` for vararg parameters +# - Use the type name instead of `` for a constructor # # class JavadocTag < Liquid::Tag QNAME_NO_NAMESPACE_REGEX = /((?:\w+\.)*\w+)/ - ARG_REGEX = Regexp.new(Regexp.union(JDocNamespaceDeclaration::NAMESPACED_FQCN_REGEX, QNAME_NO_NAMESPACE_REGEX).source + '(\[\])*') + ARG_REGEX = Regexp.new(Regexp.union(JDocNamespaceDeclaration::NAMESPACED_FQCN_REGEX, QNAME_NO_NAMESPACE_REGEX).source + '(\[\])*(...)?') ARGUMENTS_REGEX = Regexp.new('\(\)|\((' + ARG_REGEX.source + "(?:,(?:" + ARG_REGEX.source + "))*" + ')\)') @@ -174,7 +177,7 @@ class JavadocTag < Liquid::Tag def self.get_visible_name(opts, type_fqcn, member_suffix, is_package_ref, resolved_type) # method or field - if member_suffix && Regexp.new('(\w+)(' + ARGUMENTS_REGEX.source + ")?") =~ member_suffix + if member_suffix && Regexp.new('(\w+|)(' + ARGUMENTS_REGEX.source + ")?") =~ member_suffix suffix = $1 # method or field name diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index b8f8783555..4cc19b0952 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -18,6 +18,16 @@ This is a {{ site.pmd.release_type }} release. ### API Changes +#### Deprecated API + +##### For removal + +* {% jdoc core::lang.rule.RuleChainVisitor %} and all implementations in language modules +* {% jdoc core::lang.rule.AbstractRuleChainVisitor %} +* {% jdoc core::lang.Language#getRuleChainVisitorClass() %} +* {% jdoc core::lang.BaseLanguageModule#(java.lang.String,java.lang.String,java.lang.String,java.lang.Class,java.lang.String...) %} + + ### External Contributions {% endtocmaker %} From 43dd6567e2c14006570e90f0e8544818740c2926 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 2 Jul 2020 10:13:08 +0200 Subject: [PATCH 57/99] [java] Update test dependency log4j This fixes https://github.com/advisories/GHSA-vwqq-5vrc-xw9h --- pmd-java/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pmd-java/pom.xml b/pmd-java/pom.xml index 5ff10aa5fd..fe443b6268 100644 --- a/pmd-java/pom.xml +++ b/pmd-java/pom.xml @@ -158,13 +158,13 @@ org.apache.logging.log4j log4j-api - 2.12.1 + 2.13.3 test org.apache.logging.log4j log4j-core - 2.12.1 + 2.13.3 test From c7ccb8a087309cac5fa4ba7d60ef26fa3b1554d3 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 2 Jul 2020 10:27:26 +0200 Subject: [PATCH 58/99] javacc is only needed during parser generation --- pmd-core/pom.xml | 11 +++++++---- pmd-java/pom.xml | 2 ++ pmd-jsp/pom.xml | 2 ++ pmd-plsql/pom.xml | 2 ++ pmd-visualforce/pom.xml | 2 ++ pmd-vm/pom.xml | 2 ++ pom.xml | 1 + 7 files changed, 18 insertions(+), 4 deletions(-) diff --git a/pmd-core/pom.xml b/pmd-core/pom.xml index 0089efa13a..9590252ed0 100644 --- a/pmd-core/pom.xml +++ b/pmd-core/pom.xml @@ -97,6 +97,12 @@ ant provided + + net.java.dev.javacc + javacc + provided + + org.antlr antlr4-runtime @@ -122,10 +128,7 @@ --> true - - net.java.dev.javacc - javacc - + net.sourceforge.saxon saxon diff --git a/pmd-java/pom.xml b/pmd-java/pom.xml index fe443b6268..7c96252e5b 100644 --- a/pmd-java/pom.xml +++ b/pmd-java/pom.xml @@ -110,7 +110,9 @@ net.java.dev.javacc javacc + provided + net.sourceforge.pmd pmd-core diff --git a/pmd-jsp/pom.xml b/pmd-jsp/pom.xml index a01fdccf22..72fd206133 100644 --- a/pmd-jsp/pom.xml +++ b/pmd-jsp/pom.xml @@ -75,7 +75,9 @@ net.java.dev.javacc javacc + provided + net.sourceforge.pmd pmd-core diff --git a/pmd-plsql/pom.xml b/pmd-plsql/pom.xml index 3f44ed5a1d..4c1883ce71 100644 --- a/pmd-plsql/pom.xml +++ b/pmd-plsql/pom.xml @@ -83,7 +83,9 @@ net.java.dev.javacc javacc + provided + net.sourceforge.pmd pmd-core diff --git a/pmd-visualforce/pom.xml b/pmd-visualforce/pom.xml index 7c3b8b7d23..7888583f33 100644 --- a/pmd-visualforce/pom.xml +++ b/pmd-visualforce/pom.xml @@ -75,7 +75,9 @@ net.java.dev.javacc javacc + provided + net.sourceforge.pmd pmd-core diff --git a/pmd-vm/pom.xml b/pmd-vm/pom.xml index 0e92c868d2..ed046aaf92 100644 --- a/pmd-vm/pom.xml +++ b/pmd-vm/pom.xml @@ -83,7 +83,9 @@ net.java.dev.javacc javacc + provided + net.sourceforge.pmd pmd-core diff --git a/pom.xml b/pom.xml index 277c3f1a07..794df57bdc 100644 --- a/pom.xml +++ b/pom.xml @@ -658,6 +658,7 @@ net.java.dev.javacc javacc ${javacc.version} + provided commons-io From 80d9e1801155ac7ca1c35431c5283acb6b9f96b1 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 2 Jul 2020 14:16:39 +0200 Subject: [PATCH 59/99] pmd-lang-test: clarify dependencies --- pmd-lang-test/pom.xml | 36 +++++++++++++++++++++++++++++++++++- pom.xml | 13 +++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/pmd-lang-test/pom.xml b/pmd-lang-test/pom.xml index 26669a0869..ffc92adffc 100644 --- a/pmd-lang-test/pom.xml +++ b/pmd-lang-test/pom.xml @@ -89,11 +89,39 @@ pmd-core + + org.apache.commons + commons-lang3 + + + commons-io + commons-io + + + + io.kotlintest + kotlintest-assertions + compile + + + org.jetbrains.kotlin + kotlin-test + compile + + + + org.jetbrains + annotations + 13.0 + compile + + org.hamcrest hamcrest @@ -141,12 +169,18 @@ 2.1.0 compile + + com.github.oowekyala.treeutils + tree-printers + 2.1.0 + compile + net.sourceforge.pmd pmd-java - 6.12.0 + 6.25.0 test diff --git a/pom.xml b/pom.xml index 794df57bdc..a9c6d1a661 100644 --- a/pom.xml +++ b/pom.xml @@ -812,12 +812,25 @@ test + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + io.kotlintest kotlintest-runner-junit5 3.1.8 test + + io.kotlintest + kotlintest-assertions + 3.1.8 + test + From 370c0b0b6d30adea1d0d49e156f0828f640cee2c Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 2 Jul 2020 15:12:31 +0200 Subject: [PATCH 60/99] Remove unused dependency, explicitly declare used dependencies --- pmd-apex-jorje/pom.xml | 1 - pmd-apex/pom.xml | 240 +++++++++--------- .../pmd/lang/apex/metrics/ApexMetrics.java | 3 - pmd-cpp/pom.xml | 4 - pmd-cs/pom.xml | 4 +- pmd-dart/pom.xml | 8 +- pmd-dist/pom.xml | 5 - pmd-doc/pom.xml | 5 - pmd-fortran/pom.xml | 4 - pmd-go/pom.xml | 8 +- pmd-groovy/pom.xml | 13 +- pmd-java/pom.xml | 95 ++++--- pmd-java8/pom.xml | 5 - pmd-javascript/pom.xml | 4 - pmd-jsp/pom.xml | 14 - pmd-kotlin/pom.xml | 8 +- pmd-lang-test/pom.xml | 11 - pmd-lua/pom.xml | 8 +- pmd-matlab/pom.xml | 4 - pmd-modelica/pom.xml | 47 ++++ pmd-objectivec/pom.xml | 4 - pmd-perl/pom.xml | 15 ++ pmd-php/pom.xml | 10 + pmd-plsql/pom.xml | 10 - pmd-python/pom.xml | 4 - pmd-ruby/pom.xml | 4 - pmd-scala-modules/pmd-scala-common/pom.xml | 49 ++++ pmd-scala-modules/pmd-scala_2.12/pom.xml | 12 +- pmd-scala-modules/pmd-scala_2.13/pom.xml | 12 +- pmd-swift/pom.xml | 8 +- pmd-visualforce/pom.xml | 14 - pmd-vm/pom.xml | 10 - pmd-xml/pom.xml | 15 +- pom.xml | 45 +++- 34 files changed, 390 insertions(+), 313 deletions(-) diff --git a/pmd-apex-jorje/pom.xml b/pmd-apex-jorje/pom.xml index de533a5c87..f5b32e0bf3 100644 --- a/pmd-apex-jorje/pom.xml +++ b/pmd-apex-jorje/pom.xml @@ -89,7 +89,6 @@ org.antlr antlr-runtime - 3.5.2 org.antlr diff --git a/pmd-apex/pom.xml b/pmd-apex/pom.xml index 08b66db721..67ac49220f 100644 --- a/pmd-apex/pom.xml +++ b/pmd-apex/pom.xml @@ -1,125 +1,129 @@ - - 4.0.0 - pmd-apex - PMD Apex + + 4.0.0 + pmd-apex + PMD Apex - - net.sourceforge.pmd - pmd - 6.26.0-SNAPSHOT - ../ - + + net.sourceforge.pmd + pmd + 6.26.0-SNAPSHOT + ../ + - - 8 - + + 8 + - - - - ${basedir}/src/main/resources - true - - - - - maven-resources-plugin - - false - - ${*} - - - - - - - - net.sourceforge.pmd - pmd-core - - - - ${project.groupId} - pmd-apex-jorje - ${project.version} - lib - - - - ${project.groupId} - pmd-apex-jorje - ${project.version} - pom - - - - commons-io - commons-io - - - - - net.sourceforge.saxon - saxon - - - - org.hamcrest - hamcrest - test - - - junit - junit - test - - - com.github.stefanbirkner - system-rules - test - - - org.junit.vintage - junit-vintage-engine - test - - - net.sourceforge.pmd - pmd-test - test - - - net.sourceforge.pmd - pmd-lang-test - test - - - - - - designer - + + + + ${basedir}/src/main/resources + true + + - - org.codehaus.mojo - exec-maven-plugin - 1.4.0 - - net.sourceforge.pmd.util.designer.Designer - true - - - - net.sourceforge.pmd - pmd-java - ${project.version} - - - + + maven-resources-plugin + + false + + ${*} + + + - - - + + + + net.sourceforge.pmd + pmd-core + + + org.antlr + antlr-runtime + + + + ${project.groupId} + pmd-apex-jorje + ${project.version} + lib + + + + ${project.groupId} + pmd-apex-jorje + ${project.version} + pom + + + + commons-io + commons-io + + + org.apache.commons + commons-lang3 + + + + + org.hamcrest + hamcrest + test + + + junit + junit + test + + + com.github.stefanbirkner + system-rules + test + + + org.junit.vintage + junit-vintage-engine + test + + + net.sourceforge.pmd + pmd-test + test + + + net.sourceforge.pmd + pmd-lang-test + test + + + + + + designer + + + + org.codehaus.mojo + exec-maven-plugin + 1.4.0 + + net.sourceforge.pmd.util.designer.Designer + true + + + + net.sourceforge.pmd + pmd-java + ${project.version} + + + + + + + diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java index d8dd2ec984..9c22ae27e5 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java @@ -7,8 +7,6 @@ package net.sourceforge.pmd.lang.apex.metrics; import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.NonNull; - import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; @@ -136,7 +134,6 @@ public final class ApexMetrics { } - @NonNull public static List findOps(ASTUserClassOrInterface node) { List candidates = node.findChildrenOfType(ASTMethod.class); List result = new ArrayList<>(candidates); diff --git a/pmd-cpp/pom.xml b/pmd-cpp/pom.xml index 51d5111c0b..b2dd19ece7 100644 --- a/pmd-cpp/pom.xml +++ b/pmd-cpp/pom.xml @@ -70,10 +70,6 @@ net.sourceforge.pmd pmd-core - - commons-io - commons-io - junit diff --git a/pmd-cs/pom.xml b/pmd-cs/pom.xml index 7d6939ed6e..38a5a822be 100644 --- a/pmd-cs/pom.xml +++ b/pmd-cs/pom.xml @@ -36,8 +36,8 @@ pmd-core - commons-io - commons-io + org.antlr + antlr4-runtime diff --git a/pmd-dart/pom.xml b/pmd-dart/pom.xml index e2fbafc4b8..f489c42d7c 100644 --- a/pmd-dart/pom.xml +++ b/pmd-dart/pom.xml @@ -31,17 +31,13 @@ - - org.antlr - antlr4-runtime - net.sourceforge.pmd pmd-core - commons-io - commons-io + org.antlr + antlr4-runtime diff --git a/pmd-dist/pom.xml b/pmd-dist/pom.xml index 96fca07038..59c3076e03 100644 --- a/pmd-dist/pom.xml +++ b/pmd-dist/pom.xml @@ -229,11 +229,6 @@ commons-lang3 - - org.hamcrest - hamcrest - test - junit junit diff --git a/pmd-doc/pom.xml b/pmd-doc/pom.xml index 22f43c9c0b..a2811d07ce 100644 --- a/pmd-doc/pom.xml +++ b/pmd-doc/pom.xml @@ -103,11 +103,6 @@ 1.19 - - org.hamcrest - hamcrest - test - junit junit diff --git a/pmd-fortran/pom.xml b/pmd-fortran/pom.xml index 9e451b1c53..f5926fc80b 100644 --- a/pmd-fortran/pom.xml +++ b/pmd-fortran/pom.xml @@ -29,10 +29,6 @@ net.sourceforge.pmd pmd-core - - commons-io - commons-io - junit diff --git a/pmd-go/pom.xml b/pmd-go/pom.xml index 773a98e3b3..2c65c6a915 100644 --- a/pmd-go/pom.xml +++ b/pmd-go/pom.xml @@ -29,14 +29,14 @@ - - org.antlr - antlr4-runtime - net.sourceforge.pmd pmd-core + + org.antlr + antlr4-runtime + junit diff --git a/pmd-groovy/pom.xml b/pmd-groovy/pom.xml index 655f884a74..e3afe3ef74 100644 --- a/pmd-groovy/pom.xml +++ b/pmd-groovy/pom.xml @@ -26,19 +26,14 @@ - - org.codehaus.groovy - groovy - - - commons-io - commons-io - - net.sourceforge.pmd pmd-core + + org.codehaus.groovy + groovy + junit diff --git a/pmd-java/pom.xml b/pmd-java/pom.xml index 7c96252e5b..41a7476b9a 100644 --- a/pmd-java/pom.xml +++ b/pmd-java/pom.xml @@ -107,12 +107,6 @@ - - net.java.dev.javacc - javacc - provided - - net.sourceforge.pmd pmd-core @@ -134,13 +128,6 @@ commons-lang3 - - net.sourceforge.saxon - saxon - dom - runtime - - net.sourceforge.pmd @@ -152,23 +139,6 @@ pmd-test test - - org.slf4j - slf4j-api - test - - - org.apache.logging.log4j - log4j-api - 2.13.3 - test - - - org.apache.logging.log4j - log4j-core - 2.13.3 - test - org.hamcrest hamcrest @@ -195,6 +165,71 @@ ant-testutil test + + + com.github.oowekyala.treeutils + tree-matchers + test + + + com.github.oowekyala.treeutils + tree-printers + test + + + io.kotlintest + kotlintest-runner-junit5 + test + + + io.kotlintest + kotlintest-assertions + test + + + io.kotlintest + kotlintest-core + test + + + com.google.guava + guava + test + + + org.jetbrains.kotlin + kotlin-stdlib + test + + + org.jetbrains.kotlin + kotlin-test + test + + + org.jetbrains + annotations + test + + + + + org.slf4j + slf4j-api + test + + + org.apache.logging.log4j + log4j-api + 2.13.3 + test + + + org.apache.logging.log4j + log4j-core + 2.13.3 + test + org.assertj assertj-core diff --git a/pmd-java8/pom.xml b/pmd-java8/pom.xml index 6507278282..9e4bb0cf44 100644 --- a/pmd-java8/pom.xml +++ b/pmd-java8/pom.xml @@ -56,11 +56,6 @@ pmd-core - - org.hamcrest - hamcrest - test - junit junit diff --git a/pmd-javascript/pom.xml b/pmd-javascript/pom.xml index a97cff755b..706f3d09cc 100644 --- a/pmd-javascript/pom.xml +++ b/pmd-javascript/pom.xml @@ -83,10 +83,6 @@ commons-io commons-io - - net.sourceforge.saxon - saxon - junit diff --git a/pmd-jsp/pom.xml b/pmd-jsp/pom.xml index 72fd206133..554324da5d 100644 --- a/pmd-jsp/pom.xml +++ b/pmd-jsp/pom.xml @@ -72,24 +72,10 @@ - - net.java.dev.javacc - javacc - provided - - net.sourceforge.pmd pmd-core - - commons-io - commons-io - - - net.sourceforge.saxon - saxon - junit diff --git a/pmd-kotlin/pom.xml b/pmd-kotlin/pom.xml index bce62d34f6..09c881677c 100644 --- a/pmd-kotlin/pom.xml +++ b/pmd-kotlin/pom.xml @@ -31,17 +31,13 @@ - - org.antlr - antlr4-runtime - net.sourceforge.pmd pmd-core - commons-io - commons-io + org.antlr + antlr4-runtime diff --git a/pmd-lang-test/pom.xml b/pmd-lang-test/pom.xml index ffc92adffc..077910a59e 100644 --- a/pmd-lang-test/pom.xml +++ b/pmd-lang-test/pom.xml @@ -118,7 +118,6 @@ org.jetbrains annotations - 13.0 compile @@ -166,22 +165,12 @@ com.github.oowekyala.treeutils tree-matchers - 2.1.0 compile com.github.oowekyala.treeutils tree-printers - 2.1.0 compile - - - - net.sourceforge.pmd - pmd-java - 6.25.0 - test - diff --git a/pmd-lua/pom.xml b/pmd-lua/pom.xml index a519014e08..2bc6f0754f 100644 --- a/pmd-lua/pom.xml +++ b/pmd-lua/pom.xml @@ -31,17 +31,13 @@ - - org.antlr - antlr4-runtime - net.sourceforge.pmd pmd-core - commons-io - commons-io + org.antlr + antlr4-runtime diff --git a/pmd-matlab/pom.xml b/pmd-matlab/pom.xml index cd3585c4bb..0b71aad03e 100644 --- a/pmd-matlab/pom.xml +++ b/pmd-matlab/pom.xml @@ -70,10 +70,6 @@ net.sourceforge.pmd pmd-core - - commons-io - commons-io - junit diff --git a/pmd-modelica/pom.xml b/pmd-modelica/pom.xml index ddb1f877e6..9b02d62b80 100644 --- a/pmd-modelica/pom.xml +++ b/pmd-modelica/pom.xml @@ -78,6 +78,53 @@ pmd-core + + com.github.oowekyala.treeutils + tree-matchers + test + + + com.github.oowekyala.treeutils + tree-printers + test + + + org.jetbrains.kotlin + kotlin-stdlib + test + + + org.jetbrains.kotlin + kotlin-test + test + + + org.jetbrains + annotations + test + + + io.kotlintest + kotlintest-runner-junit5 + test + + + io.kotlintest + kotlintest-assertions + test + + + io.kotlintest + kotlintest-core + test + + + + + junit + junit + test + org.junit.vintage junit-vintage-engine diff --git a/pmd-objectivec/pom.xml b/pmd-objectivec/pom.xml index 2bbbb7cbf5..dafc12b184 100644 --- a/pmd-objectivec/pom.xml +++ b/pmd-objectivec/pom.xml @@ -70,10 +70,6 @@ net.sourceforge.pmd pmd-core - - commons-io - commons-io - junit diff --git a/pmd-perl/pom.xml b/pmd-perl/pom.xml index 2eb7fd9c34..55fb10ac2b 100644 --- a/pmd-perl/pom.xml +++ b/pmd-perl/pom.xml @@ -30,10 +30,25 @@ pmd-core + + junit + junit + test + + + org.junit.vintage + junit-vintage-engine + test + net.sourceforge.pmd pmd-test test + + net.sourceforge.pmd + pmd-lang-test + test + diff --git a/pmd-php/pom.xml b/pmd-php/pom.xml index a985180d05..3cf2f6bce6 100644 --- a/pmd-php/pom.xml +++ b/pmd-php/pom.xml @@ -35,10 +35,20 @@ junit test + + org.junit.vintage + junit-vintage-engine + test + net.sourceforge.pmd pmd-test test + + net.sourceforge.pmd + pmd-lang-test + test + diff --git a/pmd-plsql/pom.xml b/pmd-plsql/pom.xml index 4c1883ce71..74f54637bb 100644 --- a/pmd-plsql/pom.xml +++ b/pmd-plsql/pom.xml @@ -80,12 +80,6 @@ - - net.java.dev.javacc - javacc - provided - - net.sourceforge.pmd pmd-core @@ -94,10 +88,6 @@ commons-io commons-io - - net.sourceforge.saxon - saxon - junit diff --git a/pmd-python/pom.xml b/pmd-python/pom.xml index 1340131928..42e44fa67e 100644 --- a/pmd-python/pom.xml +++ b/pmd-python/pom.xml @@ -70,10 +70,6 @@ net.sourceforge.pmd pmd-core - - commons-io - commons-io - junit diff --git a/pmd-ruby/pom.xml b/pmd-ruby/pom.xml index fc6778050e..7c70becc38 100644 --- a/pmd-ruby/pom.xml +++ b/pmd-ruby/pom.xml @@ -16,10 +16,6 @@ net.sourceforge.pmd pmd-core - - commons-io - commons-io - junit diff --git a/pmd-scala-modules/pmd-scala-common/pom.xml b/pmd-scala-modules/pmd-scala-common/pom.xml index de9580fe17..1bfca64ac2 100644 --- a/pmd-scala-modules/pmd-scala-common/pom.xml +++ b/pmd-scala-modules/pmd-scala-common/pom.xml @@ -103,6 +103,55 @@ net.sourceforge.pmd pmd-core + + commons-io + commons-io + + + org.apache.commons + commons-lang3 + + + + com.github.oowekyala.treeutils + tree-matchers + test + + + com.github.oowekyala.treeutils + tree-printers + test + + + io.kotlintest + kotlintest-runner-junit5 + test + + + io.kotlintest + kotlintest-assertions + test + + + io.kotlintest + kotlintest-core + test + + + org.jetbrains.kotlin + kotlin-stdlib + test + + + org.jetbrains.kotlin + kotlin-test + test + + + org.jetbrains + annotations + test + junit diff --git a/pmd-scala-modules/pmd-scala_2.12/pom.xml b/pmd-scala-modules/pmd-scala_2.12/pom.xml index 1a912de417..ad244664fa 100644 --- a/pmd-scala-modules/pmd-scala_2.12/pom.xml +++ b/pmd-scala-modules/pmd-scala_2.12/pom.xml @@ -25,9 +25,19 @@ + + org.scala-lang + scala-library + ${scalaVersion} + org.scalameta - scalameta_${scalaVersion} + parsers_${scalaVersion} + ${scalameta.version} + + + org.scalameta + trees_${scalaVersion} ${scalameta.version} diff --git a/pmd-scala-modules/pmd-scala_2.13/pom.xml b/pmd-scala-modules/pmd-scala_2.13/pom.xml index 7a72d501e5..eea8d7fad3 100644 --- a/pmd-scala-modules/pmd-scala_2.13/pom.xml +++ b/pmd-scala-modules/pmd-scala_2.13/pom.xml @@ -25,9 +25,19 @@ + + org.scala-lang + scala-library + ${scalaVersion} + org.scalameta - scalameta_${scalaVersion} + parsers_${scalaVersion} + ${scalameta.version} + + + org.scalameta + trees_${scalaVersion} ${scalameta.version} diff --git a/pmd-swift/pom.xml b/pmd-swift/pom.xml index 7744be7696..20c1afe4b7 100644 --- a/pmd-swift/pom.xml +++ b/pmd-swift/pom.xml @@ -31,17 +31,13 @@ - - org.antlr - antlr4-runtime - net.sourceforge.pmd pmd-core - commons-io - commons-io + org.antlr + antlr4-runtime diff --git a/pmd-visualforce/pom.xml b/pmd-visualforce/pom.xml index 7888583f33..b9687e9df8 100644 --- a/pmd-visualforce/pom.xml +++ b/pmd-visualforce/pom.xml @@ -72,24 +72,10 @@ - - net.java.dev.javacc - javacc - provided - - net.sourceforge.pmd pmd-core - - commons-io - commons-io - - - net.sourceforge.saxon - saxon - junit diff --git a/pmd-vm/pom.xml b/pmd-vm/pom.xml index ed046aaf92..ded0e269b3 100644 --- a/pmd-vm/pom.xml +++ b/pmd-vm/pom.xml @@ -80,12 +80,6 @@ - - net.java.dev.javacc - javacc - provided - - net.sourceforge.pmd pmd-core @@ -94,10 +88,6 @@ org.apache.commons commons-lang3 - - net.sourceforge.saxon - saxon - junit diff --git a/pmd-xml/pom.xml b/pmd-xml/pom.xml index 4876d5561b..bbdf4568fc 100644 --- a/pmd-xml/pom.xml +++ b/pmd-xml/pom.xml @@ -35,30 +35,19 @@ - - org.antlr - antlr4-runtime - net.sourceforge.pmd pmd-core - net.sourceforge.saxon - saxon + org.antlr + antlr4-runtime commons-io commons-io - - net.sourceforge.saxon - saxon - dom - runtime - - junit junit diff --git a/pom.xml b/pom.xml index a9c6d1a661..d96b6fab6b 100644 --- a/pom.xml +++ b/pom.xml @@ -141,7 +141,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.1.2 + 3.1.3-SNAPSHOT org.apache.maven.plugins @@ -594,6 +594,12 @@ antlr4-runtime ${antlr.version} + + + org.antlr + antlr-runtime + 3.5.2 + org.apache.ant ant @@ -696,6 +702,26 @@ test + + com.github.oowekyala.treeutils + tree-matchers + 2.1.0 + test + + + com.github.oowekyala.treeutils + tree-printers + 2.1.0 + test + + + + com.google.guava + guava + 18.0 + test + + org.hamcrest hamcrest @@ -790,28 +816,24 @@ ${kotlin.version} test - org.jetbrains.kotlin kotlin-stdlib-jdk8 ${kotlin.version} test - org.jetbrains.kotlin kotlin-reflect ${kotlin.version} test - org.jetbrains.kotlin kotlin-test-junit ${kotlin.version} test - org.jetbrains.kotlin kotlin-test @@ -831,6 +853,19 @@ 3.1.8 test + + io.kotlintest + kotlintest-core + 3.1.8 + test + + + + org.jetbrains + annotations + 13.0 + test + From 373c9b09449a023e76b4e7640f7228b4c99942e2 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 2 Jul 2020 20:30:54 +0200 Subject: [PATCH 61/99] Fix scala library versions --- pmd-scala-modules/pmd-scala_2.12/pom.xml | 2 +- pmd-scala-modules/pmd-scala_2.13/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pmd-scala-modules/pmd-scala_2.12/pom.xml b/pmd-scala-modules/pmd-scala_2.12/pom.xml index ad244664fa..80aabc12b3 100644 --- a/pmd-scala-modules/pmd-scala_2.12/pom.xml +++ b/pmd-scala-modules/pmd-scala_2.12/pom.xml @@ -28,7 +28,7 @@ org.scala-lang scala-library - ${scalaVersion} + ${scalaVersion}.10 org.scalameta diff --git a/pmd-scala-modules/pmd-scala_2.13/pom.xml b/pmd-scala-modules/pmd-scala_2.13/pom.xml index eea8d7fad3..ffbd77819a 100644 --- a/pmd-scala-modules/pmd-scala_2.13/pom.xml +++ b/pmd-scala-modules/pmd-scala_2.13/pom.xml @@ -28,7 +28,7 @@ org.scala-lang scala-library - ${scalaVersion} + ${scalaVersion}.3 org.scalameta From 67f33e3427b61e94e07b4571b3652a6a41c30dad Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 2 Jul 2020 20:33:32 +0200 Subject: [PATCH 62/99] Add back hamcrest (it's actually test-runtime) --- pmd-dist/pom.xml | 5 +++++ pmd-doc/pom.xml | 5 +++++ pmd-java8/pom.xml | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/pmd-dist/pom.xml b/pmd-dist/pom.xml index 59c3076e03..ea56d1ccb6 100644 --- a/pmd-dist/pom.xml +++ b/pmd-dist/pom.xml @@ -234,6 +234,11 @@ junit test + + org.hamcrest + hamcrest + test + org.apache.commons commons-compress diff --git a/pmd-doc/pom.xml b/pmd-doc/pom.xml index a2811d07ce..52ad783139 100644 --- a/pmd-doc/pom.xml +++ b/pmd-doc/pom.xml @@ -108,5 +108,10 @@ junit test + + org.hamcrest + hamcrest + test + diff --git a/pmd-java8/pom.xml b/pmd-java8/pom.xml index 9e4bb0cf44..53aea03a99 100644 --- a/pmd-java8/pom.xml +++ b/pmd-java8/pom.xml @@ -61,5 +61,10 @@ junit test + + org.hamcrest + hamcrest + test + From c24efea939c202ca7b0acea8809edbaa40f0a9a6 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 2 Jul 2020 21:41:19 +0200 Subject: [PATCH 63/99] Revert to use stable maven-dependency-plugin --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d96b6fab6b..bc26acb457 100644 --- a/pom.xml +++ b/pom.xml @@ -141,7 +141,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.1.3-SNAPSHOT + 3.1.2 org.apache.maven.plugins From 03d03440b41d47b4e69ebdef3b1964dde1c8a5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 6 Jul 2020 13:25:54 +0200 Subject: [PATCH 64/99] Declare junit dependencies before kotlintest ones This is for the scala modules. Apparently in the other order, only kotlin tests are run. --- pmd-scala-modules/pmd-scala-common/pom.xml | 43 +++++++++++----------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/pmd-scala-modules/pmd-scala-common/pom.xml b/pmd-scala-modules/pmd-scala-common/pom.xml index 1bfca64ac2..751918cc7f 100644 --- a/pmd-scala-modules/pmd-scala-common/pom.xml +++ b/pmd-scala-modules/pmd-scala-common/pom.xml @@ -112,6 +112,28 @@ commons-lang3 + + junit + junit + test + + + org.junit.vintage + junit-vintage-engine + test + + + net.sourceforge.pmd + pmd-test + test + + + net.sourceforge.pmd + pmd-lang-test + test + + + com.github.oowekyala.treeutils tree-matchers @@ -152,26 +174,5 @@ annotations test - - - junit - junit - test - - - org.junit.vintage - junit-vintage-engine - test - - - net.sourceforge.pmd - pmd-test - test - - - net.sourceforge.pmd - pmd-lang-test - test - From 96998fffc2e21c41e0fd45bb2e8f2dcc1bd2094b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 6 Jul 2020 13:35:06 +0200 Subject: [PATCH 65/99] Same for modelica --- pmd-modelica/pom.xml | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/pmd-modelica/pom.xml b/pmd-modelica/pom.xml index 9b02d62b80..02fb539322 100644 --- a/pmd-modelica/pom.xml +++ b/pmd-modelica/pom.xml @@ -78,6 +78,27 @@ pmd-core + + junit + junit + test + + + org.junit.vintage + junit-vintage-engine + test + + + net.sourceforge.pmd + pmd-lang-test + test + + + net.sourceforge.pmd + pmd-test + test + + com.github.oowekyala.treeutils tree-matchers @@ -120,25 +141,5 @@ - - junit - junit - test - - - org.junit.vintage - junit-vintage-engine - test - - - net.sourceforge.pmd - pmd-lang-test - test - - - net.sourceforge.pmd - pmd-test - test - From eac96a433a64d06eb6b0f530aadbd0033cb1b66f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 6 Jul 2020 13:49:46 +0200 Subject: [PATCH 66/99] Fix remaining usages of old ctor --- .../java/net/sourceforge/pmd/lang/Dummy2LanguageModule.java | 2 +- .../java/net/sourceforge/pmd/lang/DummyLanguageModule.java | 2 +- .../sourceforge/pmd/lang/modelica/ModelicaLanguageModule.java | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/Dummy2LanguageModule.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/Dummy2LanguageModule.java index fc819c7df2..e5a8de77cb 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/Dummy2LanguageModule.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/Dummy2LanguageModule.java @@ -13,7 +13,7 @@ public class Dummy2LanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "dummy2"; public Dummy2LanguageModule() { - super(NAME, null, TERSE_NAME, null, "dummy2"); + super(NAME, null, TERSE_NAME, "dummy2"); addVersion("1.0", new DummyLanguageModule.Handler(), true); } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java index 5eb7046f08..559d26ec0d 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java @@ -37,7 +37,7 @@ public class DummyLanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "dummy"; public DummyLanguageModule() { - super(NAME, null, TERSE_NAME, DummyRuleChainVisitor.class, "dummy"); + super(NAME, null, TERSE_NAME, "dummy"); addVersion("1.0", new Handler()); addVersion("1.1", new Handler()); addVersion("1.2", new Handler()); diff --git a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ModelicaLanguageModule.java b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ModelicaLanguageModule.java index 2327f90c55..2c864b9ee9 100644 --- a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ModelicaLanguageModule.java +++ b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ModelicaLanguageModule.java @@ -5,14 +5,13 @@ package net.sourceforge.pmd.lang.modelica; import net.sourceforge.pmd.lang.BaseLanguageModule; -import net.sourceforge.pmd.lang.modelica.rule.ModelicaRuleChainVisitor; public class ModelicaLanguageModule extends BaseLanguageModule { public static final String NAME = "Modelica"; public static final String TERSE_NAME = "modelica"; public ModelicaLanguageModule() { - super(NAME, null, TERSE_NAME, ModelicaRuleChainVisitor.class, "mo"); + super(NAME, null, TERSE_NAME, "mo"); addVersion("", new ModelicaHandler(), true); } } From c25e10717b00c4d4264654ce94bce285160d1ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 6 Jul 2020 15:50:52 +0200 Subject: [PATCH 67/99] Fix construction of rulechain visitor Ctor needs to be public, because RuleChain builds them from a class --- .../pmd/lang/BaseLanguageModule.java | 28 +------------ .../internal/DefaultRulechainVisitor.java | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 27 deletions(-) create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/lang/internal/DefaultRulechainVisitor.java diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java index d8e6c13697..dada396a8e 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java @@ -11,10 +11,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import net.sourceforge.pmd.Rule; -import net.sourceforge.pmd.RuleContext; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.lang.internal.DefaultRulechainVisitor; import net.sourceforge.pmd.util.CollectionUtil; /** @@ -203,27 +200,4 @@ public abstract class BaseLanguageModule implements Language { } - - private static class DefaultRulechainVisitor extends AbstractRuleChainVisitor { - - @Override - protected void visit(Rule rule, Node node, RuleContext ctx) { - rule.apply(Collections.singletonList(node), ctx); - } - - @Override - protected void indexNodes(List nodes, RuleContext ctx) { - for (Node node : nodes) { - indexNodeRec(node); - } - } - - protected void indexNodeRec(Node top) { - indexNode(top); - for (Node child : top.children()) { - indexNodeRec(child); - } - } - } - } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/internal/DefaultRulechainVisitor.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/internal/DefaultRulechainVisitor.java new file mode 100644 index 0000000000..9ca3d94d46 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/internal/DefaultRulechainVisitor.java @@ -0,0 +1,40 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.internal; + +import java.util.Collections; +import java.util.List; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.lang.rule.RuleChainVisitor; + +/** + * @deprecated See {@link RuleChainVisitor} + */ +@Deprecated +public class DefaultRulechainVisitor extends AbstractRuleChainVisitor { + + @Override + protected void visit(Rule rule, Node node, RuleContext ctx) { + rule.apply(Collections.singletonList(node), ctx); + } + + @Override + protected void indexNodes(List nodes, RuleContext ctx) { + for (Node node : nodes) { + indexNodeRec(node); + } + } + + protected void indexNodeRec(Node top) { + indexNode(top); + for (Node child : top.children()) { + indexNodeRec(child); + } + } +} From 5e50b16e056b017d8e42c9cec896b14918c1c320 Mon Sep 17 00:00:00 2001 From: Peter Chittum Date: Tue, 30 Jun 2020 08:15:25 +0100 Subject: [PATCH 68/99] [visualforce] added new global variable name to safe resources Cherry-picked from 25cb8de645e0c016a99a933950bffdc996d74b39 --- .../sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java index 948f4264aa..4c041e5296 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java @@ -296,6 +296,7 @@ public class VfUnescapeElRule extends AbstractVfRule { case "$objecttype": case "$component": case "$remoteaction": + case "$messageservice": return true; default: From 2fb196b16a0179b1830db129c59011fffd755799 Mon Sep 17 00:00:00 2001 From: Peter Chittum Date: Tue, 30 Jun 2020 08:18:45 +0100 Subject: [PATCH 69/99] [visualforce] added new global variable name to safe resources Cherry-picked from 7f0f91f71aac132e3c5ddd9ce397c73f19ebb04e --- .../sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java index 4c041e5296..51817b896c 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java @@ -296,7 +296,7 @@ public class VfUnescapeElRule extends AbstractVfRule { case "$objecttype": case "$component": case "$remoteaction": - case "$messageservice": + case "$messagechannel": return true; default: From d89a6d080d8151330b4508d212043fd9dfcab0a1 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 9 Jul 2020 12:11:33 +0200 Subject: [PATCH 70/99] [visualforce] Add test for VfUnescapeEl with $MessageChannel --- docs/pages/release_notes.md | 4 ++++ .../pmd/lang/vf/rule/security/xml/VfUnescapeEl.xml | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index c1806b1dab..e66a576c54 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -18,10 +18,14 @@ This is a {{ site.pmd.release_type }} release. * apex-bestpractices * [#2626](https://github.com/pmd/pmd/issues/2626): \[apex] UnusedLocalVariable - false positive on case insensitivity allowed in Apex +* apex-security + * [#2620](https://github.com/pmd/pmd/issues/2620): \[visualforce] False positive on VfUnescapeEl with new Message Channel feature ### API Changes ### External Contributions +* [#2621](https://github.com/pmd/pmd/pull/2621): \[visualforce] add new safe resource for VfUnescapeEl - [Peter Chittum](https://github.com/pchittum) + {% endtocmaker %} diff --git a/pmd-visualforce/src/test/resources/net/sourceforge/pmd/lang/vf/rule/security/xml/VfUnescapeEl.xml b/pmd-visualforce/src/test/resources/net/sourceforge/pmd/lang/vf/rule/security/xml/VfUnescapeEl.xml index 7e57ba6eb9..aec8e5e7d8 100644 --- a/pmd-visualforce/src/test/resources/net/sourceforge/pmd/lang/vf/rule/security/xml/VfUnescapeEl.xml +++ b/pmd-visualforce/src/test/resources/net/sourceforge/pmd/lang/vf/rule/security/xml/VfUnescapeEl.xml @@ -537,4 +537,17 @@ ]]> + + + Support new message channel feature #2620 + 0 + + // Binding message channel to variable accessible to static resource. + window.util = { + messageChannel: '{!$MessageChannel.Record_Selected__c}' + }; + + ]]> + From 11a0ec51fdb557c59a86f002e05af7bfff66177a Mon Sep 17 00:00:00 2001 From: Young Chan Date: Sun, 31 May 2020 10:34:00 +0800 Subject: [PATCH 71/99] fix issue 1736 --- .../UseStringBufferForStringAppendsRule.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) 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 283a38d3b0..10599981f9 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 @@ -4,6 +4,7 @@ package net.sourceforge.pmd.lang.java.rule.performance; +import java.util.HashMap; import java.util.Objects; import net.sourceforge.pmd.lang.ast.Node; @@ -37,6 +38,9 @@ public class UseStringBufferForStringAppendsRule extends AbstractJavaRule { return data; } + // create a new hashmap to store occurrence of not-recommending string concatenation operations + HashMap map = new HashMap<>(); + for (NameOccurrence no : node.getUsages()) { Node name = no.getLocation(); ASTStatementExpression statement = name.getFirstParentOfType(ASTStatementExpression.class); @@ -78,13 +82,29 @@ public class UseStringBufferForStringAppendsRule extends AbstractJavaRule { ASTAssignmentOperator assignmentOperator = statement .getFirstDescendantOfType(ASTAssignmentOperator.class); if (assignmentOperator != null && assignmentOperator.isCompound()) { - addViolation(data, assignmentOperator); + // check whether is first time to break the rule + if (!map.containsKey(astName.getNameDeclaration().getName())){ + map.put(astName.getNameDeclaration().getName(), 1); + } + else { + map.put(astName.getNameDeclaration().getName(), + map.get(astName.getNameDeclaration().getName())+1); + addViolation(data, assignmentOperator); + } } } else if (astName.getImage().equals(name.getImage())) { ASTAssignmentOperator assignmentOperator = statement .getFirstDescendantOfType(ASTAssignmentOperator.class); if (assignmentOperator != null && !assignmentOperator.isCompound()) { - addViolation(data, astName); + // check whether is first time to break the rule + if (!map.containsKey(astName.getNameDeclaration().getName())){ + map.put(astName.getNameDeclaration().getName(), 1); + } + else { + map.put(astName.getNameDeclaration().getName(), + map.get(astName.getNameDeclaration().getName())+1); + addViolation(data, assignmentOperator); + } } } } From f5ccc94130d234e9e7f922ea74bb1afd2cff18dd Mon Sep 17 00:00:00 2001 From: Young Chan Date: Sun, 31 May 2020 10:36:40 +0800 Subject: [PATCH 72/99] fix issue #1736 --- .../performance/UseStringBufferForStringAppendsRule.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 10599981f9..0341bd549b 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 @@ -31,6 +31,12 @@ public class UseStringBufferForStringAppendsRule extends AbstractJavaRule { addRuleChainVisit(ASTVariableDeclaratorId.class); } + /** + * This method is used to check whether user appends string directly instead of using StringBuffer or StringBuilder + * @param node This is the expression of part of java code to be checked. + * @param data This is the data to return. + * @return Object This returns the data passed in. If violation happens, violation is added to data. + */ @Override public Object visit(ASTVariableDeclaratorId node, Object data) { if (!TypeHelper.isA(node, String.class) || node.isArray() @@ -76,8 +82,10 @@ public class UseStringBufferForStringAppendsRule extends AbstractJavaRule { } } if (statement.getNumChildren() > 0 && statement.getChild(0) instanceof ASTPrimaryExpression) { +// System.out.println(name.toString()); ASTName astName = statement.getChild(0).getFirstDescendantOfType(ASTName.class); if (astName != null) { +// System.out.println(astName.getNameDeclaration().getName()); if (astName.equals(name)) { ASTAssignmentOperator assignmentOperator = statement .getFirstDescendantOfType(ASTAssignmentOperator.class); From 6cff647681f27780e6246e94c47362bd14d6fe4b Mon Sep 17 00:00:00 2001 From: Young Chan Date: Sun, 31 May 2020 10:39:42 +0800 Subject: [PATCH 73/99] fix issue #2207 --- .../AvoidInstantiatingObjectsInLoopsRule.java | 68 +++++++++++++++---- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java index eee41ce882..226d3b3e1f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java @@ -5,33 +5,77 @@ package net.sourceforge.pmd.lang.java.rule.performance; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; -import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; -import net.sourceforge.pmd.lang.java.ast.ASTForInit; -import net.sourceforge.pmd.lang.java.ast.ASTForStatement; -import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; -import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; -import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; +import net.sourceforge.pmd.lang.java.ast.*; public class AvoidInstantiatingObjectsInLoopsRule extends AbstractOptimizationRule { - + /** + * This method is used to check whether user instantiates variables + * which are not assigned in loops. + * @param node This is the expression of part of java code to be checked. + * @param data This is the data to return. + * @return Object This returns the data passed in. If violation happens, violation is added to data. + */ @Override public Object visit(ASTAllocationExpression node, Object data) { if (insideLoop(node) && fourthParentNotThrow(node) && fourthParentNotReturn(node)) { - addViolation(data, node); + if (thirdParentNotASTExpression(node) && fourthParentNotASTStatementExpression(node)) { + addViolation(data, node); + } } return data; } - private boolean fourthParentNotThrow(ASTAllocationExpression node) { + /** + * This method is used to check whether the instantiated variable is assigned or not. + * @param node This is the expression of part of java code to be checked. + * @return boolean This returns Whether the third parent of node is an ASTExpression. + */ + public boolean thirdParentNotASTExpression(ASTAllocationExpression node) { + if (node.getParent().getClass().toString().equals( + "class net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix") && + node.getParent().getParent().getClass().toString().equals( + "class net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression")) { + return !node.getParent().getParent().getParent().getClass().toString().equals( + "class net.sourceforge.pmd.lang.java.ast.ASTExpression"); + }else { + return false; + } + } + + /** + * This method is used to check whether the instantiated variable is assigned or not. + * @param node This is the expression of part of java code to be checked. + * @return boolean This returns Whether the fourth parent of node is an ASTStatementExpression. + */ + public boolean fourthParentNotASTStatementExpression(ASTAllocationExpression node) { + return !node.getParent().getParent().getParent().getClass().toString().equals( + "class net.sourceforge.pmd.lang.java.ast.ASTStateExpression"); + } + + /** + * This method is used to check whether this expression is a throw statement. + * @param node This is the expression of part of java code to be checked. + * @return boolean This returns Whether the fourth parent of node is an instance of throw statement. + */ + public boolean fourthParentNotThrow(ASTAllocationExpression node) { return !(node.getParent().getParent().getParent().getParent() instanceof ASTThrowStatement); } - private boolean fourthParentNotReturn(ASTAllocationExpression node) { + /** + * This method is used to check whether this expression is a return statement. + * @param node This is the expression of part of java code to be checked. + * @return boolean This returns Whether the fourth parent of node is an instance of return statement. + */ + public boolean fourthParentNotReturn(ASTAllocationExpression node) { return !(node.getParent().getParent().getParent().getParent() instanceof ASTReturnStatement); } - private boolean insideLoop(ASTAllocationExpression node) { + /** + * This method is used to check whether this expression is in a loop. + * @param node This is the expression of part of java code to be checked. + * @return boolean This returns whether the expression is in a loop such as a switch or a while statement. + */ + public boolean insideLoop(ASTAllocationExpression node) { Node n = node.getParent(); while (n != null) { if (n instanceof ASTDoStatement || n instanceof ASTWhileStatement || n instanceof ASTForStatement) { From b782575951fb0b9f45c42120242c9952d1f53fd2 Mon Sep 17 00:00:00 2001 From: Young Chan Date: Sun, 31 May 2020 18:18:02 +0800 Subject: [PATCH 74/99] Update AvoidInstantiatingObjectsInLoopsRule.java --- .../AvoidInstantiatingObjectsInLoopsRule.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java index 226d3b3e1f..62b4a4d8f3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java @@ -5,7 +5,13 @@ package net.sourceforge.pmd.lang.java.rule.performance; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.*; +import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; +import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; +import net.sourceforge.pmd.lang.java.ast.ASTForInit; +import net.sourceforge.pmd.lang.java.ast.ASTForStatement; +import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; +import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; +import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; public class AvoidInstantiatingObjectsInLoopsRule extends AbstractOptimizationRule { /** @@ -17,6 +23,7 @@ public class AvoidInstantiatingObjectsInLoopsRule extends AbstractOptimizationRu */ @Override public Object visit(ASTAllocationExpression node, Object data) { + //CS304 Issue link: https://github.com/pmd/pmd/issues/2207 if (insideLoop(node) && fourthParentNotThrow(node) && fourthParentNotReturn(node)) { if (thirdParentNotASTExpression(node) && fourthParentNotASTStatementExpression(node)) { addViolation(data, node); @@ -31,13 +38,14 @@ public class AvoidInstantiatingObjectsInLoopsRule extends AbstractOptimizationRu * @return boolean This returns Whether the third parent of node is an ASTExpression. */ public boolean thirdParentNotASTExpression(ASTAllocationExpression node) { + //CS304 Issue link: https://github.com/pmd/pmd/issues/2207 if (node.getParent().getClass().toString().equals( - "class net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix") && - node.getParent().getParent().getClass().toString().equals( + "class net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix") + && node.getParent().getParent().getClass().toString().equals( "class net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression")) { return !node.getParent().getParent().getParent().getClass().toString().equals( "class net.sourceforge.pmd.lang.java.ast.ASTExpression"); - }else { + } else { return false; } } @@ -48,6 +56,7 @@ public class AvoidInstantiatingObjectsInLoopsRule extends AbstractOptimizationRu * @return boolean This returns Whether the fourth parent of node is an ASTStatementExpression. */ public boolean fourthParentNotASTStatementExpression(ASTAllocationExpression node) { + //CS304 Issue link: https://github.com/pmd/pmd/issues/2207 return !node.getParent().getParent().getParent().getClass().toString().equals( "class net.sourceforge.pmd.lang.java.ast.ASTStateExpression"); } From e6c32ecbaa7ec16f6b9ceaed5c7f640dbf67cc57 Mon Sep 17 00:00:00 2001 From: Young Chan Date: Sun, 31 May 2020 18:18:24 +0800 Subject: [PATCH 75/99] Update UseStringBufferForStringAppendsRule.java --- .../UseStringBufferForStringAppendsRule.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) 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 0341bd549b..69f6719aa8 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 @@ -44,6 +44,7 @@ public class UseStringBufferForStringAppendsRule extends AbstractJavaRule { return data; } + //CS304 Issue link: https://github.com/pmd/pmd/issues/1736 // create a new hashmap to store occurrence of not-recommending string concatenation operations HashMap map = new HashMap<>(); @@ -82,21 +83,19 @@ public class UseStringBufferForStringAppendsRule extends AbstractJavaRule { } } if (statement.getNumChildren() > 0 && statement.getChild(0) instanceof ASTPrimaryExpression) { -// System.out.println(name.toString()); ASTName astName = statement.getChild(0).getFirstDescendantOfType(ASTName.class); if (astName != null) { -// System.out.println(astName.getNameDeclaration().getName()); if (astName.equals(name)) { ASTAssignmentOperator assignmentOperator = statement .getFirstDescendantOfType(ASTAssignmentOperator.class); if (assignmentOperator != null && assignmentOperator.isCompound()) { + //CS304 Issue link: https://github.com/pmd/pmd/issues/1736 // check whether is first time to break the rule - if (!map.containsKey(astName.getNameDeclaration().getName())){ + if (!map.containsKey(astName.getNameDeclaration().getName())) { map.put(astName.getNameDeclaration().getName(), 1); - } - else { + } else { map.put(astName.getNameDeclaration().getName(), - map.get(astName.getNameDeclaration().getName())+1); + map.get(astName.getNameDeclaration().getName()) + 1); addViolation(data, assignmentOperator); } } @@ -104,13 +103,13 @@ public class UseStringBufferForStringAppendsRule extends AbstractJavaRule { ASTAssignmentOperator assignmentOperator = statement .getFirstDescendantOfType(ASTAssignmentOperator.class); if (assignmentOperator != null && !assignmentOperator.isCompound()) { + //CS304 Issue link: https://github.com/pmd/pmd/issues/1736 // check whether is first time to break the rule - if (!map.containsKey(astName.getNameDeclaration().getName())){ + if (!map.containsKey(astName.getNameDeclaration().getName())) { map.put(astName.getNameDeclaration().getName(), 1); - } - else { + } else { map.put(astName.getNameDeclaration().getName(), - map.get(astName.getNameDeclaration().getName())+1); + map.get(astName.getNameDeclaration().getName()) + 1); addViolation(data, assignmentOperator); } } From 6f6f87c7dcf98ebd3f7a78b4999609413c4cab99 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 9 Jul 2020 15:21:36 +0200 Subject: [PATCH 76/99] [java] Add test for #1736: UseStringBufferForStringAppends false positive --- .../xml/UseStringBufferForStringAppends.xml | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseStringBufferForStringAppends.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseStringBufferForStringAppends.xml index e430e4a433..2737c997e7 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseStringBufferForStringAppends.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseStringBufferForStringAppends.xml @@ -7,14 +7,15 @@ failure case 1 - 6 + 7 @@ -60,15 +61,16 @@ public class Foo { - failure case + failure case, constructor 1 - 5 + 6 @@ -77,25 +79,26 @@ public class Foo { static failure case 1 - 5 + 6 - reference self + reference self inside for loop 1 5 + + + + [java] UseStringBufferForStringAppends: False positive if only one concatenation #1736 + 0 + From 8766d54a07440a04e79c0063be65e69c55219a66 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 9 Jul 2020 16:08:30 +0200 Subject: [PATCH 77/99] [java] Fix and simplify UseStringBufferForStringAppendsRule --- .../UseStringBufferForStringAppendsRule.java | 51 ++++++++++--------- .../xml/UseStringBufferForStringAppends.xml | 7 +-- 2 files changed, 32 insertions(+), 26 deletions(-) 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 69f6719aa8..30619175ba 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 @@ -4,7 +4,6 @@ package net.sourceforge.pmd.lang.java.rule.performance; -import java.util.HashMap; import java.util.Objects; import net.sourceforge.pmd.lang.ast.Node; @@ -44,9 +43,8 @@ public class UseStringBufferForStringAppendsRule extends AbstractJavaRule { return data; } - //CS304 Issue link: https://github.com/pmd/pmd/issues/1736 - // create a new hashmap to store occurrence of not-recommending string concatenation operations - HashMap map = new HashMap<>(); + // Remember how often we the variable has been used + int usageCounter = 0; for (NameOccurrence no : node.getUsages()) { Node name = no.getLocation(); @@ -85,33 +83,36 @@ public class UseStringBufferForStringAppendsRule extends AbstractJavaRule { if (statement.getNumChildren() > 0 && statement.getChild(0) instanceof ASTPrimaryExpression) { ASTName astName = statement.getChild(0).getFirstDescendantOfType(ASTName.class); if (astName != null) { + ASTAssignmentOperator assignmentOperator = statement + .getFirstDescendantOfType(ASTAssignmentOperator.class); if (astName.equals(name)) { - ASTAssignmentOperator assignmentOperator = statement - .getFirstDescendantOfType(ASTAssignmentOperator.class); if (assignmentOperator != null && assignmentOperator.isCompound()) { - //CS304 Issue link: https://github.com/pmd/pmd/issues/1736 - // check whether is first time to break the rule - if (!map.containsKey(astName.getNameDeclaration().getName())) { - map.put(astName.getNameDeclaration().getName(), 1); - } else { - map.put(astName.getNameDeclaration().getName(), - map.get(astName.getNameDeclaration().getName()) + 1); + if (isWithinLoop(name)) { + // always report within a loop addViolation(data, assignmentOperator); + } else { + usageCounter++; + if (usageCounter > 1) { + // only report, if it is not the first time + addViolation(data, assignmentOperator); + } } } - } else if (astName.getImage().equals(name.getImage())) { - ASTAssignmentOperator assignmentOperator = statement - .getFirstDescendantOfType(ASTAssignmentOperator.class); + } else if (astName.hasImageEqualTo(name.getImage())) { if (assignmentOperator != null && !assignmentOperator.isCompound()) { - //CS304 Issue link: https://github.com/pmd/pmd/issues/1736 - // check whether is first time to break the rule - if (!map.containsKey(astName.getNameDeclaration().getName())) { - map.put(astName.getNameDeclaration().getName(), 1); - } else { - map.put(astName.getNameDeclaration().getName(), - map.get(astName.getNameDeclaration().getName()) + 1); + if (isWithinLoop(name)) { + // always report within a loop addViolation(data, assignmentOperator); + } else { + usageCounter++; + if (usageCounter > 1) { + // only report, if it is not the first time + addViolation(data, assignmentOperator); + } } + } else if (assignmentOperator != null && assignmentOperator.isCompound() + && usageCounter >= 1) { + addViolation(data, assignmentOperator); } } } @@ -125,4 +126,8 @@ public class UseStringBufferForStringAppendsRule extends AbstractJavaRule { && name.getFirstParentOfType(ASTWhileStatement.class) == null && name.getFirstParentOfType(ASTDoStatement.class) == null; } + + private boolean isWithinLoop(Node name) { + return !isNotWithinLoop(name); + } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseStringBufferForStringAppends.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseStringBufferForStringAppends.xml index 2737c997e7..33a85af08c 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseStringBufferForStringAppends.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseStringBufferForStringAppends.xml @@ -94,14 +94,15 @@ public class Foo { reference self inside for loop - 1 - 5 + 2 + 5,6 Date: Thu, 9 Jul 2020 18:54:53 +0200 Subject: [PATCH 78/99] [java] Add test for #2207: AvoidInstantiatingObjectsInLoops false positives --- .../xml/AvoidInstantiatingObjectsInLoops.xml | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml index cb9ada5759..28f7a76488 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml @@ -89,8 +89,7 @@ public class Foo { ]]> - - + #1215 AvoidInstantiatingObjectsInLoops matches the right side of a list iteration loop @@ -124,6 +123,46 @@ public class TestInstantiationInLoop { System.out.println(filename); } } +} + ]]> + + + + [java] False positive: AvoidInstantiatingObjectsInLoops should not flag objects with different parameters or objects assigned or passed as parameters #2207 + 0 + + + + + False positive when assigning to a list/array (see #2207 and #1043) + 0 + cars = new ArrayList<>(); + for(int i = 0; i < 3; ++i) { + cars.add(new Car()); + } + } } ]]> From c5364a81f8eb0c703eaeec0d366db1329d1529eb Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 9 Jul 2020 18:55:10 +0200 Subject: [PATCH 79/99] [java] Fix and simplify AvoidInstantiatingObjectsInLoopsRule --- .../AvoidInstantiatingObjectsInLoopsRule.java | 101 +++++++++++------- 1 file changed, 61 insertions(+), 40 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java index 62b4a4d8f3..247d5b8a3d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java @@ -4,61 +4,82 @@ package net.sourceforge.pmd.lang.java.rule.performance; +import java.util.Collection; + import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; +import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; +import net.sourceforge.pmd.lang.java.ast.ASTBlock; +import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; +import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement; import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; import net.sourceforge.pmd.lang.java.ast.ASTForInit; import net.sourceforge.pmd.lang.java.ast.ASTForStatement; +import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; +import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; +import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; + +public class AvoidInstantiatingObjectsInLoopsRule extends AbstractJavaRule { + + public AvoidInstantiatingObjectsInLoopsRule() { + addRuleChainVisit(ASTAllocationExpression.class); + } -public class AvoidInstantiatingObjectsInLoopsRule extends AbstractOptimizationRule { /** * This method is used to check whether user instantiates variables - * which are not assigned in loops. + * which are not assigned to arrays/lists in loops. * @param node This is the expression of part of java code to be checked. * @param data This is the data to return. * @return Object This returns the data passed in. If violation happens, violation is added to data. */ @Override public Object visit(ASTAllocationExpression node, Object data) { - //CS304 Issue link: https://github.com/pmd/pmd/issues/2207 - if (insideLoop(node) && fourthParentNotThrow(node) && fourthParentNotReturn(node)) { - if (thirdParentNotASTExpression(node) && fourthParentNotASTStatementExpression(node)) { - addViolation(data, node); - } + if (notInsideLoop(node)) { + return data; + } + + if (fourthParentNotThrow(node) + && fourthParentNotReturn(node) + && notArrayAssignment(node) + && notCollectionAccess(node) + && notBreakFollowing(node)) { + addViolation(data, node); } return data; } - /** - * This method is used to check whether the instantiated variable is assigned or not. - * @param node This is the expression of part of java code to be checked. - * @return boolean This returns Whether the third parent of node is an ASTExpression. - */ - public boolean thirdParentNotASTExpression(ASTAllocationExpression node) { - //CS304 Issue link: https://github.com/pmd/pmd/issues/2207 - if (node.getParent().getClass().toString().equals( - "class net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix") - && node.getParent().getParent().getClass().toString().equals( - "class net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression")) { - return !node.getParent().getParent().getParent().getClass().toString().equals( - "class net.sourceforge.pmd.lang.java.ast.ASTExpression"); - } else { - return false; + private boolean notArrayAssignment(ASTAllocationExpression node) { + if (node.getNthParent(4) instanceof ASTStatementExpression) { + ASTPrimaryExpression assignee = node.getNthParent(4).getFirstChildOfType(ASTPrimaryExpression.class); + ASTPrimarySuffix suffix = assignee.getFirstChildOfType(ASTPrimarySuffix.class); + return suffix == null || !suffix.isArrayDereference(); } + return true; } - /** - * This method is used to check whether the instantiated variable is assigned or not. - * @param node This is the expression of part of java code to be checked. - * @return boolean This returns Whether the fourth parent of node is an ASTStatementExpression. - */ - public boolean fourthParentNotASTStatementExpression(ASTAllocationExpression node) { - //CS304 Issue link: https://github.com/pmd/pmd/issues/2207 - return !node.getParent().getParent().getParent().getClass().toString().equals( - "class net.sourceforge.pmd.lang.java.ast.ASTStateExpression"); + private boolean notCollectionAccess(ASTAllocationExpression node) { + if (node.getNthParent(4) instanceof ASTArgumentList && node.getNthParent(8) instanceof ASTStatementExpression) { + ASTStatementExpression statement = (ASTStatementExpression) node.getNthParent(8); + return !TypeHelper.isA(statement, Collection.class); + } + return true; + } + + private boolean notBreakFollowing(ASTAllocationExpression node) { + ASTBlockStatement blockStatement = node.getFirstParentOfType(ASTBlockStatement.class); + if (blockStatement != null) { + ASTBlock block = blockStatement.getFirstParentOfType(ASTBlock.class); + if (block.getNumChildren() > blockStatement.getIndexInParent() + 1) { + ASTBlockStatement next = (ASTBlockStatement) block.getChild(blockStatement.getIndexInParent() + 1); + return !next.hasDescendantOfType(ASTBreakStatement.class); + } + } + return true; } /** @@ -66,8 +87,8 @@ public class AvoidInstantiatingObjectsInLoopsRule extends AbstractOptimizationRu * @param node This is the expression of part of java code to be checked. * @return boolean This returns Whether the fourth parent of node is an instance of throw statement. */ - public boolean fourthParentNotThrow(ASTAllocationExpression node) { - return !(node.getParent().getParent().getParent().getParent() instanceof ASTThrowStatement); + private boolean fourthParentNotThrow(ASTAllocationExpression node) { + return !(node.getNthParent(4) instanceof ASTThrowStatement); } /** @@ -75,20 +96,20 @@ public class AvoidInstantiatingObjectsInLoopsRule extends AbstractOptimizationRu * @param node This is the expression of part of java code to be checked. * @return boolean This returns Whether the fourth parent of node is an instance of return statement. */ - public boolean fourthParentNotReturn(ASTAllocationExpression node) { - return !(node.getParent().getParent().getParent().getParent() instanceof ASTReturnStatement); + private boolean fourthParentNotReturn(ASTAllocationExpression node) { + return !(node.getNthParent(4) instanceof ASTReturnStatement); } /** - * This method is used to check whether this expression is in a loop. + * This method is used to check whether this expression is not in a loop. * @param node This is the expression of part of java code to be checked. - * @return boolean This returns whether the expression is in a loop such as a switch or a while statement. + * @return boolean false if the given node is inside a loop, true otherwise */ - public boolean insideLoop(ASTAllocationExpression node) { + private boolean notInsideLoop(ASTAllocationExpression node) { Node n = node.getParent(); while (n != null) { if (n instanceof ASTDoStatement || n instanceof ASTWhileStatement || n instanceof ASTForStatement) { - return true; + return false; } else if (n instanceof ASTForInit) { /* * init part is not technically inside the loop. Skip parent @@ -108,6 +129,6 @@ public class AvoidInstantiatingObjectsInLoopsRule extends AbstractOptimizationRu } n = n.getParent(); } - return false; + return true; } } From cc495f180cc5170d3b370ed76f4b2fe6b92564ab Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 9 Jul 2020 19:00:59 +0200 Subject: [PATCH 80/99] [doc] Update release notes, refs #2558 Fixes #1736 Fixes #2207 --- docs/pages/release_notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index c1806b1dab..d336acd621 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -18,10 +18,14 @@ This is a {{ site.pmd.release_type }} release. * apex-bestpractices * [#2626](https://github.com/pmd/pmd/issues/2626): \[apex] UnusedLocalVariable - false positive on case insensitivity allowed in Apex +* java-performance + * [#1736](https://github.com/pmd/pmd/issues/1736): \[java] UseStringBufferForStringAppends: False positive if only one concatenation + * [#2207](https://github.com/pmd/pmd/issues/2207): \[java] False positive: AvoidInstantiatingObjectsInLoops should not flag objects when assigned to lists/arrays ### API Changes ### External Contributions +* [#2558](https://github.com/pmd/pmd/pull/2558): \[java] Fix issue #1736 and issue #2207 - [Young Chan](https://github.com/YYoungC) {% endtocmaker %} From cc408d2876b6fc7e0b080f00d215aa4a2e8ef239 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Mon, 13 Jul 2020 20:00:46 +0200 Subject: [PATCH 81/99] [java] AvoidInstantiatingObjectsInLoopsRule - fix false negative --- .../AvoidInstantiatingObjectsInLoopsRule.java | 4 +- .../xml/AvoidInstantiatingObjectsInLoops.xml | 39 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java index 247d5b8a3d..6a8be010f1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java @@ -76,7 +76,9 @@ public class AvoidInstantiatingObjectsInLoopsRule extends AbstractJavaRule { ASTBlock block = blockStatement.getFirstParentOfType(ASTBlock.class); if (block.getNumChildren() > blockStatement.getIndexInParent() + 1) { ASTBlockStatement next = (ASTBlockStatement) block.getChild(blockStatement.getIndexInParent() + 1); - return !next.hasDescendantOfType(ASTBreakStatement.class); + if (next.getNumChildren() == 1 && next.getChild(0).getNumChildren() == 1) { + return !(next.getChild(0).getChild(0) instanceof ASTBreakStatement); + } } } return true; diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml index 28f7a76488..c875609d59 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml @@ -163,6 +163,45 @@ public class PMDDemo { cars.add(new Car()); } } +} + ]]> + + + + False negative with break in other for-loop + 1 + 7 + getFilteredMessages( + String fileName, FileContents fileContents, DetailAST rootAST) { + final SortedSet result = new TreeSet<>(messages); + for (LocalizedMessage element : messages) { + final TreeWalkerAuditEvent event = + new TreeWalkerAuditEvent(fileContents, fileName, element, rootAST); + for (TreeWalkerFilter filter : filters) { + if (!filter.accept(event)) { + result.remove(element); + break; + } + } + } + return result; + } +} + ]]> + + + + Instantiation in loop condition + 1 + 3 + 0) { + } + } } ]]> From 2032bb4477d4179ea9340209157af46a6af3fc29 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Mon, 13 Jul 2020 20:08:40 +0200 Subject: [PATCH 82/99] [doc] Update release notes, refs #2590 --- docs/pages/release_notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 3cf2442fcb..43e599dd52 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -66,6 +66,7 @@ The command line version of PMD continues to use **scala 2.13**. * [#2547](https://github.com/pmd/pmd/pull/2547): \[scala] Add cross compilation for scala 2.12 and 2.13 - [João Ferreira](https://github.com/jtjeferreira) * [#2567](https://github.com/pmd/pmd/pull/2567): \[c#] Fix CPD suppression with comments doesn't work - [Lixon Lookose](https://github.com/LixonLookose) +* [#2590](https://github.com/pmd/pmd/pull/2590): Update libraries snyk is referring to as `unsafe` - [Artem Krosheninnikov](https://github.com/KroArtem) {% endtocmaker %} From a60615f54241e623077748aa23c17a0f66df4bef Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 16 Jul 2020 10:39:30 +0200 Subject: [PATCH 83/99] [java] AvoidInstantiatingObjectsInLoopsRule - add test case for fixed FN By changing this rule to use rule chain, this FN for anonymous classes has been fixed as well. --- .../xml/AvoidInstantiatingObjectsInLoops.xml | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml index c875609d59..d8ddc23c75 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml @@ -202,6 +202,33 @@ public class Foo { while(new String().length() > 0) { } } +} + ]]> + + + + false negative in anonymous classes + 2 + 5,14 + From 68b55a7dd26961d5b4b4be51415cef3b530636c9 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 16 Jul 2020 11:39:11 +0200 Subject: [PATCH 84/99] [ci] Update java to 11.0.8+10 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2276f63309..5ff4e3b8b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,7 @@ jobs: env: BUILD=publish before_install: - - bash .travis/before_install.sh "11.0.7+10" + - bash .travis/before_install.sh "11.0.8+10" - source ${HOME}/java.env install: true before_script: true From 693c870f28c15fe569fc60120a3505b394efa39c Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 16 Jul 2020 13:12:17 +0200 Subject: [PATCH 85/99] [java] LawOfDemeter - add test cases --- .../java/rule/design/LawOfDemeterRule.java | 1 + .../java/rule/design/xml/LawOfDemeter.xml | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterRule.java index 8447b26dfb..69c0beffed 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterRule.java @@ -15,6 +15,7 @@ import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; import net.sourceforge.pmd.lang.java.ast.ASTBlock; +import net.sourceforge.pmd.lang.java.ast.ASTCastExpression; import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTLiteral; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/LawOfDemeter.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/LawOfDemeter.xml index 56d22f3699..8f8e0b5f40 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/LawOfDemeter.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/LawOfDemeter.xml @@ -288,6 +288,60 @@ public class Test { .withFoo("foo") .build(); } +} + ]]> + + + + [java] LawOfDemeter: False positive with 'this' pointer #2174 + 0 + + + + + [java] LawOfDemeter: False positive when casting to derived class #2189 + 0 + > tasks; + + public void cancelTasks(ForkJoinTask cancelTask) { + for (ForkJoinTask task : tasks) { + if (!task.equals(cancelTask)) { + task.cancel(true); + ((SearchNumberTask) task).writeCancelMessage(); // wrong violation: method chain calls + } + } + } } ]]> From 836c272975e3811430db7b58edc7d782c3d70050 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 16 Jul 2020 13:14:17 +0200 Subject: [PATCH 86/99] [doc] Update release notes, refs #2560, fixes #2174, fixes #2189 --- docs/pages/release_notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index b8f8783555..9d75f02962 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -15,10 +15,14 @@ This is a {{ site.pmd.release_type }} release. ### New and noteworthy ### Fixed Issues +* java-design + * [#2174](https://github.com/pmd/pmd/issues/2174): \[java] LawOfDemeter: False positive with 'this' pointer + * [#2189](https://github.com/pmd/pmd/issues/2189): \[java] LawOfDemeter: False positive when casting to derived class ### API Changes ### External Contributions +* [#2560](https://github.com/pmd/pmd/pull/2560): \[java] Fix false positives of LawOfDemeter: this and cast expressions - [xioayuge](https://github.com/xioayuge) {% endtocmaker %} From 35856a10644553810467dc544285a12afd25c58c Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 16 Jul 2020 17:47:05 +0200 Subject: [PATCH 87/99] [doc] Update release notes, refs #2610 --- docs/pages/release_notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 46a45dea6c..bf12ac93e9 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -63,6 +63,8 @@ The command line version of PMD continues to use **scala 2.13**. ### Fixed Issues +* apex + * [#2610](https://github.com/pmd/pmd/pull/2610): \[apex] Support top-level enums in rules * apex-bestpractices * [#2554](https://github.com/pmd/pmd/issues/2554): \[apex] Exception applying rule UnusedLocalVariable on trigger * core From 9180b479cb77eeb315ddf78d866d6e280f1b0620 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 16 Jul 2020 18:08:06 +0200 Subject: [PATCH 88/99] [apex] Remove profile designer, which started the deprecated designer Users are encouraged to use https://github.com/pmd/pmd-designer instead. --- pmd-apex/pom.xml | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/pmd-apex/pom.xml b/pmd-apex/pom.xml index 8800163eb6..d52a14bdd1 100644 --- a/pmd-apex/pom.xml +++ b/pmd-apex/pom.xml @@ -96,30 +96,4 @@ test - - - - designer - - - - org.codehaus.mojo - exec-maven-plugin - 3.0.0 - - net.sourceforge.pmd.util.designer.Designer - true - - - - net.sourceforge.pmd - pmd-java - ${project.version} - - - - - - - From bb005ea5d4122bcc49e8599fe5bf3d4c13839024 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 16 Jul 2020 18:14:46 +0200 Subject: [PATCH 89/99] Refactor exec-maven-plugin version and usage --- .travis/build-coveralls.sh | 2 +- .travis/build-deploy.sh | 2 +- .travis/build-doc.sh | 2 +- .travis/build-publish.sh | 2 +- .travis/build-sonar.sh | 2 +- .travis/common-functions.sh | 4 ++++ pmd-doc/pom.xml | 1 - pom.xml | 5 +++++ 8 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.travis/build-coveralls.sh b/.travis/build-coveralls.sh index 4f2fcafb3c..c3834512a7 100755 --- a/.travis/build-coveralls.sh +++ b/.travis/build-coveralls.sh @@ -4,7 +4,7 @@ set -e source .travis/logger.sh source .travis/common-functions.sh -VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) +VERSION=$(get_pom_version) log_info "Building PMD Coveralls.io report ${VERSION} on branch ${TRAVIS_BRANCH}" if ! travis_isPush; then diff --git a/.travis/build-deploy.sh b/.travis/build-deploy.sh index 75a03e0484..f4bbddc2d5 100755 --- a/.travis/build-deploy.sh +++ b/.travis/build-deploy.sh @@ -7,7 +7,7 @@ source .travis/github-releases-api.sh source .travis/sourceforge-api.sh source .travis/regression-tester.sh -VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) +VERSION=$(get_pom_version) log_info "Building PMD ${VERSION} on branch ${TRAVIS_BRANCH}" MVN_BUILD_FLAGS="-B -V" diff --git a/.travis/build-doc.sh b/.travis/build-doc.sh index 2431635127..581fc64064 100755 --- a/.travis/build-doc.sh +++ b/.travis/build-doc.sh @@ -8,7 +8,7 @@ source .travis/sourceforge-api.sh source .travis/pmd-code-api.sh function main() { - VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) + VERSION=$(get_pom_version) log_info "Building PMD Documentation ${VERSION} on branch ${TRAVIS_BRANCH}" # diff --git a/.travis/build-publish.sh b/.travis/build-publish.sh index 6b25635bb2..5599103585 100644 --- a/.travis/build-publish.sh +++ b/.travis/build-publish.sh @@ -6,7 +6,7 @@ source .travis/common-functions.sh source .travis/github-releases-api.sh source .travis/sourceforge-api.sh -VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) +VERSION=$(get_pom_version) log_info "PMD Release ${VERSION}" if ! travis_isPush; then diff --git a/.travis/build-sonar.sh b/.travis/build-sonar.sh index 71d2314e2f..36cc2cc6b4 100755 --- a/.travis/build-sonar.sh +++ b/.travis/build-sonar.sh @@ -4,7 +4,7 @@ set -e source .travis/logger.sh source .travis/common-functions.sh -VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) +VERSION=$(get_pom_version) log_info "Building PMD Sonar ${VERSION} on branch ${TRAVIS_BRANCH}" if ! travis_isPush; then diff --git a/.travis/common-functions.sh b/.travis/common-functions.sh index e46731feb6..3148dd9796 100755 --- a/.travis/common-functions.sh +++ b/.travis/common-functions.sh @@ -53,3 +53,7 @@ function travis_isWindows() { return 1 fi } + +function get_pom_version() { + echo $(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) +} diff --git a/pmd-doc/pom.xml b/pmd-doc/pom.xml index 8e953090a2..3ab9d7e3a3 100644 --- a/pmd-doc/pom.xml +++ b/pmd-doc/pom.xml @@ -24,7 +24,6 @@ org.codehaus.mojo exec-maven-plugin - 3.0.0 generate-rule-docs diff --git a/pom.xml b/pom.xml index 062f361a90..b42b4cede0 100644 --- a/pom.xml +++ b/pom.xml @@ -265,6 +265,11 @@ build-helper-maven-plugin 3.0.0 + + org.codehaus.mojo + exec-maven-plugin + 3.0.0 + org.apache.maven.plugins maven-source-plugin From 34f185b03a41e5df1ed52897cf279c0607dee181 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 16 Jul 2020 18:16:19 +0200 Subject: [PATCH 90/99] Use xmlint to get the pom version --- .travis/common-functions.sh | 2 +- do-release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis/common-functions.sh b/.travis/common-functions.sh index 3148dd9796..9a8cf3c4b0 100755 --- a/.travis/common-functions.sh +++ b/.travis/common-functions.sh @@ -55,5 +55,5 @@ function travis_isWindows() { } function get_pom_version() { - echo $(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) + echo $(echo -e 'setns x=http://maven.apache.org/POM/4.0.0\ncat /x:project/x:version/text()' | xmllint --shell pom.xml | grep -v /) } diff --git a/do-release.sh b/do-release.sh index 25d5785ed3..64e5d147f2 100755 --- a/do-release.sh +++ b/do-release.sh @@ -25,7 +25,7 @@ echo "Releasing PMD" echo "-------------------------------------------" # see also https://gist.github.com/pdunnavant/4743895 -CURRENT_VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) +CURRENT_VERSION=$(echo -e 'setns x=http://maven.apache.org/POM/4.0.0\ncat /x:project/x:version/text()' | xmllint --shell pom.xml | grep -v /) RELEASE_VERSION=${CURRENT_VERSION%-SNAPSHOT} MAJOR=$(echo $RELEASE_VERSION | cut -d . -f 1) MINOR=$(echo $RELEASE_VERSION | cut -d . -f 2) From 2adc457d23bb5582a5dae9e94eb955fdfb9ebacf Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 16 Jul 2020 18:23:37 +0200 Subject: [PATCH 91/99] [doc] Update release notes, refs #2597, fixes #2594 --- docs/pages/release_notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 0013a890dc..e4a9cef52e 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -63,6 +63,8 @@ The command line version of PMD continues to use **scala 2.13**. ### Fixed Issues +* core + * [#2594](https://github.com/pmd/pmd/issues/2594): \[core] Update exec-maven-plugin and align it in all project * c# * [#2551](https://github.com/pmd/pmd/issues/2551): \[c#] CPD suppression with comments doesn't work * java-codestyle @@ -89,6 +91,7 @@ The command line version of PMD continues to use **scala 2.13**. * [#2547](https://github.com/pmd/pmd/pull/2547): \[scala] Add cross compilation for scala 2.12 and 2.13 - [João Ferreira](https://github.com/jtjeferreira) * [#2567](https://github.com/pmd/pmd/pull/2567): \[c#] Fix CPD suppression with comments doesn't work - [Lixon Lookose](https://github.com/LixonLookose) * [#2573](https://github.com/pmd/pmd/pull/2573): \[java] DefaultPackage: Allow package default JUnit 5 Test methods - [Craig Andrews](https://github.com/candrews) +* [#2597](https://github.com/pmd/pmd/pull/2597): \[dependencies] Fix issue #2594, update exec-maven-plugin everywhere - [Artem Krosheninnikov](https://github.com/KroArtem) {% endtocmaker %} From 59acd6d969c7bca48969bb554cf1ec7e00a917f2 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 16 Jul 2020 18:46:40 +0200 Subject: [PATCH 92/99] Revert "Use xmlint to get the pom version" This reverts commit 34f185b03a41e5df1ed52897cf279c0607dee181. --- .travis/common-functions.sh | 2 +- do-release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis/common-functions.sh b/.travis/common-functions.sh index 9a8cf3c4b0..3148dd9796 100755 --- a/.travis/common-functions.sh +++ b/.travis/common-functions.sh @@ -55,5 +55,5 @@ function travis_isWindows() { } function get_pom_version() { - echo $(echo -e 'setns x=http://maven.apache.org/POM/4.0.0\ncat /x:project/x:version/text()' | xmllint --shell pom.xml | grep -v /) + echo $(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) } diff --git a/do-release.sh b/do-release.sh index 64e5d147f2..25d5785ed3 100755 --- a/do-release.sh +++ b/do-release.sh @@ -25,7 +25,7 @@ echo "Releasing PMD" echo "-------------------------------------------" # see also https://gist.github.com/pdunnavant/4743895 -CURRENT_VERSION=$(echo -e 'setns x=http://maven.apache.org/POM/4.0.0\ncat /x:project/x:version/text()' | xmllint --shell pom.xml | grep -v /) +CURRENT_VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:3.0.0:exec) RELEASE_VERSION=${CURRENT_VERSION%-SNAPSHOT} MAJOR=$(echo $RELEASE_VERSION | cut -d . -f 1) MINOR=$(echo $RELEASE_VERSION | cut -d . -f 2) From a8a3fccdf58ff9e81b829ee77aefb2ed9592ae7f Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 16 Jul 2020 19:12:05 +0200 Subject: [PATCH 93/99] [doc] Update release notes, new rule UnusedAssignment --- docs/pages/release_notes.md | 6 ++++++ .../src/main/resources/rulesets/releases/6260.xml | 13 +++++++++++++ .../main/resources/category/java/bestpractices.xml | 3 ++- .../src/main/resources/rulesets/java/quickstart.xml | 1 + 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 pmd-core/src/main/resources/rulesets/releases/6260.xml diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index b8f8783555..0a9fe46759 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -14,6 +14,12 @@ This is a {{ site.pmd.release_type }} release. ### New and noteworthy +#### New Rules + +* The new Java rule {% rule "java/bestpractices/UnusedAssignment" %} (`java-bestpractices`) finds assignments + to variables, that are never used and are useless. The new rule is supposed to entirely replace + {% rule "java/errorprone/DataflowAnomalyAnalysis" %}. + ### Fixed Issues ### API Changes diff --git a/pmd-core/src/main/resources/rulesets/releases/6260.xml b/pmd-core/src/main/resources/rulesets/releases/6260.xml new file mode 100644 index 0000000000..d4200e1e0c --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/6260.xml @@ -0,0 +1,13 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.26.0 + + + + + diff --git a/pmd-java/src/main/resources/category/java/bestpractices.xml b/pmd-java/src/main/resources/category/java/bestpractices.xml index 3b92712424..6bc9247e8e 100644 --- a/pmd-java/src/main/resources/category/java/bestpractices.xml +++ b/pmd-java/src/main/resources/category/java/bestpractices.xml @@ -1316,7 +1316,8 @@ class Foo{ The rule may be suppressed with the standard `@SuppressWarnings("unused")` tag. - The rule subsumes UnusedLocalVariable, and UnusedFormalParameter. Those violations are filtered + The rule subsumes {% rule "UnusedLocalVariable" %}, and {% rule "UnusedFormalParameter" %}. + Those violations are filtered out by default, in case you already have enabled those rules, but may be enabled with the property `reportUnusedVariables`. Variables whose name starts with `ignored` are filtered out, as is standard practice for exceptions. diff --git a/pmd-java/src/main/resources/rulesets/java/quickstart.xml b/pmd-java/src/main/resources/rulesets/java/quickstart.xml index 93f72a85f0..3dd801c1eb 100644 --- a/pmd-java/src/main/resources/rulesets/java/quickstart.xml +++ b/pmd-java/src/main/resources/rulesets/java/quickstart.xml @@ -41,6 +41,7 @@ + From 88ac918e4fc7d35d1b45d74b3890775af511a63e Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 16 Jul 2020 19:28:42 +0200 Subject: [PATCH 94/99] [java] UnusedAssignmentRule - add rule chain visit --- .../pmd/lang/java/rule/bestpractices/UnusedAssignmentRule.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedAssignmentRule.java index 75d31c4c4a..540bd27afa 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedAssignmentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedAssignmentRule.java @@ -83,6 +83,7 @@ import net.sourceforge.pmd.lang.symboltable.Scope; import net.sourceforge.pmd.properties.PropertyDescriptor; import net.sourceforge.pmd.properties.PropertyFactory; +@SuppressWarnings("PMD.DontCallSuperVisitWhenUsingRuleChain") public class UnusedAssignmentRule extends AbstractJavaRule { /* @@ -141,6 +142,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule { public UnusedAssignmentRule() { definePropertyDescriptor(CHECK_PREFIX_INCREMENT); definePropertyDescriptor(REPORT_UNUSED_VARS); + addRuleChainVisit(ASTCompilationUnit.class); } @Override From 94afdb117d44d365e87006f43d51bc26962113e2 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 17 Jul 2020 12:03:45 +0200 Subject: [PATCH 95/99] Add test engines as surefire plugin dependencies This removes junit-vintage-engine as a test dependency as well as kotlintest-runner-junit5. The engines are only needed during test execution, but should not be available for test compilation. For this to work, the latest surefire plugin is required. --- pmd-apex/pom.xml | 5 -- pmd-cpp/pom.xml | 5 -- pmd-cs/pom.xml | 5 -- pmd-dart/pom.xml | 5 -- pmd-fortran/pom.xml | 5 -- pmd-go/pom.xml | 5 -- pmd-groovy/pom.xml | 5 -- pmd-java/pom.xml | 10 --- .../lang/java/ast/ASTCatchStatementTest.kt | 1 - .../pmd/lang/java/ast/ParserTestSpec.kt | 3 +- pmd-javascript/pom.xml | 5 -- pmd-jsp/pom.xml | 5 -- pmd-kotlin/pom.xml | 5 -- pmd-lang-test/pom.xml | 6 -- pmd-lua/pom.xml | 5 -- pmd-matlab/pom.xml | 5 -- pmd-modelica/pom.xml | 12 ---- .../lang/modelica/ast/ModelicaCoordsTest.kt | 4 +- pmd-objectivec/pom.xml | 5 -- pmd-perl/pom.xml | 5 -- pmd-php/pom.xml | 5 -- pmd-plsql/pom.xml | 5 -- pmd-python/pom.xml | 5 -- pmd-ruby/pom.xml | 5 -- pmd-scala-modules/pmd-scala-common/pom.xml | 10 --- .../pmd/lang/scala/ast/ScalaTreeTests.kt | 4 +- pmd-swift/pom.xml | 5 -- pmd-visualforce/pom.xml | 5 -- pmd-xml/pom.xml | 5 -- pom.xml | 61 ++++++------------- 30 files changed, 23 insertions(+), 193 deletions(-) diff --git a/pmd-apex/pom.xml b/pmd-apex/pom.xml index 67ac49220f..319268b174 100644 --- a/pmd-apex/pom.xml +++ b/pmd-apex/pom.xml @@ -84,11 +84,6 @@ system-rules test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-cpp/pom.xml b/pmd-cpp/pom.xml index b2dd19ece7..9f25873b2f 100644 --- a/pmd-cpp/pom.xml +++ b/pmd-cpp/pom.xml @@ -76,11 +76,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-cs/pom.xml b/pmd-cs/pom.xml index 38a5a822be..9ed215bb18 100644 --- a/pmd-cs/pom.xml +++ b/pmd-cs/pom.xml @@ -45,11 +45,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-dart/pom.xml b/pmd-dart/pom.xml index f489c42d7c..e3b0d8482e 100644 --- a/pmd-dart/pom.xml +++ b/pmd-dart/pom.xml @@ -45,11 +45,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-fortran/pom.xml b/pmd-fortran/pom.xml index f5926fc80b..4a94623d7c 100644 --- a/pmd-fortran/pom.xml +++ b/pmd-fortran/pom.xml @@ -35,11 +35,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-go/pom.xml b/pmd-go/pom.xml index 2c65c6a915..311df23eeb 100644 --- a/pmd-go/pom.xml +++ b/pmd-go/pom.xml @@ -43,11 +43,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-groovy/pom.xml b/pmd-groovy/pom.xml index e3afe3ef74..969a99e55e 100644 --- a/pmd-groovy/pom.xml +++ b/pmd-groovy/pom.xml @@ -40,11 +40,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-java/pom.xml b/pmd-java/pom.xml index 41a7476b9a..f3e4264c30 100644 --- a/pmd-java/pom.xml +++ b/pmd-java/pom.xml @@ -149,11 +149,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - com.github.stefanbirkner @@ -176,11 +171,6 @@ tree-printers test - - io.kotlintest - kotlintest-runner-junit5 - test - io.kotlintest kotlintest-assertions diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTCatchStatementTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTCatchStatementTest.kt index 80eb0b2c3a..8c4d34181f 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTCatchStatementTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTCatchStatementTest.kt @@ -3,7 +3,6 @@ package net.sourceforge.pmd.lang.java.ast import io.kotlintest.matchers.collections.shouldContainExactly import io.kotlintest.should import io.kotlintest.shouldBe -import io.kotlintest.specs.FunSpec import net.sourceforge.pmd.lang.java.ast.JavaVersion.* import net.sourceforge.pmd.lang.java.ast.JavaVersion.Companion.Earliest import net.sourceforge.pmd.lang.java.ast.JavaVersion.Companion.Latest diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ParserTestSpec.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ParserTestSpec.kt index d228c5d0d1..741b2bff65 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ParserTestSpec.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ParserTestSpec.kt @@ -3,7 +3,6 @@ package net.sourceforge.pmd.lang.java.ast import io.kotlintest.AbstractSpec import io.kotlintest.TestContext import io.kotlintest.TestType -import io.kotlintest.specs.IntelliMarker import net.sourceforge.pmd.lang.ast.test.Assertions import io.kotlintest.should as kotlintestShould @@ -15,7 +14,7 @@ import io.kotlintest.should as kotlintestShould * * @author Clément Fournier */ -abstract class ParserTestSpec(body: ParserTestSpec.() -> Unit) : AbstractSpec(), IntelliMarker { +abstract class ParserTestSpec(body: ParserTestSpec.() -> Unit) : AbstractSpec() { init { body() diff --git a/pmd-javascript/pom.xml b/pmd-javascript/pom.xml index 706f3d09cc..9bdb9e9bc8 100644 --- a/pmd-javascript/pom.xml +++ b/pmd-javascript/pom.xml @@ -94,11 +94,6 @@ pmd-test test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-lang-test diff --git a/pmd-jsp/pom.xml b/pmd-jsp/pom.xml index 554324da5d..860bf37c85 100644 --- a/pmd-jsp/pom.xml +++ b/pmd-jsp/pom.xml @@ -82,11 +82,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-kotlin/pom.xml b/pmd-kotlin/pom.xml index 09c881677c..5fa13a4a8e 100644 --- a/pmd-kotlin/pom.xml +++ b/pmd-kotlin/pom.xml @@ -45,11 +45,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-lang-test/pom.xml b/pmd-lang-test/pom.xml index 077910a59e..6fa4a14da8 100644 --- a/pmd-lang-test/pom.xml +++ b/pmd-lang-test/pom.xml @@ -156,12 +156,6 @@ compile - - io.kotlintest - kotlintest-runner-junit5 - compile - - com.github.oowekyala.treeutils tree-matchers diff --git a/pmd-lua/pom.xml b/pmd-lua/pom.xml index 2bc6f0754f..827030946b 100644 --- a/pmd-lua/pom.xml +++ b/pmd-lua/pom.xml @@ -45,11 +45,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-matlab/pom.xml b/pmd-matlab/pom.xml index 0b71aad03e..8b1824078c 100644 --- a/pmd-matlab/pom.xml +++ b/pmd-matlab/pom.xml @@ -76,11 +76,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-modelica/pom.xml b/pmd-modelica/pom.xml index 02fb539322..3c3f5de689 100644 --- a/pmd-modelica/pom.xml +++ b/pmd-modelica/pom.xml @@ -83,11 +83,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-lang-test @@ -124,11 +119,6 @@ annotations test - - io.kotlintest - kotlintest-runner-junit5 - test - io.kotlintest kotlintest-assertions @@ -139,7 +129,5 @@ kotlintest-core test - - diff --git a/pmd-modelica/src/test/kotlin/net/sourceforge/pmd/lang/modelica/ast/ModelicaCoordsTest.kt b/pmd-modelica/src/test/kotlin/net/sourceforge/pmd/lang/modelica/ast/ModelicaCoordsTest.kt index 6ad15f5bb4..dfa62e29b5 100644 --- a/pmd-modelica/src/test/kotlin/net/sourceforge/pmd/lang/modelica/ast/ModelicaCoordsTest.kt +++ b/pmd-modelica/src/test/kotlin/net/sourceforge/pmd/lang/modelica/ast/ModelicaCoordsTest.kt @@ -6,14 +6,14 @@ package net.sourceforge.pmd.lang.modelica.ast import io.kotlintest.should import io.kotlintest.shouldBe -import io.kotlintest.specs.FunSpec +import io.kotlintest.specs.AbstractFunSpec import net.sourceforge.pmd.lang.LanguageRegistry import net.sourceforge.pmd.lang.ast.Node import net.sourceforge.pmd.lang.ast.test.matchNode import net.sourceforge.pmd.lang.ast.test.shouldBe import java.io.StringReader -class ModelicaCoordsTest : FunSpec({ +class ModelicaCoordsTest : AbstractFunSpec({ test("Test line/column numbers for implicit nodes") { diff --git a/pmd-objectivec/pom.xml b/pmd-objectivec/pom.xml index dafc12b184..7606c1db5d 100644 --- a/pmd-objectivec/pom.xml +++ b/pmd-objectivec/pom.xml @@ -76,11 +76,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-perl/pom.xml b/pmd-perl/pom.xml index 55fb10ac2b..49cc40f80a 100644 --- a/pmd-perl/pom.xml +++ b/pmd-perl/pom.xml @@ -35,11 +35,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-php/pom.xml b/pmd-php/pom.xml index 3cf2f6bce6..26259bb0d6 100644 --- a/pmd-php/pom.xml +++ b/pmd-php/pom.xml @@ -35,11 +35,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-plsql/pom.xml b/pmd-plsql/pom.xml index 74f54637bb..334c8559dd 100644 --- a/pmd-plsql/pom.xml +++ b/pmd-plsql/pom.xml @@ -94,11 +94,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-python/pom.xml b/pmd-python/pom.xml index 42e44fa67e..c0bd0bb459 100644 --- a/pmd-python/pom.xml +++ b/pmd-python/pom.xml @@ -76,11 +76,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-ruby/pom.xml b/pmd-ruby/pom.xml index 7c70becc38..bcebcdcd0f 100644 --- a/pmd-ruby/pom.xml +++ b/pmd-ruby/pom.xml @@ -22,11 +22,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-scala-modules/pmd-scala-common/pom.xml b/pmd-scala-modules/pmd-scala-common/pom.xml index 751918cc7f..4304adf8c6 100644 --- a/pmd-scala-modules/pmd-scala-common/pom.xml +++ b/pmd-scala-modules/pmd-scala-common/pom.xml @@ -117,11 +117,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test @@ -144,11 +139,6 @@ tree-printers test - - io.kotlintest - kotlintest-runner-junit5 - test - io.kotlintest kotlintest-assertions diff --git a/pmd-scala-modules/pmd-scala-common/src/test/kotlin/net/sourceforge/pmd/lang/scala/ast/ScalaTreeTests.kt b/pmd-scala-modules/pmd-scala-common/src/test/kotlin/net/sourceforge/pmd/lang/scala/ast/ScalaTreeTests.kt index 21825ff9ea..32d808af26 100644 --- a/pmd-scala-modules/pmd-scala-common/src/test/kotlin/net/sourceforge/pmd/lang/scala/ast/ScalaTreeTests.kt +++ b/pmd-scala-modules/pmd-scala-common/src/test/kotlin/net/sourceforge/pmd/lang/scala/ast/ScalaTreeTests.kt @@ -5,14 +5,14 @@ package net.sourceforge.pmd.lang.scala.ast import io.kotlintest.should -import io.kotlintest.specs.FunSpec +import io.kotlintest.specs.AbstractFunSpec import net.sourceforge.pmd.lang.LanguageRegistry import net.sourceforge.pmd.lang.ast.Node import net.sourceforge.pmd.lang.ast.test.matchNode import net.sourceforge.pmd.lang.ast.test.shouldBe import java.io.StringReader -class ScalaTreeTests : FunSpec({ +class ScalaTreeTests : AbstractFunSpec({ test("Test line/column numbers") { diff --git a/pmd-swift/pom.xml b/pmd-swift/pom.xml index 20c1afe4b7..f33680c761 100644 --- a/pmd-swift/pom.xml +++ b/pmd-swift/pom.xml @@ -45,11 +45,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-visualforce/pom.xml b/pmd-visualforce/pom.xml index b9687e9df8..c5fb203ea0 100644 --- a/pmd-visualforce/pom.xml +++ b/pmd-visualforce/pom.xml @@ -82,11 +82,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pmd-xml/pom.xml b/pmd-xml/pom.xml index bbdf4568fc..7632c6e37d 100644 --- a/pmd-xml/pom.xml +++ b/pmd-xml/pom.xml @@ -53,11 +53,6 @@ junit test - - org.junit.vintage - junit-vintage-engine - test - net.sourceforge.pmd pmd-test diff --git a/pom.xml b/pom.xml index bc26acb457..aa7034af50 100644 --- a/pom.xml +++ b/pom.xml @@ -86,11 +86,12 @@ ${maven.compiler.test.target} 1.3.0 + 3.1.8 0.10.1 5.0 - 2.22.1 + 3.0.0-M5 8.30 3.1.1 3.13.0 @@ -259,6 +260,20 @@ ${project.build.testResources[0].directory} + + + + org.junit.vintage + junit-vintage-engine + 5.6.2 + + + + io.kotlintest + kotlintest-runner-junit5 + ${kotlintest.version} + + org.codehaus.mojo @@ -775,40 +790,6 @@ test - - - org.junit.jupiter - junit-jupiter-api - 5.5.0 - test - - - org.junit.jupiter - junit-jupiter-engine - 5.5.0 - test - - - - org.junit.platform - junit-platform-commons - 1.5.0 - - - org.junit.platform - junit-platform-launcher - 1.5.0 - test - - - - - org.junit.vintage - junit-vintage-engine - 5.5.0 - test - - org.jetbrains.kotlin @@ -841,22 +822,16 @@ test - - io.kotlintest - kotlintest-runner-junit5 - 3.1.8 - test - io.kotlintest kotlintest-assertions - 3.1.8 + ${kotlintest.version} test io.kotlintest kotlintest-core - 3.1.8 + ${kotlintest.version} test From fc3ee5375bff103b71297f4903c365e0b0bc6949 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 17 Jul 2020 12:05:06 +0200 Subject: [PATCH 96/99] [doc] Update release notes, fixes #710 --- docs/pages/release_notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index b8f8783555..6f50ac3043 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -15,6 +15,8 @@ This is a {{ site.pmd.release_type }} release. ### New and noteworthy ### Fixed Issues +* core + * [#710](https://github.com/pmd/pmd/issues/710): \[core] Review used dependencies ### API Changes From 7ae424a35bc2f893b9f12abdc30b8031663f899b Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 17 Jul 2020 14:43:43 +0200 Subject: [PATCH 97/99] Add deprecation javadocs --- .../net/sourceforge/pmd/lang/apex/ApexLanguageModule.java | 7 +++++-- .../pmd/lang/apex/rule/ApexRuleChainVisitor.java | 5 +++++ .../pmd/lang/internal/DefaultRulechainVisitor.java | 2 ++ .../java/net/sourceforge/pmd/lang/DummyLanguageModule.java | 6 ++++++ .../pmd/lang/java/rule/JavaRuleChainVisitor.java | 5 +++++ .../lang/ecmascript/rule/EcmascriptRuleChainVisitor.java | 5 +++++ .../sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java | 5 +++++ .../pmd/lang/modelica/rule/ModelicaRuleChainVisitor.java | 5 +++++ .../pmd/lang/plsql/rule/PLSQLRuleChainVisitor.java | 5 +++++ .../pmd/lang/scala/rule/ScalaRuleChainVisitor.java | 4 ++++ .../net/sourceforge/pmd/test/lang/DummyLanguageModule.java | 5 +++++ .../sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java | 5 +++++ .../sourceforge/pmd/lang/vm/rule/VmRuleChainVisitor.java | 5 +++++ .../sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java | 5 +++++ 14 files changed, 67 insertions(+), 2 deletions(-) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexLanguageModule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexLanguageModule.java index 949800ceab..3d37916f88 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexLanguageModule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexLanguageModule.java @@ -5,17 +5,20 @@ package net.sourceforge.pmd.lang.apex; import net.sourceforge.pmd.lang.BaseLanguageModule; +import net.sourceforge.pmd.util.CollectionUtil; import apex.jorje.services.Version; public class ApexLanguageModule extends BaseLanguageModule { + private static final String FIRST_EXTENSION = "cls"; + private static final String[] REMAINING_EXTENSIONS = {"trigger"}; public static final String NAME = "Apex"; public static final String TERSE_NAME = "apex"; - public static final String[] EXTENSIONS = { "cls", "trigger" }; + public static final String[] EXTENSIONS = CollectionUtil.listOf(FIRST_EXTENSION, REMAINING_EXTENSIONS).toArray(new String[0]); public ApexLanguageModule() { - super(NAME, null, TERSE_NAME, "cls", "trigger"); + super(NAME, null, TERSE_NAME, FIRST_EXTENSION, REMAINING_EXTENSIONS); addVersion(String.valueOf((int) Version.CURRENT.getExternal()), new ApexHandler(), true); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleChainVisitor.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleChainVisitor.java index 062c3e8d3a..341eca9851 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleChainVisitor.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleChainVisitor.java @@ -14,8 +14,13 @@ import net.sourceforge.pmd.lang.apex.ast.ApexNode; import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitor; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.lang.rule.RuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +/** + * @deprecated for removal with PMD 7. A language dependent rule chain visitor is not needed anymore. + * See {@link RuleChainVisitor}. + */ @Deprecated public class ApexRuleChainVisitor extends AbstractRuleChainVisitor { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/internal/DefaultRulechainVisitor.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/internal/DefaultRulechainVisitor.java index 9ca3d94d46..6cc7897885 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/internal/DefaultRulechainVisitor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/internal/DefaultRulechainVisitor.java @@ -9,6 +9,7 @@ import java.util.List; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; import net.sourceforge.pmd.lang.rule.RuleChainVisitor; @@ -17,6 +18,7 @@ import net.sourceforge.pmd.lang.rule.RuleChainVisitor; * @deprecated See {@link RuleChainVisitor} */ @Deprecated +@InternalApi public class DefaultRulechainVisitor extends AbstractRuleChainVisitor { @Override diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java index 559d26ec0d..491a0854cb 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java @@ -24,6 +24,7 @@ import net.sourceforge.pmd.lang.ast.xpath.DocumentNavigator; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; import net.sourceforge.pmd.lang.rule.AbstractRuleViolationFactory; import net.sourceforge.pmd.lang.rule.ParametricRuleViolation; +import net.sourceforge.pmd.lang.rule.RuleChainVisitor; import net.sf.saxon.expr.XPathContext; import net.sf.saxon.sxpath.IndependentContext; @@ -49,6 +50,11 @@ public class DummyLanguageModule extends BaseLanguageModule { addVersion("1.8", new Handler(), "8"); } + /** + * @deprecated for removal with PMD 7. A language dependent rule chain visitor is not needed anymore. + * See {@link RuleChainVisitor}. + */ + @Deprecated public static class DummyRuleChainVisitor extends AbstractRuleChainVisitor { @Override protected void visit(Rule rule, Node node, RuleContext ctx) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleChainVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleChainVisitor.java index 40fd701e69..bfd1bc2ccc 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleChainVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleChainVisitor.java @@ -14,8 +14,13 @@ import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitor; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.lang.rule.RuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +/** + * @deprecated for removal with PMD 7. A language dependent rule chain visitor is not needed anymore. + * See {@link RuleChainVisitor}. + */ @Deprecated public class JavaRuleChainVisitor extends AbstractRuleChainVisitor { diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleChainVisitor.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleChainVisitor.java index 2c30f8a51b..bb4ddfb8df 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleChainVisitor.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleChainVisitor.java @@ -14,8 +14,13 @@ import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ecmascript.ast.EcmascriptNode; import net.sourceforge.pmd.lang.ecmascript.ast.EcmascriptParserVisitor; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.lang.rule.RuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +/** + * @deprecated for removal with PMD 7. A language dependent rule chain visitor is not needed anymore. + * See {@link RuleChainVisitor}. + */ @Deprecated public class EcmascriptRuleChainVisitor extends AbstractRuleChainVisitor { diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java index 0d289da074..ed7580a7c8 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java @@ -14,8 +14,13 @@ import net.sourceforge.pmd.lang.jsp.ast.JspNode; import net.sourceforge.pmd.lang.jsp.ast.JspParserVisitor; import net.sourceforge.pmd.lang.jsp.ast.JspParserVisitorAdapter; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.lang.rule.RuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +/** + * @deprecated for removal with PMD 7. A language dependent rule chain visitor is not needed anymore. + * See {@link RuleChainVisitor}. + */ @Deprecated public class JspRuleChainVisitor extends AbstractRuleChainVisitor { diff --git a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/ModelicaRuleChainVisitor.java b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/ModelicaRuleChainVisitor.java index 24ee97f68d..140892eaa8 100644 --- a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/ModelicaRuleChainVisitor.java +++ b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/ModelicaRuleChainVisitor.java @@ -14,8 +14,13 @@ import net.sourceforge.pmd.lang.modelica.ast.ModelicaNode; import net.sourceforge.pmd.lang.modelica.ast.ModelicaParserVisitor; import net.sourceforge.pmd.lang.modelica.ast.ModelicaParserVisitorAdapter; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.lang.rule.RuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +/** + * @deprecated for removal with PMD 7. A language dependent rule chain visitor is not needed anymore. + * See {@link RuleChainVisitor}. + */ @Deprecated public class ModelicaRuleChainVisitor extends AbstractRuleChainVisitor { @Override diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/PLSQLRuleChainVisitor.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/PLSQLRuleChainVisitor.java index cb66c26269..24b58121b2 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/PLSQLRuleChainVisitor.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/PLSQLRuleChainVisitor.java @@ -16,8 +16,13 @@ import net.sourceforge.pmd.lang.plsql.ast.PLSQLNode; import net.sourceforge.pmd.lang.plsql.ast.PLSQLParserVisitor; import net.sourceforge.pmd.lang.plsql.ast.PLSQLParserVisitorAdapter; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.lang.rule.RuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +/** + * @deprecated for removal with PMD 7. A language dependent rule chain visitor is not needed anymore. + * See {@link RuleChainVisitor}. + */ @Deprecated public class PLSQLRuleChainVisitor extends AbstractRuleChainVisitor { private static final Logger LOGGER = Logger.getLogger(PLSQLRuleChainVisitor.class.getName()); diff --git a/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/rule/ScalaRuleChainVisitor.java b/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/rule/ScalaRuleChainVisitor.java index 317809ce79..b700f92deb 100644 --- a/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/rule/ScalaRuleChainVisitor.java +++ b/pmd-scala-modules/pmd-scala-common/src/main/java/net/sourceforge/pmd/lang/scala/rule/ScalaRuleChainVisitor.java @@ -10,6 +10,7 @@ import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.lang.rule.RuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; import net.sourceforge.pmd.lang.scala.ast.ASTSource; import net.sourceforge.pmd.lang.scala.ast.ScalaNode; @@ -18,6 +19,9 @@ import net.sourceforge.pmd.lang.scala.ast.ScalaParserVisitorAdapter; /** * A Rule Chain visitor for Scala. + * + * @deprecated for removal with PMD 7. A language dependent rule chain visitor is not needed anymore. + * See {@link RuleChainVisitor}. */ @Deprecated public class ScalaRuleChainVisitor extends AbstractRuleChainVisitor { diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java index a2bb003882..9ca7d14606 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java @@ -25,6 +25,7 @@ import net.sourceforge.pmd.lang.ast.ParseException; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; import net.sourceforge.pmd.lang.rule.AbstractRuleViolationFactory; import net.sourceforge.pmd.lang.rule.ParametricRuleViolation; +import net.sourceforge.pmd.lang.rule.RuleChainVisitor; import net.sourceforge.pmd.test.lang.ast.DummyNode; /** @@ -48,6 +49,10 @@ public class DummyLanguageModule extends BaseLanguageModule { addVersion("1.8", new Handler(), false); } + /** + * @deprecated for removal with PMD 7. A language dependent rule chain visitor is not needed anymore. + * See {@link RuleChainVisitor}. + */ @Deprecated public static class DummyRuleChainVisitor extends AbstractRuleChainVisitor { @Override diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java index 71514756ac..3c511ecfe6 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java @@ -10,12 +10,17 @@ import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.lang.rule.RuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; import net.sourceforge.pmd.lang.vf.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.vf.ast.VfNode; import net.sourceforge.pmd.lang.vf.ast.VfParserVisitor; import net.sourceforge.pmd.lang.vf.ast.VfParserVisitorAdapter; +/** + * @deprecated for removal with PMD 7. A language dependent rule chain visitor is not needed anymore. + * See {@link RuleChainVisitor}. + */ @Deprecated public class VfRuleChainVisitor extends AbstractRuleChainVisitor { diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/VmRuleChainVisitor.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/VmRuleChainVisitor.java index 8f5070cbf1..f7a7c8e0b0 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/VmRuleChainVisitor.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/VmRuleChainVisitor.java @@ -10,6 +10,7 @@ import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.lang.rule.RuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; import net.sourceforge.pmd.lang.vm.ast.ASTprocess; import net.sourceforge.pmd.lang.vm.ast.AbstractVmNode; @@ -17,6 +18,10 @@ import net.sourceforge.pmd.lang.vm.ast.VmNode; import net.sourceforge.pmd.lang.vm.ast.VmParserVisitor; import net.sourceforge.pmd.lang.vm.ast.VmParserVisitorAdapter; +/** + * @deprecated for removal with PMD 7. A language dependent rule chain visitor is not needed anymore. + * See {@link RuleChainVisitor}. + */ @Deprecated public class VmRuleChainVisitor extends AbstractRuleChainVisitor { diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java index a1f97cc043..aa928d6923 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java @@ -12,8 +12,13 @@ import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.lang.rule.RuleChainVisitor; import net.sourceforge.pmd.lang.rule.XPathRule; +/** + * @deprecated for removal with PMD 7. A language dependent rule chain visitor is not needed anymore. + * See {@link RuleChainVisitor}. + */ @Deprecated public class XmlRuleChainVisitor extends AbstractRuleChainVisitor { From e4690cb56a7666744637e7eedff0c547b618fad7 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 17 Jul 2020 14:57:43 +0200 Subject: [PATCH 98/99] Fix compile errors for CPD only languages --- .../sourceforge/pmd/lang/LanguageRegistry.java | 11 ++++++++++- .../pmd/lang/cpp/CppLanguageModule.java | 2 +- .../pmd/lang/cs/CsLanguageModule.java | 2 +- .../pmd/lang/fortran/FortranLanguageModule.java | 2 +- .../pmd/lang/go/GoLanguageModule.java | 2 +- .../pmd/lang/groovy/GroovyLanguageModule.java | 2 +- .../pmd/lang/matlab/MatlabLanguageModule.java | 2 +- .../objectivec/ObjectiveCLanguageModule.java | 2 +- .../pmd/lang/php/PhpLanguageModule.java | 2 +- .../pmd/lang/python/PythonLanguageModule.java | 2 +- .../pmd/lang/ruby/RubyLanguageModule.java | 2 +- .../pmd/lang/swift/SwiftLanguageModule.java | 2 +- .../pmd/AbstractLanguageVersionTest.java | 17 ++++++++++++++++- 13 files changed, 37 insertions(+), 13 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java index 5241e53592..518231c756 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java @@ -71,7 +71,16 @@ public final class LanguageRegistry { // TODO This is unnecessary, if the incomplete language modules have been removed. List languages = new ArrayList<>(); for (Language language : getInstance().languages.values()) { - if (language.getRuleChainVisitorClass() != null) { + LanguageVersionHandler languageVersionHandler = language.getDefaultVersion().getLanguageVersionHandler(); + boolean pmdSupported = false; + + if (languageVersionHandler != null) { + ParserOptions defaultParserOptions = languageVersionHandler.getDefaultParserOptions(); + Parser parser = languageVersionHandler.getParser(defaultParserOptions); + pmdSupported = parser.canParse(); + } + + if (pmdSupported) { languages.add(language); } } diff --git a/pmd-cpp/src/main/java/net/sourceforge/pmd/lang/cpp/CppLanguageModule.java b/pmd-cpp/src/main/java/net/sourceforge/pmd/lang/cpp/CppLanguageModule.java index 6cd9a34eb5..3f0cdf06a0 100644 --- a/pmd-cpp/src/main/java/net/sourceforge/pmd/lang/cpp/CppLanguageModule.java +++ b/pmd-cpp/src/main/java/net/sourceforge/pmd/lang/cpp/CppLanguageModule.java @@ -24,7 +24,7 @@ public class CppLanguageModule extends BaseLanguageModule { * extensions for C++. */ public CppLanguageModule() { - super(NAME, null, TERSE_NAME, null, "h", "c", "cpp", "cxx", "cc", "C"); + super(NAME, null, TERSE_NAME, "h", "c", "cpp", "cxx", "cc", "C"); addVersion("", new CppHandler(), true); } } diff --git a/pmd-cs/src/main/java/net/sourceforge/pmd/lang/cs/CsLanguageModule.java b/pmd-cs/src/main/java/net/sourceforge/pmd/lang/cs/CsLanguageModule.java index c90fd57029..d0f8a08f38 100644 --- a/pmd-cs/src/main/java/net/sourceforge/pmd/lang/cs/CsLanguageModule.java +++ b/pmd-cs/src/main/java/net/sourceforge/pmd/lang/cs/CsLanguageModule.java @@ -23,7 +23,7 @@ public class CsLanguageModule extends BaseLanguageModule { * Create a new instance of C# Language Module. */ public CsLanguageModule() { - super(NAME, null, TERSE_NAME, null, "cs"); + super(NAME, null, TERSE_NAME, "cs"); addVersion("", null, true); } } diff --git a/pmd-fortran/src/main/java/net/sourceforge/pmd/lang/fortran/FortranLanguageModule.java b/pmd-fortran/src/main/java/net/sourceforge/pmd/lang/fortran/FortranLanguageModule.java index 48ac68a69f..744559a948 100644 --- a/pmd-fortran/src/main/java/net/sourceforge/pmd/lang/fortran/FortranLanguageModule.java +++ b/pmd-fortran/src/main/java/net/sourceforge/pmd/lang/fortran/FortranLanguageModule.java @@ -23,7 +23,7 @@ public class FortranLanguageModule extends BaseLanguageModule { * Creates a new instance of {@link FortranLanguageModule} */ public FortranLanguageModule() { - super(NAME, null, TERSE_NAME, null, "for", "f", "f66", "f77", "f90"); + super(NAME, null, TERSE_NAME, "for", "f", "f66", "f77", "f90"); addVersion("", null, true); } diff --git a/pmd-go/src/main/java/net/sourceforge/pmd/lang/go/GoLanguageModule.java b/pmd-go/src/main/java/net/sourceforge/pmd/lang/go/GoLanguageModule.java index 9c35be150d..754adb768b 100644 --- a/pmd-go/src/main/java/net/sourceforge/pmd/lang/go/GoLanguageModule.java +++ b/pmd-go/src/main/java/net/sourceforge/pmd/lang/go/GoLanguageModule.java @@ -20,7 +20,7 @@ public class GoLanguageModule extends BaseLanguageModule { * Create a new instance of Golang Language Module. */ public GoLanguageModule() { - super(NAME, null, TERSE_NAME, null, "go"); + super(NAME, null, TERSE_NAME, "go"); addVersion("1", null, true); } } diff --git a/pmd-groovy/src/main/java/net/sourceforge/pmd/lang/groovy/GroovyLanguageModule.java b/pmd-groovy/src/main/java/net/sourceforge/pmd/lang/groovy/GroovyLanguageModule.java index 02149c8db5..57c6f0ed0d 100644 --- a/pmd-groovy/src/main/java/net/sourceforge/pmd/lang/groovy/GroovyLanguageModule.java +++ b/pmd-groovy/src/main/java/net/sourceforge/pmd/lang/groovy/GroovyLanguageModule.java @@ -23,7 +23,7 @@ public class GroovyLanguageModule extends BaseLanguageModule { * Create a new instance of Groovy Language Module. */ public GroovyLanguageModule() { - super(NAME, null, TERSE_NAME, null, "groovy"); + super(NAME, null, TERSE_NAME, "groovy"); addVersion("", null, true); } } diff --git a/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabLanguageModule.java b/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabLanguageModule.java index 8c310584a6..6ce5e806ba 100644 --- a/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabLanguageModule.java +++ b/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabLanguageModule.java @@ -24,7 +24,7 @@ public class MatlabLanguageModule extends BaseLanguageModule { * file extensions for Matlab. */ public MatlabLanguageModule() { - super(NAME, null, TERSE_NAME, null, "m"); + super(NAME, null, TERSE_NAME, "m"); addVersion("", new MatlabHandler(), true); } } diff --git a/pmd-objectivec/src/main/java/net/sourceforge/pmd/lang/objectivec/ObjectiveCLanguageModule.java b/pmd-objectivec/src/main/java/net/sourceforge/pmd/lang/objectivec/ObjectiveCLanguageModule.java index 51612b04ff..e78c7ee653 100644 --- a/pmd-objectivec/src/main/java/net/sourceforge/pmd/lang/objectivec/ObjectiveCLanguageModule.java +++ b/pmd-objectivec/src/main/java/net/sourceforge/pmd/lang/objectivec/ObjectiveCLanguageModule.java @@ -24,7 +24,7 @@ public class ObjectiveCLanguageModule extends BaseLanguageModule { * default file extensions for Objective-C. */ public ObjectiveCLanguageModule() { - super(NAME, null, TERSE_NAME, null, "h", "m"); + super(NAME, null, TERSE_NAME, "h", "m"); addVersion("", new ObjectiveCHandler(), true); } } diff --git a/pmd-php/src/main/java/net/sourceforge/pmd/lang/php/PhpLanguageModule.java b/pmd-php/src/main/java/net/sourceforge/pmd/lang/php/PhpLanguageModule.java index 010a7c542a..b08fac51ad 100644 --- a/pmd-php/src/main/java/net/sourceforge/pmd/lang/php/PhpLanguageModule.java +++ b/pmd-php/src/main/java/net/sourceforge/pmd/lang/php/PhpLanguageModule.java @@ -23,7 +23,7 @@ public class PhpLanguageModule extends BaseLanguageModule { * Create a new instance of the PHP Language Module. */ public PhpLanguageModule() { - super(NAME, "PHP", TERSE_NAME, null, "php", "class"); + super(NAME, "PHP", TERSE_NAME, "php", "class"); addVersion("", null, true); } diff --git a/pmd-python/src/main/java/net/sourceforge/pmd/lang/python/PythonLanguageModule.java b/pmd-python/src/main/java/net/sourceforge/pmd/lang/python/PythonLanguageModule.java index 3003d53389..e6185cbf06 100644 --- a/pmd-python/src/main/java/net/sourceforge/pmd/lang/python/PythonLanguageModule.java +++ b/pmd-python/src/main/java/net/sourceforge/pmd/lang/python/PythonLanguageModule.java @@ -24,7 +24,7 @@ public class PythonLanguageModule extends BaseLanguageModule { * file extensions for Python. */ public PythonLanguageModule() { - super(NAME, null, TERSE_NAME, null, "py"); + super(NAME, null, TERSE_NAME, "py"); addVersion("", new PythonHandler(), true); } } diff --git a/pmd-ruby/src/main/java/net/sourceforge/pmd/lang/ruby/RubyLanguageModule.java b/pmd-ruby/src/main/java/net/sourceforge/pmd/lang/ruby/RubyLanguageModule.java index 36ec4680e2..a37bd74a9d 100644 --- a/pmd-ruby/src/main/java/net/sourceforge/pmd/lang/ruby/RubyLanguageModule.java +++ b/pmd-ruby/src/main/java/net/sourceforge/pmd/lang/ruby/RubyLanguageModule.java @@ -23,7 +23,7 @@ public class RubyLanguageModule extends BaseLanguageModule { * Creates a new Ruby Language Module instance. */ public RubyLanguageModule() { - super(NAME, null, TERSE_NAME, null, "rb", "cgi", "class"); + super(NAME, null, TERSE_NAME, "rb", "cgi", "class"); addVersion("", null, true); } } diff --git a/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftLanguageModule.java b/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftLanguageModule.java index 76fa53d5a6..3c108b6919 100644 --- a/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftLanguageModule.java +++ b/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftLanguageModule.java @@ -23,7 +23,7 @@ public class SwiftLanguageModule extends BaseLanguageModule { * Create a new instance of Swift Language Module. */ public SwiftLanguageModule() { - super(NAME, null, TERSE_NAME, null, "swift"); + super(NAME, null, TERSE_NAME, "swift"); addVersion("", null, true); } } diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/AbstractLanguageVersionTest.java b/pmd-test/src/main/java/net/sourceforge/pmd/AbstractLanguageVersionTest.java index 2b37182ef2..d181cbf529 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/AbstractLanguageVersionTest.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/AbstractLanguageVersionTest.java @@ -19,6 +19,9 @@ import net.sourceforge.pmd.ant.SourceLanguage; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.LanguageVersionHandler; +import net.sourceforge.pmd.lang.Parser; +import net.sourceforge.pmd.lang.ParserOptions; import net.sourceforge.pmd.util.ResourceLoader; /** @@ -103,6 +106,18 @@ public class AbstractLanguageVersionTest { assertEquals(expected, languageVersion); } + private boolean supportsRules() { + if (expected == null || expected.getLanguage().getRuleChainVisitorClass() == null + || expected.getLanguageVersionHandler() == null) { + return false; + } + + LanguageVersionHandler languageVersionHandler = expected.getLanguageVersionHandler(); + ParserOptions defaultParserOptions = languageVersionHandler.getDefaultParserOptions(); + Parser parser = languageVersionHandler.getParser(defaultParserOptions); + return parser.canParse(); + } + /** * Makes sure, that for each language a "categories.properties" file exists. * @@ -112,7 +127,7 @@ public class AbstractLanguageVersionTest { @Test public void testRegisteredRulesets() throws Exception { // only check for languages, that support rules - if (expected == null || expected.getLanguage().getRuleChainVisitorClass() == null) { + if (!supportsRules()) { return; } From 666621e95847169c701b74dbb8c1327646ef1520 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 17 Jul 2020 15:16:29 +0200 Subject: [PATCH 99/99] Fix checkstyle --- .../pmd/lang/ecmascript/EcmascriptLanguageModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/EcmascriptLanguageModule.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/EcmascriptLanguageModule.java index 7614fa35fd..b3008396fa 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/EcmascriptLanguageModule.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/EcmascriptLanguageModule.java @@ -15,7 +15,7 @@ public class EcmascriptLanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "ecmascript"; public EcmascriptLanguageModule() { - super(NAME, null, TERSE_NAME,"js"); + super(NAME, null, TERSE_NAME, "js"); addVersion("3", new Ecmascript3Handler(), true); }