Make typeIs XPath functions use the typeres cache

This commit is contained in:
Clément Fournier
2020-03-22 21:52:21 +01:00
parent 17ddfd2a22
commit 96532f26cc
7 changed files with 143 additions and 27 deletions

View File

@ -60,6 +60,11 @@ public class ASTCompilationUnit extends AbstractJavaTypeNode implements RootNode
return null;
}
@Override
public ASTCompilationUnit getRoot() {
return this;
}
/**
* Returns the package name of this compilation unit. If this is in
* the default package, returns the empty string.

View File

@ -15,6 +15,7 @@ public abstract class AbstractJavaNode extends AbstractJjtreeNode<JavaNode> impl
protected JavaParser parser;
private Scope scope;
private Comment comment;
private ASTCompilationUnit root;
@InternalApi
@Deprecated
@ -64,6 +65,14 @@ public abstract class AbstractJavaNode extends AbstractJjtreeNode<JavaNode> impl
return data;
}
@Override
public ASTCompilationUnit getRoot() {
if (root == null) {
root = getParent().getRoot();
}
return root;
}
@Override
public Scope getScope() {
if (scope == null) {

View File

@ -41,6 +41,7 @@ public interface JavaNode extends ScopedNode {
@Deprecated
Object childrenAccept(JavaParserVisitor visitor, Object data);
ASTCompilationUnit getRoot();
@Override
JavaNode getChild(int index);

View File

@ -66,7 +66,7 @@ public class DuplicateImportsRule extends AbstractJavaRule {
return true;
}
} else {
Class<?> importClass = node.getClassTypeResolver().loadClass(thisImportOnDemand.getName());
Class<?> importClass = node.getClassTypeResolver().loadClassOrNull(thisImportOnDemand.getName());
if (importClass != null) {
for (Method m : importClass.getMethods()) {
if (Modifier.isStatic(m.getModifiers()) && m.getName().equals(singleTypeName)) {

View File

@ -99,6 +99,7 @@ import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter;
import net.sourceforge.pmd.lang.java.ast.TypeNode;
import net.sourceforge.pmd.lang.java.symboltable.ClassScope;
import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader;
import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition;
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
import net.sourceforge.pmd.lang.symboltable.Scope;
@ -112,7 +113,7 @@ import net.sourceforge.pmd.lang.symboltable.Scope;
@Deprecated
@InternalApi
public class ClassTypeResolver extends JavaParserVisitorAdapter {
public class ClassTypeResolver extends JavaParserVisitorAdapter implements NullableClassLoader {
private static final Logger LOG = Logger.getLogger(ClassTypeResolver.class.getName());
@ -1487,10 +1488,15 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter {
return pmdClassLoader.loadClassOrNull(fullyQualifiedClassName) != null;
}
public Class<?> loadClass(String fullyQualifiedClassName) {
@Override
public Class<?> loadClassOrNull(String fullyQualifiedClassName) {
return pmdClassLoader.loadClassOrNull(fullyQualifiedClassName);
}
public Class<?> loadClass(String fullyQualifiedClassName) {
return loadClassOrNull(fullyQualifiedClassName);
}
private Class<?> processOnDemand(String qualifiedName) {
for (String entry : importedOnDemand) {
String fullClassName = entry + "." + qualifiedName;
@ -1533,12 +1539,12 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter {
String strPackage = anImportDeclaration.getPackageName();
if (anImportDeclaration.isStatic()) {
if (anImportDeclaration.isImportOnDemand()) {
importOnDemandStaticClasses.add(JavaTypeDefinition.forClass(loadClass(strPackage)));
importOnDemandStaticClasses.add(JavaTypeDefinition.forClass(loadClassOrNull(strPackage)));
} else { // not import on-demand
String strName = anImportDeclaration.getImportedName();
String fieldName = strName.substring(strName.lastIndexOf('.') + 1);
Class<?> staticClassWithField = loadClass(strPackage);
Class<?> staticClassWithField = loadClassOrNull(strPackage);
if (staticClassWithField != null) {
JavaTypeDefinition typeDef = getFieldType(JavaTypeDefinition.forClass(staticClassWithField),
fieldName, currentAcu.getType());

View File

@ -4,7 +4,12 @@
package net.sourceforge.pmd.lang.java.typeresolution;
import org.apache.commons.lang3.ClassUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
@ -13,9 +18,48 @@ import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTImplementsList;
import net.sourceforge.pmd.lang.java.ast.TypeNode;
import net.sourceforge.pmd.lang.java.symboltable.TypedNameDeclaration;
import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader;
import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader.ClassLoaderWrapper;
public final class TypeHelper {
/** Maps a primitive class name to its corresponding abbreviation used in array class names. */
private static final Map<String, String> abbreviationMap;
/** Maps names of primitives to their corresponding primitive {@code Class}es. */
private static final Map<String, Class<?>> namePrimitiveMap = new HashMap<>();
static {
namePrimitiveMap.put("boolean", Boolean.TYPE);
namePrimitiveMap.put("byte", Byte.TYPE);
namePrimitiveMap.put("char", Character.TYPE);
namePrimitiveMap.put("short", Short.TYPE);
namePrimitiveMap.put("int", Integer.TYPE);
namePrimitiveMap.put("long", Long.TYPE);
namePrimitiveMap.put("double", Double.TYPE);
namePrimitiveMap.put("float", Float.TYPE);
namePrimitiveMap.put("void", Void.TYPE);
}
static {
final Map<String, String> m = new HashMap<>();
m.put("int", "I");
m.put("boolean", "Z");
m.put("float", "F");
m.put("long", "J");
m.put("short", "S");
m.put("byte", "B");
m.put("double", "D");
m.put("char", "C");
final Map<String, String> r = new HashMap<>();
for (final Map.Entry<String, String> e : m.entrySet()) {
r.put(e.getValue(), e.getKey());
}
abbreviationMap = Collections.unmodifiableMap(m);
}
private TypeHelper() {
// utility class
}
@ -101,34 +145,56 @@ public final class TypeHelper {
private static Class<?> loadClassWithNodeClassloader(final TypeNode n, final String clazzName) {
if (n.getType() != null) {
return loadClass(n.getType().getClassLoader(), clazzName);
return loadClass(n.getRoot().getClassTypeResolver(), clazzName);
}
return null;
}
private static Class<?> loadClass(final ClassLoader nullableClassLoader, final String clazzName) {
try {
ClassLoader classLoader = nullableClassLoader;
if (classLoader == null) {
// Using the system classloader then
classLoader = ClassLoader.getSystemClassLoader();
private static Class<?> loadClass(NullableClassLoader ctr, String className) {
Class<?> clazz;
if (namePrimitiveMap.containsKey(className)) {
clazz = namePrimitiveMap.get(className);
} else {
clazz = ctr.loadClassOrNull(toCanonicalName(className));
}
if (clazz != null) {
return clazz;
}
// allow path separators (.) as inner class name separators
final int lastDotIndex = className.lastIndexOf('.');
if (lastDotIndex != -1) {
String asInner = className.substring(0, lastDotIndex)
+ '$' + className.substring(lastDotIndex + 1);
return loadClass(ctr, asInner);
}
return null;
}
private static String toCanonicalName(String className) {
className = StringUtils.deleteWhitespace(className);
Validate.notNull(className, "className must not be null.");
if (className.endsWith("[]")) {
final StringBuilder classNameBuffer = new StringBuilder();
while (className.endsWith("[]")) {
className = className.substring(0, className.length() - 2);
classNameBuffer.append("[");
}
// If the requested type is in the classpath, using the same classloader should work
return ClassUtils.getClass(classLoader, clazzName);
} catch (ClassNotFoundException ignored) {
// The requested type is not on the auxclasspath. This might happen, if the type node
// is probed for a specific type (e.g. is is a JUnit5 Test Annotation class).
// Failing to resolve clazzName does not necessarily indicate an incomplete auxclasspath.
} catch (final LinkageError expected) {
// We found the class but it's invalid / incomplete. This may be an incomplete auxclasspath
// if it was a NoClassDefFoundError. TODO : Report it?
final String abbreviation = abbreviationMap.get(className);
if (abbreviation != null) {
classNameBuffer.append(abbreviation);
} else {
classNameBuffer.append("L").append(className).append(";");
}
className = classNameBuffer.toString();
}
return null;
return className;
}
/** @see #isA(TypeNode, String) */
public static boolean isA(TypeNode n, Class<?> clazz) {
return subclasses(n, clazz);
@ -142,7 +208,7 @@ public final class TypeHelper {
Class<?> type = vnd.getType();
for (final Class<?> clazz : clazzes) {
if (type != null && type.equals(clazz) || type == null
&& (clazz.getSimpleName().equals(vnd.getTypeImage()) || clazz.getName().equals(vnd.getTypeImage()))) {
&& (clazz.getSimpleName().equals(vnd.getTypeImage()) || clazz.getName().equals(vnd.getTypeImage()))) {
return true;
}
}
@ -192,7 +258,7 @@ public final class TypeHelper {
public static boolean isA(TypedNameDeclaration vnd, String className) {
Class<?> type = vnd.getType();
if (type != null) {
Class<?> clazz = loadClass(type.getClassLoader(), className);
Class<?> clazz = loadClass(new ClassLoaderWrapper(type.getClassLoader()), className);
if (clazz != null) {
return clazz.isAssignableFrom(type);
}

View File

@ -0,0 +1,29 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.typeresolution.internal;
public interface NullableClassLoader {
Class<?> loadClassOrNull(String binaryName);
class ClassLoaderWrapper implements NullableClassLoader {
private final ClassLoader classLoader;
public ClassLoaderWrapper(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public Class<?> loadClassOrNull(String binaryName) {
try {
return classLoader.loadClass(binaryName);
} catch (ClassNotFoundException e) {
return null;
}
}
}
}