blob: 9fcbd00138dd59adbd1c7542ade04a9305ed295b [file] [log] [blame]
// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import java.io.Closeable;
import java.io.IOException;
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.NoSuchFileException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Lazy Java class file resource provider loading class files from a JDK.
*
* <p>The descriptor index is built eagerly upon creating the provider and subsequent requests for
* resources in the descriptor set will then force the read of JDK content.
*
* <p>This supports all JDK versions. For JDK's of version 8 or lower classes in
* <code>lib/rt.jar</code> will be loaded. JDK's of version 9 or higher system module classes will
* be loaded using <code>lib/jrt-fs.jar/<code>.
*/
@Keep
public class JdkClassFileProvider implements ClassFileResourceProvider, Closeable {
private Origin origin;
private final Set<String> descriptors = new HashSet<>();
private final Map<String, String> descriptorToModule = new HashMap<>();
private URLClassLoader jrtFsJarLoader;
private FileSystem jrtFs;
/**
* Creates a lazy class-file program-resource provider for the system modules of the running Java
* VM.
*
* <p>Only supported if the current VM is of version 9 or higher.
*/
public static ClassFileResourceProvider fromSystemJdk() throws IOException {
return new JdkClassFileProvider();
}
/**
* Creates a lazy class-file program-resource provider for the system modules of a JDK.
*
* @param home Location of the JDK to read the program-resources from.
* <p>Only supported if the JDK to read is of version 9 or higher.
*/
public static ClassFileResourceProvider fromSystemModulesJdk(Path home) throws IOException {
Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
if (!Files.exists(jrtFsJar)) {
throw new NoSuchFileException(jrtFsJar.toString());
}
return new JdkClassFileProvider(home);
}
/**
* Creates a lazy class-file program-resource provider for a JDK.
*
* <p>This will load the program resources form the system modules for JDK of version 9 or higher.
*
* <p>This will load <code>lib/rt.jar</code> for JDK of version 8 and lower.
*
* @param home Location of the JDK to read the program-resources from.
*/
public static ClassFileResourceProvider fromJdkHome(Path home) throws IOException {
Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
if (Files.exists(jrtFsJar)) {
return JdkClassFileProvider.fromSystemModulesJdk(home);
}
// JDK has rt.jar in jre/lib/rt.jar.
Path rtJar = home.resolve("jre").resolve("lib").resolve("rt.jar");
if (Files.exists(rtJar)) {
return fromJavaRuntimeJar(rtJar);
}
// JRE has rt.jar in lib/rt.jar.
rtJar = home.resolve("lib").resolve("rt.jar");
if (Files.exists(rtJar)) {
return fromJavaRuntimeJar(rtJar);
}
throw new IOException("Path " + home + " does not look like a Java home");
}
public static ClassFileResourceProvider fromJavaRuntimeJar(Path archive) throws IOException {
return new ArchiveClassFileProvider(archive);
}
private JdkClassFileProvider() throws IOException {
origin = Origin.unknown();
collectDescriptors(FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap()));
}
/**
* Creates a lazy class-file program-resource provider for the system modules of a JDK.
*
* @param home Location of the JDK to read the program-resources from.
* <p>Only supported if the JDK to read is of version 9 or higher.
*/
private JdkClassFileProvider(Path home) throws IOException {
origin = new PathOrigin(home);
Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
assert Files.exists(jrtFsJar);
jrtFsJarLoader = new URLClassLoader(new URL[] {jrtFsJar.toUri().toURL()});
FileSystem jrtFs =
FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap(), jrtFsJarLoader);
collectDescriptors(jrtFs);
}
private void collectDescriptors(FileSystem jrtFs) throws IOException {
this.jrtFs = jrtFs;
Files.walk(jrtFs.getPath("/modules"))
.forEach(
path -> {
if (FileUtils.isClassFile(path)) {
DescriptorUtils.ModuleAndDescriptor moduleAndDescriptor =
DescriptorUtils.guessJrtModuleAndTypeDescriptor(path.toString());
descriptorToModule.put(
moduleAndDescriptor.getDescriptor(), moduleAndDescriptor.getModule());
descriptors.add(moduleAndDescriptor.getDescriptor());
}
});
}
@Override
public Set<String> getClassDescriptors() {
return Collections.unmodifiableSet(descriptors);
}
@Override
public ProgramResource getProgramResource(String descriptor) {
if (!descriptors.contains(descriptor)) {
return null;
}
try {
return ProgramResource.fromBytes(
Origin.unknown(),
ProgramResource.Kind.CF,
Files.readAllBytes(
jrtFs.getPath(
"modules",
descriptorToModule.get(descriptor),
DescriptorUtils.getPathFromDescriptor(descriptor))),
Collections.singleton(descriptor));
} catch (IOException e) {
throw new CompilationError("Failed to read '" + descriptor, origin);
}
}
@Override
protected void finalize() throws Throwable {
close();
super.finalize();
}
@Override
public void close() throws IOException {
jrtFs.close();
if (jrtFsJarLoader != null) {
jrtFsJarLoader.close();
}
}
}