Merge pull request #4628 from adangel:support-jrt-fs

[java] Support loading classes from java runtime images #4628
This commit is contained in:
Andreas Dangel 2024-01-05 13:51:40 +01:00
commit 070cca8743
No known key found for this signature in database
GPG Key ID: 93450DF2DF9A3FA3
16 changed files with 387 additions and 66 deletions

View File

@ -63,7 +63,7 @@ The semantic analysis roughly works like so:
3. The last pass resolves the types of expressions, which performs overload resolution on method calls, and type inference.
TODO describe
* why we need auxclasspath
* why we need auxclasspath, and how to put the java classes onto the auxclasspath (jre/lib/rt.jar or lib/jrt-fs.jar).
* how disambiguation can fail
## Type and symbol APIs

View File

@ -123,6 +123,7 @@ in the Migration Guide.
* groovy
* [#4726](https://github.com/pmd/pmd/pull/4726): \[groovy] Support Groovy to 3 and 4 and CPD suppressions
* java
* [#4628](https://github.com/pmd/pmd/pull/4628): \[java] Support loading classes from java runtime images
* [#4753](https://github.com/pmd/pmd/issues/4753): \[java] PMD crashes while using generics and wildcards
* java-codestyle
* [#2847](https://github.com/pmd/pmd/issues/2847): \[java] New Rule: Use Explicit Types
@ -645,6 +646,7 @@ Language specific fixes:
* [#4401](https://github.com/pmd/pmd/issues/4401): \[java] PMD 7 fails to build under Java 19
* [#4405](https://github.com/pmd/pmd/issues/4405): \[java] Processing error with ArrayIndexOutOfBoundsException
* [#4583](https://github.com/pmd/pmd/issues/4583): \[java] Support JDK 21 (LTS)
* [#4628](https://github.com/pmd/pmd/pull/4628): \[java] Support loading classes from java runtime images
* [#4753](https://github.com/pmd/pmd/issues/4753): \[java] PMD crashes while using generics and wildcards
* java-bestpractices
* [#342](https://github.com/pmd/pmd/issues/342): \[java] AccessorMethodGeneration: Name clash with another public field not properly handled

View File

@ -13,6 +13,11 @@ public final class PmdCli {
private PmdCli() { }
public static void main(String[] args) {
// See https://github.com/remkop/picocli/blob/main/RELEASE-NOTES.md#-picocli-470
// and https://picocli.info/#_closures_in_annotations
// we don't use this feature. Disabling it avoids leaving the groovy jar open
// caused by Class.forName("groovy.lang.Closure")
System.setProperty("picocli.disable.closures", "true");
final CommandLine cli = new CommandLine(new PmdRootCommand())
.setCaseInsensitiveEnumValuesAllowed(true);

View File

@ -323,7 +323,8 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand<PMDConfiguration>
return CliExitCode.ERROR;
}
LOG.debug("Current classpath:\n{}", System.getProperty("java.class.path"));
LOG.debug("Runtime classpath:\n{}", System.getProperty("java.class.path"));
LOG.debug("Aux classpath: {}", configuration.getClassLoader());
if (showProgressBar) {
if (reportFile == null) {

View File

@ -7,14 +7,27 @@ package net.sourceforge.pmd.internal.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@ -33,29 +46,38 @@ public class ClasspathClassLoader extends URLClassLoader {
private static final Logger LOG = LoggerFactory.getLogger(ClasspathClassLoader.class);
String javaHome;
private FileSystem fileSystem;
private Map<String, Set<String>> packagesDirsToModules;
static {
registerAsParallelCapable();
}
public ClasspathClassLoader(List<File> files, ClassLoader parent) throws IOException {
super(fileToURL(files), parent);
super(new URL[0], parent);
for (URL url : fileToURL(files)) {
addURL(url);
}
}
public ClasspathClassLoader(String classpath, ClassLoader parent) throws IOException {
super(initURLs(classpath), parent);
}
private static URL[] fileToURL(List<File> files) throws IOException {
List<URL> urlList = new ArrayList<>();
for (File f : files) {
urlList.add(f.toURI().toURL());
super(new URL[0], parent);
for (URL url : initURLs(classpath)) {
addURL(url);
}
return urlList.toArray(new URL[0]);
}
private static URL[] initURLs(String classpath) {
private List<URL> fileToURL(List<File> files) throws IOException {
List<URL> urlList = new ArrayList<>();
for (File f : files) {
urlList.add(createURLFromPath(f.getAbsolutePath()));
}
return urlList;
}
private List<URL> initURLs(String classpath) {
AssertionUtil.requireParamNotNull("classpath", classpath);
final List<URL> urls = new ArrayList<>();
try {
@ -69,10 +91,10 @@ public class ClasspathClassLoader extends URLClassLoader {
} catch (IOException e) {
throw new IllegalArgumentException("Cannot prepend classpath " + classpath + "\n" + e.getMessage(), e);
}
return urls.toArray(new URL[0]);
return urls;
}
private static void addClasspathURLs(final List<URL> urls, final String classpath) throws MalformedURLException {
private void addClasspathURLs(final List<URL> urls, final String classpath) throws MalformedURLException {
StringTokenizer toker = new StringTokenizer(classpath, File.pathSeparator);
while (toker.hasMoreTokens()) {
String token = toker.nextToken();
@ -81,7 +103,7 @@ public class ClasspathClassLoader extends URLClassLoader {
}
}
private static void addFileURLs(List<URL> urls, URL fileURL) throws IOException {
private void addFileURLs(List<URL> urls, URL fileURL) throws IOException {
try (BufferedReader in = new BufferedReader(new InputStreamReader(fileURL.openStream()))) {
String line;
while ((line = in.readLine()) != null) {
@ -95,9 +117,67 @@ public class ClasspathClassLoader extends URLClassLoader {
}
}
private static URL createURLFromPath(String path) throws MalformedURLException {
File file = new File(path);
return file.getAbsoluteFile().toURI().normalize().toURL();
private URL createURLFromPath(String path) throws MalformedURLException {
Path filePath = Paths.get(path).toAbsolutePath();
if (filePath.endsWith(Paths.get("lib", "jrt-fs.jar"))) {
initializeJrtFilesystem(filePath);
// don't add jrt-fs.jar to the normal aux classpath
return null;
}
return filePath.toUri().normalize().toURL();
}
/**
* Initializes a Java Runtime Filesystem that will be used to load class files.
* This allows end users to provide in the aux classpath another Java Runtime version
* than the one used for executing PMD.
*
* @param filePath path to the file "lib/jrt-fs.jar" inside the java installation directory.
* @see <a href="https://openjdk.org/jeps/220">JEP 220: Modular Run-Time Images</a>
*/
private void initializeJrtFilesystem(Path filePath) {
try {
LOG.debug("Detected Java Runtime Filesystem Provider in {}", filePath);
if (fileSystem != null) {
throw new IllegalStateException("There is already a jrt filesystem. Do you have multiple jrt-fs.jar files on the classpath?");
}
if (filePath.getNameCount() < 2) {
throw new IllegalArgumentException("Can't determine java home from " + filePath + " - please provide a complete path.");
}
try (URLClassLoader loader = new URLClassLoader(new URL[] { filePath.toUri().toURL() })) {
Map<String, String> env = new HashMap<>();
// note: providing java.home here is crucial, so that the correct runtime image is loaded.
// the class loader is only used to provide an implementation of JrtFileSystemProvider, if the current
// Java runtime doesn't provide one (e.g. if running in Java 8).
javaHome = filePath.getParent().getParent().toString();
env.put("java.home", javaHome);
LOG.debug("Creating jrt-fs with env {}", env);
fileSystem = FileSystems.newFileSystem(URI.create("jrt:/"), env, loader);
}
packagesDirsToModules = new HashMap<>();
Path packages = fileSystem.getPath("packages");
try (Stream<Path> packagesStream = Files.list(packages)) {
packagesStream.forEach(p -> {
String packageName = p.getFileName().toString().replace('.', '/');
try (Stream<Path> modulesStream = Files.list(p)) {
Set<String> modules = modulesStream
.map(Path::getFileName)
.map(Path::toString)
.collect(Collectors.toSet());
packagesDirsToModules.put(packageName, modules);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
@ -105,7 +185,42 @@ public class ClasspathClassLoader extends URLClassLoader {
return getClass().getSimpleName()
+ "[["
+ StringUtils.join(getURLs(), ":")
+ "] parent: " + getParent() + ']';
+ "] jrt-fs: " + javaHome + " parent: " + getParent() + ']';
}
@Override
public InputStream getResourceAsStream(String name) {
// always first search in jrt-fs, if available
// note: we can't override just getResource(String) and return a jrt:/-URL, because the URL itself
// won't be connected to the correct JrtFileSystem and would just load using the system classloader.
if (fileSystem != null) {
int lastSlash = name.lastIndexOf('/');
String packageName = name.substring(0, Math.max(lastSlash, 0));
Set<String> moduleNames = packagesDirsToModules.get(packageName);
if (moduleNames != null) {
LOG.trace("Trying to find {} in jrt-fs with packageName={} and modules={}",
name, packageName, moduleNames);
for (String moduleCandidate : moduleNames) {
Path candidate = fileSystem.getPath("modules", moduleCandidate, name);
if (Files.exists(candidate)) {
LOG.trace("Found {}", candidate);
try {
// Note: The input streams from JrtFileSystem are ByteArrayInputStreams and do not
// need to be closed - we don't need to track these. The filesystem itself needs to be closed at the end.
// See https://github.com/openjdk/jdk/blob/970cd202049f592946f9c1004ea92dbd58abf6fb/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java#L334
return Files.newInputStream(candidate);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
}
}
// search in the other jars of the aux classpath.
// this will call this.getResource, which will do a child-first search, see below.
return super.getResourceAsStream(name);
}
@Override
@ -126,24 +241,22 @@ public class ClasspathClassLoader extends URLClassLoader {
@Override
protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// checking local
c = findClass(name);
} catch (final ClassNotFoundException | SecurityException e) {
// checking parent
// This call to loadClass may eventually call findClass again, in case the parent doesn't find anything.
c = super.loadClass(name, resolve);
}
}
throw new IllegalStateException("This class loader shouldn't be used to load classes");
}
if (resolve) {
resolveClass(c);
@Override
public void close() throws IOException {
if (fileSystem != null) {
fileSystem.close();
// jrt created an own classloader to load the JrtFileSystemProvider class out of the
// jrt-fs.jar. This needs to be closed manually.
ClassLoader classLoader = fileSystem.getClass().getClassLoader();
if (classLoader instanceof URLClassLoader) {
((URLClassLoader) classLoader).close();
}
return c;
packagesDirsToModules = null;
fileSystem = null;
}
super.close();
}
}

View File

@ -0,0 +1,125 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.internal.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
class ClasspathClassLoaderTest {
@TempDir
private Path tempDir;
@Test
void loadEmptyClasspathWithParent() throws IOException {
try (ClasspathClassLoader loader = new ClasspathClassLoader("", ClasspathClassLoader.class.getClassLoader())) {
try (InputStream resource = loader.getResourceAsStream("java/lang/Object.class")) {
assertNotNull(resource);
try (DataInputStream data = new DataInputStream(resource)) {
assertClassFile(data, Integer.valueOf(System.getProperty("java.specification.version")));
}
}
}
}
/**
* This test case just documents the current behavior: Eventually we load
* the class files from the system class loader, even if the auxclasspath
* is essentially empty and no parent is provided. This is an unavoidable
* behavior of {@link java.lang.ClassLoader#getResource(java.lang.String)}, which will
* search the class loader built into the VM (BootLoader).
*/
@Test
void loadEmptyClasspathNoParent() throws IOException {
try (ClasspathClassLoader loader = new ClasspathClassLoader("", null)) {
try (InputStream resource = loader.getResourceAsStream("java/lang/Object.class")) {
assertNotNull(resource);
try (DataInputStream data = new DataInputStream(resource)) {
assertClassFile(data, Integer.valueOf(System.getProperty("java.specification.version")));
}
}
}
}
@Test
void loadFromJar() throws IOException {
final String RESOURCE_NAME = "net/sourceforge/pmd/Sample.txt";
final String TEST_CONTENT = "Test\n";
Path jarPath = tempDir.resolve("custom.jar");
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(jarPath))) {
out.putNextEntry(new ZipEntry(RESOURCE_NAME));
out.write(TEST_CONTENT.getBytes(StandardCharsets.UTF_8));
}
String classpath = jarPath.toString();
try (ClasspathClassLoader loader = new ClasspathClassLoader(classpath, null)) {
try (InputStream in = loader.getResourceAsStream(RESOURCE_NAME)) {
assertNotNull(in);
String s = IOUtil.readToString(in, StandardCharsets.UTF_8);
assertEquals(TEST_CONTENT, s);
}
}
}
/**
* Verifies, that we load the class files from the runtime image of the correct java home.
* This tests multiple versions, in order to avoid that the test accidentally is successful when
* testing e.g. java17 and running the build with java17. In that case, we might load java.lang.Object
* from the system classloader and not from jrt-fs.jar.
*
* <p>
* This test only runs, if you have a folder ${HOME}/openjdk{javaVersion}.
* </p>
*/
@ParameterizedTest
@ValueSource(ints = {11, 17, 21})
void loadFromJava(int javaVersion) throws IOException {
Path javaHome = Paths.get(System.getProperty("user.home"), "openjdk" + javaVersion);
assumeTrue(Files.isDirectory(javaHome), "Couldn't find java" + javaVersion + " installation at " + javaHome);
Path jrtfsPath = javaHome.resolve("lib/jrt-fs.jar");
assertTrue(Files.isRegularFile(jrtfsPath), "java" + javaVersion + " installation is incomplete. " + jrtfsPath + " not found!");
String classPath = jrtfsPath.toString();
try (ClasspathClassLoader loader = new ClasspathClassLoader(classPath, null)) {
assertEquals(javaHome.toString(), loader.javaHome);
try (InputStream stream = loader.getResourceAsStream("java/lang/Object.class")) {
assertNotNull(stream);
try (DataInputStream data = new DataInputStream(stream)) {
assertClassFile(data, javaVersion);
}
}
// should not fail for resources without a package
assertNull(loader.getResourceAsStream("ClassInDefaultPackage.class"));
}
}
private void assertClassFile(DataInputStream data, int javaVersion) throws IOException {
int magicNumber = data.readInt();
assertEquals(0xcafebabe, magicNumber);
data.readUnsignedShort(); // minorVersion
int majorVersion = data.readUnsignedShort();
assertEquals(44 + javaVersion, majorVersion);
}
}

View File

@ -8,6 +8,8 @@ import java.util.List;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sourceforge.pmd.ViolationSuppressor;
import net.sourceforge.pmd.lang.LanguageVersionHandler;
@ -36,6 +38,8 @@ import net.sourceforge.pmd.util.designerbindings.DesignerBindings;
public class JavaLanguageProcessor extends BatchLanguageProcessor<JavaLanguageProperties>
implements LanguageVersionHandler {
private static final Logger LOG = LoggerFactory.getLogger(JavaLanguageProcessor.class);
private final LanguageMetricsProvider myMetricsProvider = new JavaMetricsProvider();
private final JavaParser parser;
private final JavaParser parserWithoutProcessing;
@ -52,6 +56,7 @@ public class JavaLanguageProcessor extends BatchLanguageProcessor<JavaLanguagePr
public JavaLanguageProcessor(JavaLanguageProperties properties) {
this(properties, TypeSystem.usingClassLoaderClasspath(properties.getAnalysisClassLoader()));
LOG.debug("Using analysis classloader: {}", properties.getAnalysisClassLoader());
}
@Override
@ -124,4 +129,10 @@ public class JavaLanguageProcessor extends BatchLanguageProcessor<JavaLanguagePr
public void setTypeSystem(TypeSystem ts) {
this.typeSystem = Objects.requireNonNull(ts);
}
@Override
public void close() throws Exception {
this.typeSystem.logStats();
super.close();
}
}

View File

@ -81,7 +81,16 @@ public interface SymbolResolver {
}
return null;
}
@Override
public void logStats() {
stack.forEach(SymbolResolver::logStats);
}
};
}
/**
* Called at the end of the analysis in order to log out statistics of the resolved symbols.
*/
void logStats();
}

View File

@ -5,18 +5,20 @@
package net.sourceforge.pmd.lang.java.symbols.internal.asm;
import java.net.URL;
import java.io.InputStream;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.symbols.SymbolResolver;
import net.sourceforge.pmd.lang.java.symbols.internal.asm.Loader.FailedLoader;
import net.sourceforge.pmd.lang.java.symbols.internal.asm.Loader.UrlLoader;
import net.sourceforge.pmd.lang.java.symbols.internal.asm.Loader.StreamLoader;
import net.sourceforge.pmd.lang.java.types.TypeSystem;
import net.sourceforge.pmd.util.AssertionUtil;
@ -24,6 +26,7 @@ import net.sourceforge.pmd.util.AssertionUtil;
* A {@link SymbolResolver} that reads class files to produce symbols.
*/
public class AsmSymbolResolver implements SymbolResolver {
private static final Logger LOG = LoggerFactory.getLogger(AsmSymbolResolver.class);
static final int ASM_API_V = Opcodes.ASM9;
@ -53,12 +56,12 @@ public class AsmSymbolResolver implements SymbolResolver {
String internalName = getInternalName(binaryName);
ClassStub found = knownStubs.computeIfAbsent(internalName, iname -> {
@Nullable URL url = getUrlOfInternalName(iname);
if (url == null) {
@Nullable InputStream inputStream = getStreamOfInternalName(iname);
if (inputStream == null) {
return failed;
}
return new ClassStub(this, iname, new UrlLoader(url), ClassStub.UNKNOWN_ARITY);
return new ClassStub(this, iname, new StreamLoader(binaryName, inputStream), ClassStub.UNKNOWN_ARITY);
});
if (!found.hasCanonicalName()) {
@ -84,7 +87,7 @@ public class AsmSymbolResolver implements SymbolResolver {
}
@Nullable
URL getUrlOfInternalName(String internalName) {
InputStream getStreamOfInternalName(String internalName) {
return classLoader.findResource(internalName + ".class");
}
@ -105,9 +108,38 @@ public class AsmSymbolResolver implements SymbolResolver {
if (prev != failed && prev != null) {
return prev;
}
@Nullable URL url = getUrlOfInternalName(iname);
Loader loader = url == null ? FailedLoader.INSTANCE : new UrlLoader(url);
@Nullable InputStream inputStream = getStreamOfInternalName(iname);
Loader loader = inputStream == null ? FailedLoader.INSTANCE : new StreamLoader(internalName, inputStream);
return new ClassStub(this, iname, loader, observedArity);
});
}
@Override
public void logStats() {
int numParsed = 0;
int numFailed = 0;
int numFailedQueries = 0;
int numNotParsed = 0;
for (ClassStub stub : knownStubs.values()) {
if (stub == failed) { // NOPMD CompareObjectsWithEquals
// Note that failed queries may occur under normal circumstances.
// Eg package names may be queried just to figure
// out whether they're packages or classes.
numFailedQueries++;
} else if (stub.isNotParsed()) {
numNotParsed++;
} else if (!stub.isFailed()) {
numParsed++;
} else {
numFailed++;
}
}
LOG.trace("Of {} distinct queries to the classloader, {} queries failed, "
+ "{} classes were found and parsed successfully, "
+ "{} were found but failed parsing (!), "
+ "{} were found but never parsed.",
knownStubs.size(), numFailedQueries, numParsed, numFailed, numNotParsed);
}
}

View File

@ -540,6 +540,14 @@ final class ClassStub implements JClassSymbol, AsmStub, AnnotationOwner {
return getSimpleName().isEmpty();
}
boolean isFailed() {
return this.parseLock.isFailed();
}
boolean isNotParsed() {
return this.parseLock.isNotParsed();
}
// </editor-fold>

View File

@ -4,7 +4,7 @@
package net.sourceforge.pmd.lang.java.symbols.internal.asm;
import java.net.URL;
import java.io.InputStream;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -23,9 +23,9 @@ public interface Classpath {
*
* @param resourcePath Resource path, as described in {@link ClassLoader#getResource(String)}
*
* @return A URL if the resource exists, otherwise null
* @return A InputStream if the resource exists, otherwise null
*/
@Nullable URL findResource(String resourcePath);
@Nullable InputStream findResource(String resourcePath);
// <editor-fold defaultstate="collapsed" desc="Transformation methods (defaults)">
@ -42,7 +42,7 @@ public interface Classpath {
default Classpath delegateTo(Classpath c) {
return path -> {
URL p = findResource(path);
InputStream p = findResource(path);
if (p != null) {
return p;
}
@ -56,11 +56,11 @@ public interface Classpath {
/**
* Returns a classpath instance that uses {@link ClassLoader#getResource(String)}
* Returns a classpath instance that uses {@link ClassLoader#getResourceAsStream(String)}
* to find resources.
*/
static Classpath forClassLoader(ClassLoader classLoader) {
return classLoader::getResource;
return classLoader::getResourceAsStream;
}
static Classpath contextClasspath() {

View File

@ -7,7 +7,6 @@ package net.sourceforge.pmd.lang.java.symbols.internal.asm;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -35,27 +34,23 @@ abstract class Loader {
}
static class UrlLoader extends Loader {
static class StreamLoader extends Loader {
private final @NonNull String name;
private final @NonNull InputStream stream;
private final @NonNull URL url;
UrlLoader(@NonNull URL url) {
assert url != null : "Null url";
this.url = url;
StreamLoader(@NonNull String name, @NonNull InputStream stream) {
this.name = name;
this.stream = stream;
}
@Override
@Nullable
InputStream getInputStream() throws IOException {
return url.openStream();
@NonNull InputStream getInputStream() {
return stream;
}
@Override
public String toString() {
return "(URL loader)";
return "(StreamLoader for " + name + ")";
}
}
}

View File

@ -58,6 +58,10 @@ abstract class ParseLock {
return getFinalStatus() == ParseStatus.FAILED;
}
public boolean isNotParsed() {
return status == ParseStatus.NOT_PARSED;
}
// will be called in the critical section after parse is done
protected void finishParse(boolean failed) {
// by default do nothing

View File

@ -8,6 +8,8 @@ import java.util.Map;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.symbols.SymbolResolver;
@ -16,6 +18,7 @@ import net.sourceforge.pmd.lang.java.symbols.SymbolResolver;
* A symbol resolver that knows about a few hand-picked symbols.
*/
final class MapSymResolver implements SymbolResolver {
private static final Logger LOG = LoggerFactory.getLogger(MapSymResolver.class);
private final Map<String, JClassSymbol> byCanonicalName;
private final Map<String, JClassSymbol> byBinaryName;
@ -35,4 +38,10 @@ final class MapSymResolver implements SymbolResolver {
public @Nullable JClassSymbol resolveClassFromCanonicalName(@NonNull String canonicalName) {
return byCanonicalName.get(canonicalName);
}
@Override
public void logStats() {
LOG.trace("Used {} classes by canonical name and {} classes by binary name",
byCanonicalName.size(), byBinaryName.size());
}
}

View File

@ -737,6 +737,13 @@ public final class TypeSystem {
return new TypeVarImpl.RegularTypeVar(this, symbol, HashTreePSet.empty());
}
/**
* Called at the end of the analysis to log statistics about the loaded types.
*/
public void logStats() {
resolver.logStats();
}
private static final class NullType implements JTypeMirror {
private final TypeSystem ts;

View File

@ -38,7 +38,7 @@ class AsmLoaderTest : IntelliMarker, FunSpec({
// access flags
// method reference with static ctdecl & zero formal parameters (asInstanceMethod)
val contextClasspath = Classpath { Thread.currentThread().contextClassLoader.getResource(it) }
val contextClasspath = Classpath { Thread.currentThread().contextClassLoader.getResourceAsStream(it) }
test("First ever ASM test") {