| // 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.keepanno.annotations.KeepForApi; |
| 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>. |
| */ |
| @KeepForApi |
| 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()}); |
| Map<String, String> env = new HashMap<>(); |
| env.put("java.home", home.toString()); |
| FileSystem jrtFs = FileSystems.newFileSystem(URI.create("jrt:/"), env, jrtFsJarLoader); |
| collectDescriptors(jrtFs); |
| } |
| |
| @SuppressWarnings("StreamResourceLeak") |
| 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 |
| @SuppressWarnings("Finalize") |
| protected void finalize() throws Throwable { |
| close(); |
| super.finalize(); |
| } |
| |
| @Override |
| public void close() throws IOException { |
| jrtFs.close(); |
| if (jrtFsJarLoader != null) { |
| jrtFsJarLoader.close(); |
| } |
| } |
| } |