Merge branch 'master' into issue-2755

This commit is contained in:
Clément Fournier
2020-09-25 14:24:08 +02:00
8 changed files with 490 additions and 70 deletions

View File

@ -3,6 +3,7 @@
[![Join the chat at https://gitter.im/pmd/pmd](https://badges.gitter.im/pmd/pmd.svg)](https://gitter.im/pmd/pmd?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://travis-ci.org/pmd/pmd.svg?branch=master)](https://travis-ci.org/pmd/pmd)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.sourceforge.pmd/pmd/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.sourceforge.pmd/pmd)
[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-green?labelColor=blue)](https://github.com/jvm-repo-rebuild/reproducible-central#net.sourceforge.pmd:pmd)
[![Coverage Status](https://coveralls.io/repos/github/pmd/pmd/badge.svg)](https://coveralls.io/github/pmd/pmd)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/a674ee8642ed44c6ba7633626ee95967)](https://www.codacy.com/app/pmd/pmd?utm_source=github.com&utm_medium=referral&utm_content=pmd/pmd&utm_campaign=Badge_Grade)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](code_of_conduct.md)

View File

@ -29,14 +29,19 @@ AbstractTokenizer and the custom tokenizers of Fortran, Perl and Ruby are deprec
* [#2758](https://github.com/pmd/pmd/pull/2758): \[cpd] Improve AnyTokenizer
* [#2760](https://github.com/pmd/pmd/issues/2760): \[cpd] AnyTokenizer doesn't count columns correctly
* pmd-java
* [#2708](https://github.com/pmd/pmd/issues/2708): \[java] False positive FinalFieldCouldBeStatic when using lombok Builder.Default
* apex-security
* [#2774](https://github.com/pmd/pmd/issues/2774): \[apex] ApexSharingViolations does not correlate sharing settings with class that contains data access
* java
* [#2738](https://github.com/pmd/pmd/issues/2738): \[java] Custom rule with @ExhaustiveEnumSwitch throws NPE
* [#2755](https://github.com/pmd/pmd/issues/2755): \[java] \[6.27.0] Exception applying rule CloseResource on file ... java.lang.NullPointerException
* [#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
* [#2783](https://github.com/pmd/pmd/issues/2783): \[java] Error while parsing with lambda of custom interface
* java-bestpractices
* [#2759](https://github.com/pmd/pmd/issues/2759): \[java] False positive in UnusedAssignment
* java-design
* [#2708](https://github.com/pmd/pmd/issues/2708): \[java] False positive FinalFieldCouldBeStatic when using lombok Builder.Default
### API Changes
@ -69,6 +74,8 @@ AbstractTokenizer and the custom tokenizers of Fortran, Perl and Ruby are deprec
* [#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)
* [#2789](https://github.com/pmd/pmd/pull/2789): Add badge for reproducible build - [Dan Rollo](https://github.com/bhamail)
* [#2791](https://github.com/pmd/pmd/pull/2791): \[apex] Analyze inner classes for sharing violations - [Jeff Bartolotta](https://github.com/jbartolotta-sfdc)
{% endtocmaker %}

View File

@ -4,11 +4,20 @@
package net.sourceforge.pmd.lang.apex.rule.security;
import java.util.List;
import java.util.Optional;
import java.util.WeakHashMap;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.lang.apex.ast.ASTDmlDeleteStatement;
import net.sourceforge.pmd.lang.apex.ast.ASTDmlInsertStatement;
import net.sourceforge.pmd.lang.apex.ast.ASTDmlMergeStatement;
import net.sourceforge.pmd.lang.apex.ast.ASTDmlUndeleteStatement;
import net.sourceforge.pmd.lang.apex.ast.ASTDmlUpdateStatement;
import net.sourceforge.pmd.lang.apex.ast.ASTDmlUpsertStatement;
import net.sourceforge.pmd.lang.apex.ast.ASTMethodCallExpression;
import net.sourceforge.pmd.lang.apex.ast.ASTModifierNode;
import net.sourceforge.pmd.lang.apex.ast.ASTSoqlExpression;
import net.sourceforge.pmd.lang.apex.ast.ASTSoslExpression;
import net.sourceforge.pmd.lang.apex.ast.ASTUserClass;
import net.sourceforge.pmd.lang.apex.ast.ApexNode;
import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule;
@ -21,45 +30,103 @@ import net.sourceforge.pmd.lang.apex.rule.internal.Helper;
*/
public class ApexSharingViolationsRule extends AbstractApexRule {
/**
* Keep track of previously reported violations to avoid duplicates.
*/
private WeakHashMap<ApexNode<?>, Object> localCacheOfReportedNodes = new WeakHashMap<>();
public ApexSharingViolationsRule() {
setProperty(CODECLIMATE_CATEGORIES, "Security");
setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 100);
setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false);
addRuleChainVisit(ASTDmlDeleteStatement.class);
addRuleChainVisit(ASTDmlInsertStatement.class);
addRuleChainVisit(ASTDmlMergeStatement.class);
addRuleChainVisit(ASTDmlUndeleteStatement.class);
addRuleChainVisit(ASTDmlUpdateStatement.class);
addRuleChainVisit(ASTDmlUpsertStatement.class);
addRuleChainVisit(ASTMethodCallExpression.class);
addRuleChainVisit(ASTSoqlExpression.class);
addRuleChainVisit(ASTSoslExpression.class);
}
@Override
public Object visit(ASTUserClass node, Object data) {
if (Helper.isTestMethodOrClass(node) || Helper.isSystemLevelClass(node)) {
return data; // stops all the rules
}
if (!Helper.isTestMethodOrClass(node)) {
boolean sharingFound = isSharingPresent(node);
checkForSharingDeclaration(node, data, sharingFound);
checkForDatabaseMethods(node, data, sharingFound);
}
public void start(RuleContext ctx) {
super.start(ctx);
localCacheOfReportedNodes.clear();
}
@Override
public Object visit(ASTSoqlExpression node, Object data) {
checkForViolation(node, data);
return data;
}
/**
* Check if class contains any Database.query / Database.insert [ Database.*
* ] methods
*
* @param node
* @param data
*/
private void checkForDatabaseMethods(ASTUserClass node, Object data, boolean sharingFound) {
List<ASTMethodCallExpression> calls = node.findDescendantsOfType(ASTMethodCallExpression.class);
for (ASTMethodCallExpression call : calls) {
if (Helper.isMethodName(call, "Database", Helper.ANY_METHOD)) {
if (!sharingFound) {
reportViolation(node, data);
}
@Override
public Object visit(ASTSoslExpression node, Object data) {
checkForViolation(node, data);
return data;
}
@Override
public Object visit(ASTDmlUpsertStatement node, Object data) {
checkForViolation(node, data);
return data;
}
@Override
public Object visit(ASTDmlUpdateStatement node, Object data) {
checkForViolation(node, data);
return data;
}
@Override
public Object visit(ASTDmlUndeleteStatement node, Object data) {
checkForViolation(node, data);
return data;
}
@Override
public Object visit(ASTDmlMergeStatement node, Object data) {
checkForViolation(node, data);
return data;
}
@Override
public Object visit(ASTDmlInsertStatement node, Object data) {
checkForViolation(node, data);
return data;
}
@Override
public Object visit(ASTDmlDeleteStatement node, Object data) {
checkForViolation(node, data);
return data;
}
@Override
public Object visit(ASTMethodCallExpression node, Object data) {
if (Helper.isMethodName(node, "Database", Helper.ANY_METHOD)) {
checkForViolation(node, data);
}
return data;
}
private void checkForViolation(ApexNode<?> node, Object data) {
// The closest ASTUserClass class in the tree hierarchy is the node that requires the sharing declaration
ASTUserClass sharingDeclarationClass = node.getFirstParentOfType(ASTUserClass.class);
// This is null in the case of triggers
if (sharingDeclarationClass != null) {
// Apex allows a single level of class nesting. Check to see if sharingDeclarationClass has an outer class
ASTUserClass outerClass = sharingDeclarationClass.getFirstParentOfType(ASTUserClass.class);
// The test annotation needs to be on the outermost class
ASTUserClass testAnnotationClass = Optional.ofNullable(outerClass).orElse(sharingDeclarationClass);
if (!Helper.isTestMethodOrClass(testAnnotationClass) && !Helper.isSystemLevelClass(sharingDeclarationClass) && !isSharingPresent(sharingDeclarationClass)) {
// The violation is reported on the class, not the node that performs data access
reportViolation(sharingDeclarationClass, data);
}
}
}
@ -77,19 +144,6 @@ public class ApexSharingViolationsRule extends AbstractApexRule {
}
}
/**
* Check if class has no sharing declared
*
* @param node
* @param data
*/
private void checkForSharingDeclaration(ApexNode<?> node, Object data, boolean sharingFound) {
final boolean foundAnyDMLorSOQL = Helper.foundAnyDML(node) || Helper.foundAnySOQLorSOSL(node);
if (!sharingFound && !Helper.isTestMethodOrClass(node) && foundAnyDMLorSOQL) {
reportViolation(node, data);
}
}
/**
* Does class have sharing keyword declared?
*

View File

@ -0,0 +1,194 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.apex.rule.security;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import net.sourceforge.pmd.Report;
import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.RuleViolation;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.apex.ApexLanguageModule;
import net.sourceforge.pmd.testframework.RuleTst;
/**
* <p>Sharing settings are not inherited by inner classes. Sharing settings need to be declared on the class that
* contains the Database method, DML, SOQL, or SOSL.</p>
*
* <p>This test runs against Apex code that has an Outer class and and Inner class. Different Apex code is generated
* based on the boolean permutations. Any classes that includes data access cod, but doesn't declare a sharing setting
* should trigger a violation.</p>
*/
@RunWith(Parameterized.class)
public class ApexSharingViolationsNestedClassTest extends RuleTst {
/**
* Type of operation that may require a sharing declaration.
*/
private enum Operation {
NONE(null),
DML_DELETE("Contact c = new Contact(); delete c;"),
DML_INSERT("Contact c = new Contact(); insert c;"),
DML_MERGE("Contact c1 = new Contact(); Contact c2 = new Contact(); merge c1 c2;"),
DML_UNDELETE("Contact c = new Contact(); undelete c;"),
DML_UPDATE("Contact c = new Contact(); update c;"),
DML_UPSERT("Contact c = new Contact(); upsert c;"),
METHOD_DATABASE("Database.query('Select Id from Contact LIMIT 100');"),
SOQL("[SELECT Name FROM Contact];"),
SOSL("[FIND 'Foo' IN ALL FIELDS RETURNING Account(Name)];");
final boolean requiresSharingDeclaration;
final String codeSnippet;
Operation(String codeSnippet) {
this.requiresSharingDeclaration = codeSnippet != null;
this.codeSnippet = codeSnippet;
}
}
/**
* The permutations used for class generation. See {@link #generateClass(boolean, Operation, boolean, Operation)}
*/
private final boolean outerSharingDeclared;
private final Operation outerOperation;
private final boolean innerSharingDeclared;
private final Operation innerOperation;
/**
* Track the expected violations based on the permutations.
*/
private final int expectedViolations;
private final List<Integer> expectedLineNumbers;
public ApexSharingViolationsNestedClassTest(boolean outerSharingDeclared, Operation outerOperation,
boolean innerSharingDeclared, Operation innerOperation,
int expectedViolations, List<Integer> expectedLineNumbers) {
this.outerSharingDeclared = outerSharingDeclared;
this.outerOperation = outerOperation;
this.innerSharingDeclared = innerSharingDeclared;
this.innerOperation = innerOperation;
this.expectedViolations = expectedViolations;
this.expectedLineNumbers = expectedLineNumbers;
}
@Test
public void testSharingPermutation() {
String apexClass = generateClass(outerSharingDeclared, outerOperation, innerSharingDeclared, innerOperation);
Report rpt = new Report();
runTestFromString(apexClass, new ApexSharingViolationsRule(), rpt,
LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getDefaultVersion());
List<RuleViolation> violations = rpt.getViolations();
assertEquals("Unexpected Violation Size\n" + apexClass, expectedViolations, violations.size());
List<Integer> lineNumbers = violations.stream().map(v -> v.getBeginLine()).collect(Collectors.toList());
assertEquals("Unexpected Line Numbers\n" + apexClass, expectedLineNumbers, lineNumbers);
}
@Override
protected List<Rule> getRules() {
Rule rule = findRule("category/apex/security.xml", "ApexSharingViolations");
return Collections.singletonList(rule);
}
/**
* Parameter provider that covers are all permutations
*/
@Parameterized.Parameters
public static Collection<?> data() {
List<Object[]> data = new ArrayList<>();
boolean[] boolPermutations = {false, true};
for (boolean outerSharingDeclared : boolPermutations) {
for (Operation outerOperation : Operation.values()) {
for (boolean innerSharingDeclared : boolPermutations) {
for (Operation innerOperation : Operation.values()) {
int expectedViolations = 0;
List<Integer> expectedLineNumbers = new ArrayList<>();
if (outerOperation.requiresSharingDeclaration && !outerSharingDeclared) {
// The outer class contains SOQL but doesn't declare sharing
expectedViolations++;
expectedLineNumbers.add(1);
}
if (innerOperation.requiresSharingDeclaration && !innerSharingDeclared) {
// The inner class contains SOQL but doesn't declare sharing
expectedViolations++;
// The location of the inner class declaration depends upon the content of the outer class
expectedLineNumbers.add(outerOperation.requiresSharingDeclaration ? 3 : 2);
}
data.add(new Object[]{outerSharingDeclared, outerOperation, innerSharingDeclared, innerOperation,
expectedViolations, expectedLineNumbers});
}
}
}
}
return data;
}
/**
* <p>Generate an Apex class with various Sharing/Database/DML/SOQL/SOSL permutations. An example of the class
* returned when invoked with generateClass(true, SOQL, true, SOQL).</p>
*
* <pre>
* public with sharing class Outer {
* public void outerSOQL() {[SELECT Name FROM Contact];}
* public with sharing class Inner {
* public void innerSOQL() {[SELECT Name FROM Contact];}
* }
* }
* </pre>
*
* @param outerSharing Add 'with sharing' to Outer class definition
* @param outerOperation Add a method to Outer class that performs the given operation
* @param innerSharing Add 'with sharing' to Inner class definition
* @param innerOperation Add a method to Inner class that performs the given operation
* @return String that represents Apex code
*/
private static String generateClass(boolean outerSharing, Operation outerOperation, boolean innerSharing,
Operation innerOperation) {
StringBuilder sb = new StringBuilder();
sb.append("public ");
if (outerSharing) {
sb.append("with sharing ");
}
sb.append("class Outer {\n");
switch (outerOperation) {
case NONE:
// Do nothing
break;
default:
sb.append(String.format("\t\tpublic void outer%s(){ %s }\n", outerOperation.name(), outerOperation.codeSnippet));
break;
}
sb.append("\tpublic ");
if (innerSharing) {
sb.append("with sharing ");
}
sb.append("class Inner {\n");
switch (innerOperation) {
case NONE:
// DO Nothing
break;
default:
sb.append(String.format("\t\tpublic void inner%s(){ %s }\n", innerOperation.name(), innerOperation.codeSnippet));
break;
}
sb.append("\t}\n"); // Closes class Inner
sb.append("}\n"); // Closes class Outer
return sb.toString();
}
}

View File

@ -99,6 +99,87 @@ public inherited sharing class MyClass {
public List<Contact> getAllTheSecrets() {
return [SELECT Name FROM Contact];
}
}
]]></code>
</test-code>
<test-code>
<description>Apex test classes do not need a sharing declaration</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
@isTest public class MyClass {
public List<Contact> getAllTheSecrets() {
return [SELECT Name FROM Contact];
}
}
]]></code>
</test-code>
<!-- @isTest can only be declared on the outer class, it is inherited by the inner class-->
<test-code>
<description>Apex inner test classes do not need a sharing declaration</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
@isTest public class Outer {
public List<Contact> getAllTheOuterSecrets() {
return [SELECT Name FROM Contact];
}
public class Inner {
public List<Contact> getAllTheInnerSecrets() {
return [SELECT Name FROM Contact];
}
}
}
]]></code>
</test-code>
<test-code>
<description>Nested method calls are detected</description>
<expected-problems>1</expected-problems>
<code><![CDATA[
public class Dao {
static Map<String, List<SObject>> recordsMap = new Map<String, List<SObject>>();
public List<SObject> getRecords(String query) {
if (!recordsMap.containsKey(query)) {
recordsMap.put(query, Database.query(query));
}
return recordsMap.get(query);
}
}
]]></code>
</test-code>
<test-code>
<description>Trigger does not require sharing</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
trigger MyTrigger on Account(after insert, after update) {
[SELECT Id,(SELECT Id FROM Opportunities) FROM Account WHERE Id IN :Trigger.New];
}
]]></code>
</test-code>
<test-code>
<description>Issue #2774(false positive). Sharing correctly declared on inner class.</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Outer {
public with sharing class Inner {
public List<Contact> getAllInnerSoqlSecrets() { return [SELECT Name FROM Contact]; }
}
}
]]></code>
</test-code>
<test-code>
<description>Issue #2774(false negative). Sharing incorrectly declared on outer class.</description>
<expected-problems>1</expected-problems>
<code><![CDATA[
public with sharing class Outer {
public class Inner {
public List<Contact> getAllInnerSoqlSecrets() { return [SELECT Name FROM Contact]; }
}
}
]]></code>
</test-code>

View File

@ -4,10 +4,9 @@
package net.sourceforge.pmd.lang.java.types;
import java.lang.reflect.Modifier;
import java.util.List;
import org.objectweb.asm.Opcodes;
import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
@ -42,10 +41,10 @@ public final class TypeTestUtil {
* if the type of the node is parameterized. Examples:
*
* <pre>{@code
* isA(<new ArrayList<String>()>, List.class) = true
* isA(<new ArrayList<String>()>, ArrayList.class) = true
* isA(<new int[0]>, int[].class) = true
* isA(<new String[0]>, Object[].class) = true
* isA(List.class, <new ArrayList<String>()>) = true
* isA(ArrayList.class, <new ArrayList<String>()>) = true
* isA(int[].class, <new int[0]>) = true
* isA(Object[].class, <new String[0]>) = true
* isA(_, null) = false
* isA(null, _) = NullPointerException
* }</pre>
@ -65,8 +64,11 @@ public final class TypeTestUtil {
return true;
}
return canBeExtended(clazz) ? isA(clazz.getName(), node)
: isExactlyA(clazz, node);
if (hasNoSubtypes(clazz)) {
return isExactlyA(clazz, node);
}
String canoName = clazz.getCanonicalName();
return canoName != null && isA(canoName, node);
}
@ -76,10 +78,10 @@ public final class TypeTestUtil {
* if the type of the node is parameterized. Examples:
*
* <pre>{@code
* isA(<new ArrayList<String>()>, "java.util.List") = true
* isA(<new ArrayList<String>()>, "java.util.ArrayList") = true
* isA(<new int[0]>, "int[]") = true
* isA(<new String[0]>, "java.lang.Object[]") = true
* isA("java.util.List", <new ArrayList<String>()>) = true
* isA("java.util.ArrayList", <new ArrayList<String>()>) = true
* isA("int[]", <new int[0]>) = true
* isA("java.lang.Object[]", <new String[0]>) = true
* isA(_, null) = false
* isA(null, _) = NullPointerException
* }</pre>
@ -156,10 +158,10 @@ public final class TypeTestUtil {
* if the type of the node is parameterized.
*
* <pre>{@code
* isExactlyA(<new ArrayList<String>()>, List.class) = false
* isExactlyA(<new ArrayList<String>()>, ArrayList.class) = true
* isExactlyA(<new int[0]>, int[].class) = true
* isExactlyA(<new String[0]>, Object[].class) = false
* isExactlyA(List.class, <new ArrayList<String>()>) = false
* isExactlyA(ArrayList.class, <new ArrayList<String>()>) = true
* isExactlyA(int[].class, <new int[0]>) = true
* isExactlyA(Object[].class, <new String[0]>) = false
* isExactlyA(_, null) = false
* isExactlyA(null, _) = NullPointerException
* }</pre>
@ -198,9 +200,12 @@ public final class TypeTestUtil {
}
}
private static boolean canBeExtended(Class<?> clazz) {
private static boolean hasNoSubtypes(Class<?> clazz) {
// Neither final nor an annotation. Enums & records have ACC_FINAL
return (clazz.getModifiers() & (Opcodes.ACC_ANNOTATION | Opcodes.ACC_FINAL)) == 0;
// Note: arrays have ACC_FINAL, but have subtypes by covariance
// Note: annotations may be implemented by classes
return Modifier.isFinal(clazz.getModifiers()) && !clazz.isArray();
}
// those fallbacks can be removed with the newer symbol resolution,

View File

@ -4,6 +4,7 @@
package net.sourceforge.pmd.lang.java.types;
import java.io.ObjectStreamField;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.concurrent.Callable;
@ -20,6 +21,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMarkerAnnotation;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTType;
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;
@ -56,12 +58,60 @@ public class TypeTestUtilTest extends BaseNonParserTest {
Assert.assertNull(klass.getType());
Assert.assertTrue(TypeTestUtil.isA("org.FooBar", klass));
assertIsA(klass, Iterable.class);
assertIsA(klass, Enum.class);
assertIsA(klass, Serializable.class);
assertIsA(klass, Object.class);
assertIsStrictSubtype(klass, Iterable.class);
assertIsStrictSubtype(klass, Enum.class);
assertIsStrictSubtype(klass, Serializable.class);
assertIsStrictSubtype(klass, Object.class);
}
@Test
public void testIsAnArrayClass() {
ASTType arrayT =
java.parse("import java.io.ObjectStreamField; "
+ "class Foo { private static final ObjectStreamField[] serialPersistentFields; }")
.getFirstDescendantOfType(ASTType.class);
assertIsExactlyA(arrayT, ObjectStreamField[].class);
assertIsStrictSubtype(arrayT, Object[].class);
assertIsStrictSubtype(arrayT, Serializable.class);
assertIsNot(arrayT, Serializable[].class);
assertIsStrictSubtype(arrayT, Object.class);
}
@Test
public void testIsAnAnnotationClass() {
ASTType arrayT =
java.parse("class Foo { org.junit.Test field; }")
.getFirstDescendantOfType(ASTType.class);
assertIsExactlyA(arrayT, Test.class);
assertIsStrictSubtype(arrayT, Annotation.class);
assertIsStrictSubtype(arrayT, Object.class);
}
@Test
public void testIsAPrimitiveArrayClass() {
ASTType arrayT =
java.parse("import java.io.ObjectStreamField; "
+ "class Foo { private static final int[] serialPersistentFields; }")
.getFirstDescendantOfType(ASTType.class);
assertIsExactlyA(arrayT, int[].class);
assertIsNot(arrayT, long[].class);
assertIsNot(arrayT, Object[].class);
assertIsStrictSubtype(arrayT, Serializable.class);
assertIsStrictSubtype(arrayT, Object.class);
}
@Test
public void testIsAFallbackAnnotation() {
@ -161,10 +211,38 @@ public class TypeTestUtilTest extends BaseNonParserTest {
}
private void assertIsA(TypeNode node, Class<?> type) {
Assert.assertTrue("TypeTestUtil::isA with class arg: " + type.getCanonicalName(),
TypeTestUtil.isA(type, node));
Assert.assertTrue("TypeTestUtil::isA with string arg: " + type.getCanonicalName(),
TypeTestUtil.isA(type.getCanonicalName(), node));
assertIsA(node, type, false, true);
}
private void assertIsExactlyA(TypeNode node, Class<?> type) {
assertIsA(node, type, true, true);
assertIsA(node, type, false, true);
}
private void assertIsNot(TypeNode node, Class<?> type) {
assertIsA(node, type, true, false);
assertIsA(node, type, false, false);
}
private void assertIsNotExactly(TypeNode node, Class<?> type) {
assertIsA(node, type, true, false);
}
private void assertIsStrictSubtype(TypeNode node, Class<?> type) {
assertIsNotExactly(node, type);
assertIsA(node, type);
}
private void assertIsA(TypeNode node, Class<?> type, boolean exactly, boolean expectTrue) {
Assert.assertEquals("TypeTestUtil::isA with class arg: " + type.getCanonicalName(),
expectTrue,
exactly ? TypeTestUtil.isExactlyA(type, node)
: TypeTestUtil.isA(type, node));
Assert.assertEquals("TypeTestUtil::isA with string arg: " + type.getCanonicalName(),
expectTrue,
exactly ? TypeTestUtil.isExactlyA(type.getCanonicalName(), node)
: TypeTestUtil.isA(type.getCanonicalName(), node));
}
}

View File

@ -95,7 +95,7 @@
<checkstyle.version>8.30</checkstyle.version>
<checkstyle.plugin.version>3.1.1</checkstyle.plugin.version>
<pmd.plugin.version>3.13.0</pmd.plugin.version>
<ant.version>1.10.1</ant.version>
<ant.version>1.10.8</ant.version>
<javadoc.plugin.version>3.2.0</javadoc.plugin.version>
<antlr.version>4.7</antlr.version>