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

@ -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));
}
}