forked from phoedos/pmd
Merge branch 'pmd7-delete-old-typeres' into 7.0.x
This commit is contained in:
@ -16,6 +16,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTCompactConstructorDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
|
||||
@ -35,8 +36,8 @@ import net.sourceforge.pmd.lang.java.ast.JModifier;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaNode;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaVisitorBase;
|
||||
import net.sourceforge.pmd.lang.java.ast.TypeNode;
|
||||
import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol;
|
||||
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
|
||||
import net.sourceforge.pmd.lang.java.types.JVariableSig;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.Attribute;
|
||||
import net.sourceforge.pmd.util.designerbindings.DesignerBindings.DefaultDesignerBindings;
|
||||
import net.sourceforge.pmd.util.designerbindings.RelatedNodesSelector;
|
||||
@ -118,15 +119,10 @@ public final class JavaDesignerBindings extends DefaultDesignerBindings {
|
||||
@Override
|
||||
public RelatedNodesSelector getRelatedNodesSelector() {
|
||||
return n -> {
|
||||
if (n instanceof ASTVariableAccess) {
|
||||
// poor man's reference search
|
||||
JVariableSig var = ((JavaNode) n).getSymbolTable()
|
||||
.variables()
|
||||
.resolveFirst(n.getImage());
|
||||
if (var != null) {
|
||||
return n.getRoot().descendants(ASTVariableDeclaratorId.class)
|
||||
.filter(it -> it.getSymbol().equals(var.getSymbol()))
|
||||
.toList(it -> it);
|
||||
if (n instanceof ASTNamedReferenceExpr) {
|
||||
JVariableSymbol sym = ((ASTNamedReferenceExpr) n).getReferencedSym();
|
||||
if (sym != null && sym.tryGetNode() != null) {
|
||||
return Collections.unmodifiableList(sym.tryGetNode().getLocalUsages());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,39 +0,0 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.typeresolution;
|
||||
|
||||
import net.sourceforge.pmd.annotation.DeprecatedUntil700;
|
||||
import net.sourceforge.pmd.annotation.InternalApi;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter;
|
||||
import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader;
|
||||
|
||||
|
||||
//
|
||||
// Helpful reading:
|
||||
// http://www.janeg.ca/scjp/oper/promotions.html
|
||||
// http://java.sun.com/docs/books/jls/second_edition/html/conversions.doc.html
|
||||
//
|
||||
|
||||
/**
|
||||
* @deprecated Some rules still use this so we keep it around, but it's dysfunctional
|
||||
*/
|
||||
@Deprecated
|
||||
@DeprecatedUntil700
|
||||
@InternalApi
|
||||
public class ClassTypeResolver extends JavaParserVisitorAdapter implements NullableClassLoader {
|
||||
|
||||
/**
|
||||
* Check whether the supplied class name exists.
|
||||
*/
|
||||
public boolean classNameExists(String fullyQualifiedClassName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> loadClassOrNull(String fullyQualifiedClassName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.typeresolution;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
|
||||
import net.sourceforge.pmd.annotation.InternalApi;
|
||||
import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader;
|
||||
import net.sourceforge.pmd.lang.java.typeresolution.visitors.PMDASMVisitor;
|
||||
|
||||
/*
|
||||
* I've refactored this class to not cache the results any more. This is a
|
||||
* tradeoff in testing I've found the CPU tradeoff is negligeable. With the
|
||||
* cache, large codebases consumed a lot of memory and slowed down greatly when
|
||||
* approaching 3,000 classes. I'm adding this comment in case someone is looking
|
||||
* at this code and thinks a cache may help.
|
||||
*
|
||||
* see: git show 9e7deee88f63870a1de2cd86458278a027deb6d6
|
||||
*
|
||||
* However, there seems to be a big performance improvement by caching
|
||||
* the negative cases only. The cache is shared between loadClass and getImportedClasses,
|
||||
* as they are using the same (parent) class loader, e.g. if the class foo.Bar cannot be loaded,
|
||||
* then the resource foo/Bar.class will not exist, too.
|
||||
*
|
||||
* Note: since git show 46ad3a4700b7a233a177fa77d08110127a85604c the cache is using
|
||||
* a concurrent hash map to avoid synchronizing on the class loader instance.
|
||||
*/
|
||||
@InternalApi
|
||||
@Deprecated
|
||||
public final class PMDASMClassLoader extends ClassLoader implements NullableClassLoader {
|
||||
|
||||
private static PMDASMClassLoader cachedPMDASMClassLoader;
|
||||
private static ClassLoader cachedClassLoader;
|
||||
private static final Object CACHE_LOCK = new Object();
|
||||
|
||||
/**
|
||||
* Caches the names of the classes that we can't load or that don't exist.
|
||||
*/
|
||||
private final ConcurrentMap<String, Boolean> dontBother = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
registerAsParallelCapable();
|
||||
}
|
||||
|
||||
private PMDASMClassLoader(ClassLoader parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* A new PMDASMClassLoader is created for each compilation unit, this method
|
||||
* allows to reuse the same PMDASMClassLoader across all the compilation
|
||||
* units.
|
||||
*/
|
||||
public static PMDASMClassLoader getInstance(ClassLoader parent) {
|
||||
if (parent instanceof PMDASMClassLoader) {
|
||||
return (PMDASMClassLoader) parent;
|
||||
}
|
||||
synchronized (CACHE_LOCK) {
|
||||
if (parent.equals(cachedClassLoader)) {
|
||||
return cachedPMDASMClassLoader;
|
||||
}
|
||||
cachedClassLoader = parent;
|
||||
cachedPMDASMClassLoader = new PMDASMClassLoader(parent);
|
||||
|
||||
return cachedPMDASMClassLoader;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||
Class<?> aClass = loadClassOrNull(name);
|
||||
if (aClass == null) {
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
return aClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not throwing CNFEs to represent failure makes a huge performance
|
||||
* difference. Typeres as a whole is 2x faster.
|
||||
*/
|
||||
@Override
|
||||
public Class<?> loadClassOrNull(String name) {
|
||||
if (dontBother.containsKey(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return super.loadClass(name);
|
||||
} catch (ClassNotFoundException | LinkageError e) {
|
||||
dontBother.put(name, Boolean.TRUE);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the class loader could resolve a given class name (ie: it
|
||||
* doesn't know for sure it will fail). Notice, that the ability to resolve
|
||||
* a class does not imply that the class will actually be found and
|
||||
* resolved.
|
||||
*
|
||||
* @param name
|
||||
* the name of the class
|
||||
* @return whether the class can be resolved
|
||||
*/
|
||||
public boolean couldResolve(String name) {
|
||||
return !dontBother.containsKey(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FIXME what does this do?
|
||||
*/
|
||||
public synchronized Map<String, String> getImportedClasses(String name) throws ClassNotFoundException {
|
||||
if (dontBother.containsKey(name)) {
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
try (InputStream classResource = getResourceAsStream(name.replace('.', '/') + ".class")) {
|
||||
ClassReader reader = new ClassReader(classResource);
|
||||
PMDASMVisitor asmVisitor = new PMDASMVisitor(name);
|
||||
reader.accept(asmVisitor, 0);
|
||||
|
||||
List<String> inner = asmVisitor.getInnerClasses();
|
||||
if (inner != null && !inner.isEmpty()) {
|
||||
// to avoid ConcurrentModificationException
|
||||
inner = new ArrayList<>(inner);
|
||||
for (String str : inner) {
|
||||
try (InputStream innerClassStream = getResourceAsStream(str.replace('.', '/') + ".class")) {
|
||||
if (innerClassStream != null) {
|
||||
reader = new ClassReader(innerClassStream);
|
||||
reader.accept(asmVisitor, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return asmVisitor.getPackages();
|
||||
} catch (IOException e) {
|
||||
dontBother.put(name, Boolean.TRUE);
|
||||
throw new ClassNotFoundException(name, e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.typeresolution.internal;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.typeresolution.PMDASMClassLoader;
|
||||
|
||||
/**
|
||||
* A classloader that doesn't throw a {@link ClassNotFoundException}
|
||||
* to report unresolved classes. This is a big performance improvement
|
||||
* for {@link PMDASMClassLoader}, which caches negative cases.
|
||||
*
|
||||
* <p>See https://github.com/pmd/pmd/pull/2236
|
||||
*/
|
||||
public interface NullableClassLoader {
|
||||
|
||||
/**
|
||||
* Load a class from its binary name. Returns null if not found.
|
||||
*/
|
||||
Class<?> loadClassOrNull(String binaryName);
|
||||
|
||||
|
||||
final class ClassLoaderWrapper implements NullableClassLoader {
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private ClassLoaderWrapper(ClassLoader classLoader) {
|
||||
assert classLoader != null : "Null classloader";
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> loadClassOrNull(String binaryName) {
|
||||
try {
|
||||
return classLoader.loadClass(binaryName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ClassLoaderWrapper wrapNullable(ClassLoader classLoader) {
|
||||
if (classLoader == null) {
|
||||
classLoader = ClassLoader.getSystemClassLoader();
|
||||
}
|
||||
return new ClassLoaderWrapper(classLoader);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@
|
||||
package net.sourceforge.pmd.lang.java.types;
|
||||
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
@ -32,6 +33,7 @@ abstract class MapFunction<T, R> implements Function<T, R> {
|
||||
@Override
|
||||
public String toString() {
|
||||
return map.entrySet().stream()
|
||||
.sorted(Comparator.comparing(e -> e.getKey().toString()))
|
||||
.map(it -> it.getKey() + " => " + it.getValue())
|
||||
.collect(Collectors.joining("; ", getClass().getSimpleName() + "[", "]"));
|
||||
}
|
||||
|
@ -112,6 +112,24 @@ class SubstTest : ProcessorTestSpec({
|
||||
|
||||
}
|
||||
|
||||
parserTest("Test subst toString") {
|
||||
|
||||
|
||||
val (a, b, c) = makeDummyTVars("A", "B", "C")
|
||||
|
||||
with(TypeDslOf(a.typeSystem)) {
|
||||
val `t_Iter{B}` = Iterable::class[b]
|
||||
val `t_Coll{C}` = Collection::class[c]
|
||||
|
||||
|
||||
val sub1 = subOf(a to `t_Iter{B}`, b to `t_Coll{C}`)
|
||||
|
||||
sub1.toString() shouldBe "Substitution[A => java.lang.Iterable<B>; B => java.util.Collection<C>]"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
||||
|
@ -190,6 +190,7 @@ class F {
|
||||
val (acu, spy) = parser.parseWithTypeInferenceSpy(
|
||||
|
||||
"""
|
||||
interface Collection<E> {}
|
||||
class Sup {
|
||||
static <E> void m(Collection<? extends E> e) {}
|
||||
}
|
||||
@ -379,19 +380,84 @@ class Scratch {
|
||||
|
||||
spy.shouldBeOk {
|
||||
ctor.methodType.shouldBeSomeInstantiationOf(withSupplier)
|
||||
}
|
||||
|
||||
spy.shouldBeOk {
|
||||
voidM.methodType.shouldBeSomeInstantiationOf(withRunnable)
|
||||
}
|
||||
|
||||
spy.shouldBeOk {
|
||||
stdM.methodType.shouldBeSomeInstantiationOf(withSupplier)
|
||||
}
|
||||
|
||||
spy.shouldBeOk {
|
||||
polyM.methodType.shouldBeSomeInstantiationOf(withSupplier)
|
||||
}
|
||||
}
|
||||
|
||||
parserTest("Test specificity between exact mrefs") {
|
||||
|
||||
val (acu, spy) = parser.parseWithTypeInferenceSpy(
|
||||
"""
|
||||
class Scratch {
|
||||
|
||||
interface Runnable { void run(); }
|
||||
interface Supplier<E> { E get(); }
|
||||
|
||||
static void bench(String label, Runnable runnable) { }
|
||||
static <T> T bench(String label, Supplier<T> runnable) { return null; }
|
||||
|
||||
static void voidMethod() {}
|
||||
static Scratch standaloneMethod() { return null; }
|
||||
static <T> T polyMethod() { return null; }
|
||||
|
||||
static {
|
||||
bench("foo", Scratch::new); // selects the supplier
|
||||
bench("foo", Scratch::voidMethod); // selects the runnable
|
||||
bench("foo", Scratch::standaloneMethod); // selects the supplier
|
||||
bench("foo", Scratch::<Object>polyMethod); // selects the supplier
|
||||
bench("foo", Scratch::polyMethod); // ambiguous
|
||||
}
|
||||
}
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val (_, _, withRunnable, withSupplier) = acu.declaredMethodSignatures()
|
||||
|
||||
val (ctor, voidM, stdM, polyM, ambigM) = acu.methodCalls().toList()
|
||||
|
||||
spy.shouldBeAmbiguous(ambigM)
|
||||
|
||||
ctor.methodType.shouldBeSomeInstantiationOf(withSupplier)
|
||||
voidM.methodType.shouldBeSomeInstantiationOf(withRunnable)
|
||||
stdM.methodType.shouldBeSomeInstantiationOf(withSupplier)
|
||||
polyM.methodType.shouldBeSomeInstantiationOf(withSupplier)
|
||||
}
|
||||
|
||||
parserTest("Test specificity between implicitly typed lamdbas (ambiguous)") {
|
||||
|
||||
val (acu, spy) = parser.parseWithTypeInferenceSpy(
|
||||
"""
|
||||
class Scratch {
|
||||
|
||||
interface Runnable { void run(Object o); }
|
||||
interface Supplier<E> { E get(Object u); }
|
||||
|
||||
static void bench(String label, Runnable runnable) { }
|
||||
static <T> T bench(String label, Supplier<T> runnable) { return null; }
|
||||
|
||||
static void voidMethod() {}
|
||||
static Scratch standaloneMethod() { return null; }
|
||||
static <T> T polyMethod() { return null; }
|
||||
|
||||
static {
|
||||
bench("foo", o -> new Scratch()); // selects the supplier
|
||||
bench("foo", o -> voidMethod()); // selects the runnable
|
||||
bench("foo", o -> standaloneMethod()); // selects the supplier
|
||||
bench("foo", o -> polyMethod()); // selects the supplier
|
||||
}
|
||||
}
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val (ctor, voidM, stdM, polyM) = acu.methodCalls().toList()
|
||||
spy.shouldBeAmbiguous(ctor)
|
||||
spy.shouldBeAmbiguous(voidM)
|
||||
spy.shouldBeAmbiguous(stdM)
|
||||
spy.shouldBeAmbiguous(polyM)
|
||||
}
|
||||
|
||||
})
|
||||
|
Reference in New Issue
Block a user