diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..d674a5bc29
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,13 @@
+version: 2
+updates:
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ target-branch: "master"
+ open-pull-requests-limit: 0
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ target-branch: "master"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000000..c4c6346b0a
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,29 @@
+name: Java CI
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ continue-on-error: ${{ matrix.experimental }}
+ strategy:
+ matrix:
+ os: [ ubuntu-latest , windows-latest , macos-latest ]
+ java: [ 11 ]
+ experimental: [ false ]
+
+ steps:
+ - uses: actions/checkout@v2.3.2
+ - uses: actions/cache@v2
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Set up JDK ${{ matrix.java }}
+ uses: actions/setup-java@v1.4.0
+ with:
+ java-version: ${{ matrix.java }}
+ - name: Build with mvnw
+ run: |
+ ./mvnw clean install
diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md
index a0c5c59808..30c35733f5 100644
--- a/docs/pages/release_notes.md
+++ b/docs/pages/release_notes.md
@@ -22,14 +22,26 @@ This is a {{ site.pmd.release_type }} release.
### Fixed Issues
* pmd-java
- * [#2708](https://github.com/pmd/pmd/pull/2708): \[java] False positive FinalFieldCouldBeStatic when using lombok Builder.Default
+ * [#2708](https://github.com/pmd/pmd/issues/2708): \[java] False positive FinalFieldCouldBeStatic when using lombok Builder.Default
+ * [#2738](https://github.com/pmd/pmd/issues/2738): \[java] Custom rule with @ExhaustiveEnumSwitch throws NPE
+ * [#2756](https://github.com/pmd/pmd/issues/2756): \[java] TypeTestUtil fails with NPE for anonymous class
+ * [#2759](https://github.com/pmd/pmd/issues/2759): \[java] False positive in UnusedAssignment
+ * [#2767](https://github.com/pmd/pmd/issues/2767): \[java] IndexOutOfBoundsException when parsing an initializer BlockStatement
### API Changes
+#### Deprecated API
+
+##### For removal
+
+* {% jdoc !!core::RuleViolationComparator %}. Use {% jdoc !!core::RuleViolation#DEFAULT_COMPARATOR %} instead.
+
### External Contributions
+* [#2735](https://github.com/pmd/pmd/pull/2735): \[ci] Add github actions for a fast view of pr succeed/not - [XenoAmess](https://github.com/XenoAmess)
* [#2747](https://github.com/pmd/pmd/pull/2747): \[java] Don't trigger FinalFieldCouldBeStatic when field is annotated with lombok @Builder.Default - [Ollie Abbey](https://github.com/ollieabbey)
+* [#2773](https://github.com/pmd/pmd/pull/2773): \[java] issue-2738: Adding null check to avoid npe when switch case is default - [Nimit Patel](https://github.com/nimit-patel)
{% endtocmaker %}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/Report.java b/pmd-core/src/main/java/net/sourceforge/pmd/Report.java
index 15508e1c0a..c24a3d642b 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/Report.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/Report.java
@@ -180,7 +180,7 @@ public class Report {
* @param violation the violation to add
*/
public void addRuleViolation(RuleViolation violation) {
- int index = Collections.binarySearch(violations, violation, RuleViolationComparator.INSTANCE);
+ int index = Collections.binarySearch(violations, violation, RuleViolation.DEFAULT_COMPARATOR);
violations.add(index < 0 ? -index - 1 : index, violation);
for (ThreadSafeReportListener listener : listeners) {
listener.ruleViolationAdded(violation);
@@ -228,7 +228,7 @@ public class Report {
suppressedRuleViolations.addAll(r.suppressedRuleViolations);
for (RuleViolation violation : r.getViolations()) {
- int index = Collections.binarySearch(violations, violation, RuleViolationComparator.INSTANCE);
+ int index = Collections.binarySearch(violations, violation, RuleViolation.DEFAULT_COMPARATOR);
violations.add(index < 0 ? -index - 1 : index, violation);
}
}
@@ -245,7 +245,7 @@ public class Report {
* Returns an unmodifiable list of violations that have been
* recorded until now. None of those violations were suppressed.
*
- *
The violations list is sorted with {@link RuleViolationComparator#INSTANCE}.
+ *
The violations list is sorted with {@link RuleViolation#DEFAULT_COMPARATOR}.
*/
public final List getViolations() {
return Collections.unmodifiableList(violations);
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/RuleViolation.java b/pmd-core/src/main/java/net/sourceforge/pmd/RuleViolation.java
index dc36455ba5..b832c7d8a3 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/RuleViolation.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/RuleViolation.java
@@ -4,6 +4,8 @@
package net.sourceforge.pmd;
+import java.util.Comparator;
+
/**
* A RuleViolation is created by a Rule when it identifies a violation of the
* Rule constraints. RuleViolations are simple data holders that are collected
@@ -16,6 +18,14 @@ package net.sourceforge.pmd;
*/
public interface RuleViolation {
+ /**
+ * A comparator for rule violations. This compares all exposed attributes
+ * of a violation, filename first. The remaining parameters are compared
+ * in an unspecified order.
+ */
+ // TODO in java 8 this can be a chained Comparator.comparing call
+ Comparator DEFAULT_COMPARATOR = RuleViolationComparator.INSTANCE;
+
/**
* Get the Rule which identified this violation.
*
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/RuleViolationComparator.java b/pmd-core/src/main/java/net/sourceforge/pmd/RuleViolationComparator.java
index bbfa48ed07..bc1b1adf39 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/RuleViolationComparator.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/RuleViolationComparator.java
@@ -17,7 +17,19 @@ import java.util.Comparator;
* End column
* Rule name
*
+ *
+ * TODO why is begin line/begin column split?? would make more sense to use
+ * - filename
+ * - begin line
+ * - begin column
+ * - description
+ * - rule name
+ * - end line
+ * - end column
+ *
+ * @deprecated Use {@link RuleViolation#DEFAULT_COMPARATOR}
*/
+@Deprecated
public final class RuleViolationComparator implements Comparator {
public static final RuleViolationComparator INSTANCE = new RuleViolationComparator();
diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationComparatorTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationComparatorTest.java
index 45d3571c5c..bc7eb32fe2 100644
--- a/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationComparatorTest.java
+++ b/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationComparatorTest.java
@@ -58,7 +58,7 @@ public class RuleViolationComparatorTest {
Collections.shuffle(ruleViolations, random);
// Sort
- Collections.sort(ruleViolations, RuleViolationComparator.INSTANCE);
+ Collections.sort(ruleViolations, RuleViolation.DEFAULT_COMPARATOR);
// Check
int count = 0;
diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationTest.java
index d9717e3e4c..c7c448823c 100644
--- a/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationTest.java
+++ b/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationTest.java
@@ -8,6 +8,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
+import java.util.Comparator;
import org.junit.Ignore;
import org.junit.Test;
@@ -51,7 +52,7 @@ public class RuleViolationTest {
@Test
public void testComparatorWithDifferentFilenames() {
Rule rule = new MockRule("name", "desc", "msg", "rulesetname");
- RuleViolationComparator comp = RuleViolationComparator.INSTANCE;
+ Comparator comp = RuleViolation.DEFAULT_COMPARATOR;
RuleContext ctx = new RuleContext();
ctx.setSourceCodeFile(new File("filename1"));
DummyNode s = new DummyNode();
@@ -68,7 +69,7 @@ public class RuleViolationTest {
@Test
public void testComparatorWithSameFileDifferentLines() {
Rule rule = new MockRule("name", "desc", "msg", "rulesetname");
- RuleViolationComparator comp = RuleViolationComparator.INSTANCE;
+ Comparator comp = RuleViolation.DEFAULT_COMPARATOR;
RuleContext ctx = new RuleContext();
ctx.setSourceCodeFile(new File("filename"));
DummyNode s = new DummyNode();
@@ -85,7 +86,7 @@ public class RuleViolationTest {
@Test
public void testComparatorWithSameFileSameLines() {
Rule rule = new MockRule("name", "desc", "msg", "rulesetname");
- RuleViolationComparator comp = RuleViolationComparator.INSTANCE;
+ Comparator comp = RuleViolation.DEFAULT_COMPARATOR;
RuleContext ctx = new RuleContext();
ctx.setSourceCodeFile(new File("filename"));
DummyNode s = new DummyNode();
diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt
index 88f2f9d6b3..438cb80ce2 100644
--- a/pmd-java/etc/grammar/Java.jjt
+++ b/pmd-java/etc/grammar/Java.jjt
@@ -1731,8 +1731,9 @@ void BlockStatement():
|
{
List annotations = new ArrayList();
- while (jjtree.peekNode() instanceof ASTAnnotation) {
- annotations.add((ASTAnnotation) jjtree.popNode());
+ if (jjtree.nodeArity() > 0) { // peekNode would throw if the stack is empty
+ while (jjtree.peekNode() instanceof ASTAnnotation) {
+ annotations.add((ASTAnnotation) jjtree.popNode());}
}
}
LocalVariableDeclaration()
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatement.java
index f0827016db..324775ae9f 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatement.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatement.java
@@ -79,7 +79,10 @@ public class ASTSwitchStatement extends AbstractJavaNode implements Iterable 0 && child.getChild(0) instanceof ASTAllocationExpression
|| child instanceof ASTPrimaryPrefix && child.getNumChildren() > 0 && child.getChild(0) instanceof ASTAllocationExpression) {
- state.abruptCompletionByThrow(true);
+ state.abruptCompletionByThrow(true); // this is a noop if we're outside a try block that has catch/finally
}
}
}
@@ -1380,11 +1375,6 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
return this;
}
- private boolean isInTryBlock() {
- // ignore resources, once they're initialized everything's fine in the block
- return !myCatches.isEmpty() || myFinally != null;
- }
-
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
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeTestUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeTestUtil.java
index 873697e422..a2d187c631 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeTestUtil.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeTestUtil.java
@@ -106,7 +106,11 @@ public final class TypeTestUtil {
final Class> clazz = loadClassWithNodeClassloader(node, canonicalName);
+
if (clazz != null) {
+ if (clazz.getCanonicalName() == null) {
+ return false; // no canonical name, give up: we shouldn't be able to access them
+ }
return clazz.isAssignableFrom(nodeType);
} else {
return fallbackIsA(node, canonicalName, true);
@@ -174,8 +178,16 @@ public final class TypeTestUtil {
}
- return node.getType() == null ? fallbackIsA(node, canonicalName, false)
- : node.getType().getCanonicalName().equals(canonicalName);
+ if (node.getType() == null) {
+ return fallbackIsA(node, canonicalName, false);
+ }
+
+ String canoname = node.getType().getCanonicalName();
+ if (canoname == null) {
+ // anonymous/local class, or class nested within one of those
+ return false;
+ }
+ return canoname.equals(canonicalName);
}
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatementTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatementTest.java
new file mode 100644
index 0000000000..5915a67c3d
--- /dev/null
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatementTest.java
@@ -0,0 +1,21 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.ast;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ASTSwitchStatementTest extends BaseParserTest {
+
+ @Test
+ public void exhaustiveEnumSwitchWithDefault() {
+ ASTSwitchStatement switchStatement = getNodes(ASTSwitchStatement.class,
+ "import java.nio.file.AccessMode; class Foo { void bar(AccessMode m) {"
+ + "switch (m) { case READ: break; default: break; } } }")
+ .get(0);
+ Assert.assertFalse(switchStatement.isExhaustiveEnumSwitch()); // this should not throw a NPE...
+ Assert.assertTrue(switchStatement.hasDefaultCase());
+ }
+}
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java
index 30799b2550..2ee0d72368 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java
@@ -172,6 +172,16 @@ public class ParserCornersTest {
java8.parseResource("GitHubBug207.java");
}
+ @Test
+ public void testGitHubBug2767() {
+ // PMD fails to parse an initializer block.
+ // PMD 6.26.0 parses this code just fine.
+ java.withDefaultVersion("15-preview")
+ .parse("class Foo {\n"
+ + " {final int I;}\n"
+ + "}\n");
+ }
+
@Test
public void testBug206() {
java8.parse("public @interface Foo {" + "\n"
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypeTestUtilTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypeTestUtilTest.java
index a04149a88c..d672afc702 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypeTestUtilTest.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypeTestUtilTest.java
@@ -6,6 +6,7 @@ package net.sourceforge.pmd.lang.java.types;
import java.io.Serializable;
import java.lang.annotation.Annotation;
+import java.util.concurrent.Callable;
import org.junit.Assert;
import org.junit.Rule;
@@ -13,6 +14,7 @@ import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import org.junit.rules.ExpectedException;
+import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
@@ -20,6 +22,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTMarkerAnnotation;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.TypeNode;
import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest;
+import net.sourceforge.pmd.lang.java.types.testdata.SomeClassWithAnon;
public class TypeTestUtilTest extends BaseNonParserTest {
@@ -74,6 +77,31 @@ public class TypeTestUtilTest extends BaseNonParserTest {
assertIsA(klass, Object.class);
}
+ @Test
+ public void testAnonClassTypeNPE() {
+ // #2756
+
+ ASTAllocationExpression anon =
+ java.parseClass(SomeClassWithAnon.class)
+ .getFirstDescendantOfType(ASTAllocationExpression.class);
+
+
+ Assert.assertNotNull("Type should be resolved", anon.getType());
+ Assert.assertTrue("Anon class", anon.isAnonymousClass());
+ Assert.assertTrue("Anon class", anon.getType().isAnonymousClass());
+ Assert.assertTrue("Should be a Runnable", TypeTestUtil.isA(Runnable.class, anon));
+
+ // This is not a canonical name, so we give up early
+ Assert.assertFalse(TypeTestUtil.isA(SomeClassWithAnon.class.getName() + "$1", anon));
+ Assert.assertFalse(TypeTestUtil.isExactlyA(SomeClassWithAnon.class.getName() + "$1", anon));
+
+ // this is the failure case: if the binary name doesn't match, we test the canoname, which was null
+ Assert.assertFalse(TypeTestUtil.isA(Callable.class, anon));
+ Assert.assertFalse(TypeTestUtil.isA(Callable.class.getCanonicalName(), anon));
+ Assert.assertFalse(TypeTestUtil.isExactlyA(Callable.class, anon));
+ Assert.assertFalse(TypeTestUtil.isExactlyA(Callable.class.getCanonicalName(), anon));
+ }
+
/**
* If we don't have the annotation on the classpath,
* we should resolve the full name via the import, if possible
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/SomeClassWithAnon.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/SomeClassWithAnon.java
new file mode 100644
index 0000000000..09d2467f25
--- /dev/null
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/SomeClassWithAnon.java
@@ -0,0 +1,25 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.types.testdata;
+
+/**
+ * #2756
+ */
+public class SomeClassWithAnon {
+
+ {
+ new Runnable() {
+
+ @Override
+ public void run() {
+
+ }
+ };
+
+
+ }
+
+
+}
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedAssignment.xml
index 1f0d55cebc..0dd7179c5e 100644
--- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedAssignment.xml
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedAssignment.xml
@@ -3159,4 +3159,29 @@ public class UnusedAssignmentNative {
]]>
+
+ False positive with method that may throw in forked state (the if state) #2759
+ 0
+ 0) {
+ a--;
+ try {
+ if (dummy) {
+ return somethingThatCanThrowRandomly(1);
+ } else {
+ return somethingThatCanThrowRandomly(2);
+ }
+ } catch (RuntimeException e) {
+ // retry
+ }
+ }
+ return 0;
+ }
+ }
+ ]]>
+
+