Add class-file program-resource provider for the system modules of a JDK
This makes it possible to use the system modules from JDK 9+ as library
classpath. These JDKs does not have a rt.jar file, but a file system
provided by the "jtr:" file system provider.
Bug: 143335486
Change-Id: If1fac4623742e5f72774845249f546fa076da5c1
diff --git a/src/main/java/com/android/tools/r8/JdkClassFileProvider.java b/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
new file mode 100644
index 0000000..5e35cf0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
@@ -0,0 +1,172 @@
+// 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>Currently only JDK's of version 9 or higher with a lib/jrt-fs.jar file present is supported.
+ */
+@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 the runtime of 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>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();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index af056a8..adbaad4 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.MODULES_PREFIX;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
@@ -429,6 +430,46 @@
return 'L' + descriptor + ';';
}
+ public static class ModuleAndDescriptor {
+ private final String module;
+ private final String descriptor;
+
+ ModuleAndDescriptor(String module, String descriptor) {
+ this.module = module;
+ this.descriptor = descriptor;
+ }
+
+ public String getModule() {
+ return module;
+ }
+
+ public String getDescriptor() {
+ return descriptor;
+ }
+ }
+
+ /**
+ * Guess module and class descriptor from the location of a class file in a jrt file system.
+ *
+ * @param name the location in a jrt file system of the class file to convert to descriptor
+ * @return module and java class descriptor
+ */
+ public static ModuleAndDescriptor guessJrtModuleAndTypeDescriptor(String name) {
+ assert name != null;
+ assert name.endsWith(CLASS_EXTENSION)
+ : "Name " + name + " must have " + CLASS_EXTENSION + " suffix";
+ assert name.startsWith(MODULES_PREFIX)
+ : "Name " + name + " must have " + MODULES_PREFIX + " prefix";
+ assert name.charAt(MODULES_PREFIX.length()) == '/';
+ int moduleNameEnd = name.indexOf('/', MODULES_PREFIX.length() + 1);
+ String module = name.substring(MODULES_PREFIX.length() + 1, moduleNameEnd);
+ String descriptor = name.substring(moduleNameEnd + 1, name.length() - CLASS_EXTENSION.length());
+ if (descriptor.indexOf(JAVA_PACKAGE_SEPARATOR) != -1) {
+ throw new CompilationError("Unexpected class file name: " + name);
+ }
+ return new ModuleAndDescriptor(module, 'L' + descriptor + ';');
+ }
+
public static String getPathFromDescriptor(String descriptor) {
// We are quite loose on names here to support testing illegal names, too.
assert descriptor.startsWith("L");
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 24c35fb..19fc10a 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -30,6 +30,7 @@
public static final String JAVA_EXTENSION = ".java";
public static final String KT_EXTENSION = ".kt";
public static final String MODULE_INFO_CLASS = "module-info.class";
+ public static final String MODULES_PREFIX = "/modules";
public static final boolean isAndroid =
System.getProperty("java.vm.name").equalsIgnoreCase("Dalvik");
diff --git a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
new file mode 100644
index 0000000..1754bff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
@@ -0,0 +1,655 @@
+package com.android.tools.r8;
+
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.List;
+import org.hamcrest.core.StringContains;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+@RunWith(Parameterized.class)
+public class CompileWithJdkClassFileProviderTest extends TestBase implements Opcodes {
+
+ @Parameters(name = "{0}, library: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withAllRuntimes().build(), CfVm.values());
+ }
+
+ private final TestParameters parameters;
+ private final CfVm library;
+
+ public CompileWithJdkClassFileProviderTest(TestParameters parameters, CfVm library) {
+ this.parameters = parameters;
+ this.library = library;
+ }
+
+ @Test
+ public void compileSimpleCodeWithJdkLibrary() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromJdkHome(TestRuntime.getCheckedInJDKHome(library));
+
+ testForR8(parameters.getBackend())
+ .addLibraryProvider(provider)
+ .addProgramClasses(TestRunner.class)
+ .addKeepMainRule(TestRunner.class)
+ .setMinApi(AndroidApiLevel.B)
+ .run(parameters.getRuntime(), TestRunner.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void compileSimpleCodeWithSystemJdk() throws Exception {
+ // Don't run duplicate tests (library is not used by the test).
+ assumeTrue(library == CfVm.JDK8);
+
+ ClassFileResourceProvider provider = JdkClassFileProvider.fromSystemJdk();
+
+ testForR8(parameters.getBackend())
+ .addLibraryProvider(provider)
+ .addProgramClasses(TestRunner.class)
+ .addKeepMainRule(TestRunner.class)
+ .setMinApi(AndroidApiLevel.B)
+ .run(parameters.getRuntime(), TestRunner.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void compileCodeWithJava9APIUsage() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromJdkHome(TestRuntime.getCheckedInJDKHome(library));
+
+ TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder =
+ testForR8(parameters.getBackend())
+ .addLibraryProvider(provider)
+ .addProgramClassFileData(dumpClassWhichUseJava9Flow())
+ .addKeepMainRule("MySubscriber");
+
+ if (library == CfVm.JDK8) {
+ try {
+ // java.util.concurrent.Flow$Subscriber is not present in JDK8 rt.jar.
+ testBuilder.compileWithExpectedDiagnostics(
+ diagnotics -> {
+ diagnotics.assertErrorsCount(1);
+ diagnotics.assertWarningsCount(1);
+ diagnotics.assertInfosCount(0);
+ assertThat(
+ diagnotics.getErrors().get(0).getDiagnosticMessage(),
+ StringContains.containsString("java.util.concurrent.Flow$Subscriber"));
+ assertThat(
+ diagnotics.getWarnings().get(0).getDiagnosticMessage(),
+ StringContains.containsString("java.util.concurrent.Flow$Subscriber"));
+ });
+ } catch (CompilationFailedException e) {
+ return;
+ }
+ fail("Expected compilation error");
+ } else {
+ if (parameters.getRuntime().isDex()) {
+ // java.util.concurrent.Flow$Subscriber is not present on Android.
+ testBuilder
+ .run(parameters.getRuntime(), "MySubscriber")
+ .assertFailureWithErrorThatMatches(
+ anyOf(
+ // Dalvik 4.0.4
+ containsString("java.lang.NoClassDefFoundError: MySubscriber"),
+ // Other Dalviks.
+ containsString(
+ "java.lang.ClassNotFoundException: Didn't find class \"MySubscriber\""),
+ // Art.
+ containsString(
+ "java.lang.NoClassDefFoundError: "
+ + "Failed resolution of: Ljava/util/concurrent/Flow$Subscriber;")));
+ } else {
+ if (parameters.getRuntime().asCf().getVm() == CfVm.JDK8) {
+ // java.util.concurrent.Flow$Subscriber not present in JDK8.
+ testBuilder
+ .run(parameters.getRuntime(), "MySubscriber")
+ .assertFailureWithErrorThatMatches(
+ containsString("Could not find or load main class MySubscriber"));
+
+ } else {
+ // java.util.concurrent.Flow$Subscriber present in JDK9+.
+ testBuilder
+ .run(parameters.getRuntime(), "MySubscriber")
+ .disassemble()
+ .assertSuccessWithOutputLines("Got : 1", "Got : 2", "Got : 3", "Done");
+ }
+ }
+ }
+
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ /*
+ * The code below is from compiling the following source with javac from OpenJDK 9.0.4 with
+ * options:
+ *
+ * -source 1.8 -target 1.8
+ *
+ * Note that -source 1.8 on on Java 9 will use the builtin boot class path (including
+ * java.util.concurrent.Flow) if no explicit boot classpath is specified.
+ *
+ * import java.util.List;
+ * import java.lang.Thread;
+ * import java.util.concurrent.Flow.Subscriber;
+ * import java.util.concurrent.Flow.Subscription;
+ * import java.util.concurrent.SubmissionPublisher;
+ * import java.util.concurrent.locks.Condition;
+ * import java.util.concurrent.locks.Lock;
+ * import java.util.concurrent.locks.ReentrantLock;
+ *
+ * public class MySubscriber<T> implements Subscriber<T> {
+ * final static Lock lock = new ReentrantLock();
+ * final static Condition done = lock.newCondition();
+ *
+ * private Subscription subscription;
+ *
+ * @Override
+ * public void onSubscribe(Subscription subscription) {
+ * this.subscription = subscription;
+ * subscription.request(1);
+ * }
+ *
+ * @Override
+ * public void onNext(T item) {
+ * System.out.println("Got : " + item);
+ * subscription.request(1);
+ * }
+ * @Override
+ * public void onError(Throwable t) {
+ * t.printStackTrace();
+ * }
+ *
+ * @Override
+ * public void onComplete() {
+ * System.out.println("Done");
+ * signalCondition(done);
+ * }
+ *
+ * public static void awaitCondition(Condition condition) throws Exception {
+ * lock.lock();
+ * try {
+ * condition.await();
+ * } finally {
+ * lock.unlock();
+ * }
+ * }
+ *
+ * public static void signalCondition(Condition condition) {
+ * lock.lock();
+ * try {
+ * condition.signal();
+ * } finally {
+ * lock.unlock();
+ * }
+ * }
+ *
+ * public static void main(String[] args) throws Exception {
+ * SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
+ * MySubscriber<String> subscriber = new MySubscriber<>();
+ * publisher.subscribe(subscriber);
+ * List<String> items = List.of("1", "2", "3");
+ *
+ * items.forEach(publisher::submit);
+ * publisher.close();
+ *
+ * awaitCondition(done);
+ * }
+ * }
+ *
+ */
+ public static byte[] dumpClassWhichUseJava9Flow() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "MySubscriber",
+ "<T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/concurrent/Flow$Subscriber<TT;>;",
+ "java/lang/Object",
+ new String[] {"java/util/concurrent/Flow$Subscriber"});
+
+ classWriter.visitSource("MySubscriber.java", null);
+
+ classWriter.visitInnerClass(
+ "java/util/concurrent/Flow$Subscription",
+ "java/util/concurrent/Flow",
+ "Subscription",
+ ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
+
+ classWriter.visitInnerClass(
+ "java/util/concurrent/Flow$Subscriber",
+ "java/util/concurrent/Flow",
+ "Subscriber",
+ ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
+
+ classWriter.visitInnerClass(
+ "java/lang/invoke/MethodHandles$Lookup",
+ "java/lang/invoke/MethodHandles",
+ "Lookup",
+ ACC_PUBLIC | ACC_FINAL | ACC_STATIC);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_FINAL | ACC_STATIC, "lock", "Ljava/util/concurrent/locks/Lock;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_FINAL | ACC_STATIC, "done", "Ljava/util/concurrent/locks/Condition;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_PRIVATE, "subscription", "Ljava/util/concurrent/Flow$Subscription;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(10, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC, "onSubscribe", "(Ljava/util/concurrent/Flow$Subscription;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(18, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitFieldInsn(
+ PUTFIELD, "MySubscriber", "subscription", "Ljava/util/concurrent/Flow$Subscription;");
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(19, label1);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitInsn(LCONST_1);
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/Flow$Subscription", "request", "(J)V", true);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(20, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PUBLIC, "onNext", "(Ljava/lang/Object;)V", "(TT;)V", null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(24, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+ methodVisitor.visitLdcInsn("Got : ");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ false);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ "(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
+ false);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(25, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(
+ GETFIELD, "MySubscriber", "subscription", "Ljava/util/concurrent/Flow$Subscription;");
+ methodVisitor.visitInsn(LCONST_1);
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/Flow$Subscription", "request", "(J)V", true);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(26, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PUBLIC, "onError", "(Ljava/lang/Throwable;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(29, label0);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Throwable", "printStackTrace", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(30, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "onComplete", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(34, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("Done");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(35, label1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/locks/Condition;");
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "MySubscriber",
+ "signalCondition",
+ "(Ljava/util/concurrent/locks/Condition;)V",
+ false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(36, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "awaitCondition",
+ "(Ljava/util/concurrent/locks/Condition;)V",
+ null,
+ new String[] {"java/lang/Exception"});
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ Label label1 = new Label();
+ Label label2 = new Label();
+ methodVisitor.visitTryCatchBlock(label0, label1, label2, null);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(39, label3);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "lock", "()V", true);
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(41, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Condition", "await", "()V", true);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(43, label1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(44, label4);
+ Label label5 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label5);
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(43, label2);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(45, label5);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "signalCondition",
+ "(Ljava/util/concurrent/locks/Condition;)V",
+ null,
+ null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ Label label1 = new Label();
+ Label label2 = new Label();
+ methodVisitor.visitTryCatchBlock(label0, label1, label2, null);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(48, label3);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "lock", "()V", true);
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(50, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Condition", "signal", "()V", true);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(52, label1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(53, label4);
+ Label label5 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label5);
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(52, label2);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(54, label5);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "main",
+ "([Ljava/lang/String;)V",
+ null,
+ new String[] {"java/lang/Exception"});
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(57, label0);
+ methodVisitor.visitTypeInsn(NEW, "java/util/concurrent/SubmissionPublisher");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL, "java/util/concurrent/SubmissionPublisher", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(58, label1);
+ methodVisitor.visitTypeInsn(NEW, "MySubscriber");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "MySubscriber", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ASTORE, 2);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(59, label2);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitVarInsn(ALOAD, 2);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/util/concurrent/SubmissionPublisher",
+ "subscribe",
+ "(Ljava/util/concurrent/Flow$Subscriber;)V",
+ false);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(60, label3);
+ methodVisitor.visitLdcInsn("1");
+ methodVisitor.visitLdcInsn("2");
+ methodVisitor.visitLdcInsn("3");
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "java/util/List",
+ "of",
+ "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;",
+ true);
+ methodVisitor.visitVarInsn(ASTORE, 3);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(62, label4);
+ methodVisitor.visitVarInsn(ALOAD, 3);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "java/util/Objects",
+ "requireNonNull",
+ "(Ljava/lang/Object;)Ljava/lang/Object;",
+ false);
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitInvokeDynamicInsn(
+ "accept",
+ "(Ljava/util/concurrent/SubmissionPublisher;)Ljava/util/function/Consumer;",
+ new Handle(
+ Opcodes.H_INVOKESTATIC,
+ "java/lang/invoke/LambdaMetafactory",
+ "metafactory",
+ "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
+ false),
+ new Object[] {
+ Type.getType("(Ljava/lang/Object;)V"),
+ new Handle(
+ Opcodes.H_INVOKEVIRTUAL,
+ "java/util/concurrent/SubmissionPublisher",
+ "submit",
+ "(Ljava/lang/Object;)I",
+ false),
+ Type.getType("(Ljava/lang/String;)V")
+ });
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/List", "forEach", "(Ljava/util/function/Consumer;)V", true);
+ Label label5 = new Label();
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(63, label5);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/util/concurrent/SubmissionPublisher", "close", "()V", false);
+ Label label6 = new Label();
+ methodVisitor.visitLabel(label6);
+ methodVisitor.visitLineNumber(65, label6);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/locks/Condition;");
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "MySubscriber",
+ "awaitCondition",
+ "(Ljava/util/concurrent/locks/Condition;)V",
+ false);
+ Label label7 = new Label();
+ methodVisitor.visitLabel(label7);
+ methodVisitor.visitLineNumber(66, label7);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 4);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(11, label0);
+ methodVisitor.visitTypeInsn(NEW, "java/util/concurrent/locks/ReentrantLock");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL, "java/util/concurrent/locks/ReentrantLock", "<init>", "()V", false);
+ methodVisitor.visitFieldInsn(
+ PUTSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(12, label1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE,
+ "java/util/concurrent/locks/Lock",
+ "newCondition",
+ "()Ljava/util/concurrent/locks/Condition;",
+ true);
+ methodVisitor.visitFieldInsn(
+ PUTSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/locks/Condition;");
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 0);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+
+ public static class TestRunner {
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java
new file mode 100644
index 0000000..d6f38af
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java
@@ -0,0 +1,120 @@
+package com.android.tools.r8;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.objectweb.asm.Opcodes;
+
+public class JdkClassFileProviderTest extends TestBase implements Opcodes {
+
+ @Test
+ public void testInvalid8RuntimeClassPath() throws Exception {
+ Path path = temp.newFolder().toPath();
+ try {
+ JdkClassFileProvider.fromJdkHome(path);
+ fail("Not supposed to succeed");
+ } catch (IOException e) {
+ assertThat(e.toString(), containsString(path.toString()));
+ assertThat(e.toString(), containsString("does not look like a Java home"));
+ }
+ }
+
+ @Test
+ public void testJdk8JavHome() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8));
+ assertJavaLangObject(provider);
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void testJdk8RuntimeClassPath() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromJavaRuntimeJar(
+ ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8)
+ .resolve("jre")
+ .resolve("lib")
+ .resolve("rt.jar"));
+ assertJavaLangObject(provider);
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void testJdk8SystemModules() throws Exception {
+ try {
+ JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8));
+ fail("Not supposed to succeed");
+ } catch (NoSuchFileException e) {
+ assertThat(e.toString(), containsString("lib/jrt-fs.jar"));
+ }
+ }
+
+ @Test
+ public void testJdk9JavaHome() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK9));
+ assertJavaLangObject(provider);
+ assertJavaUtilConcurrentFlowSubscriber(provider);
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void testJdk9SystemModules() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK9));
+ assertJavaLangObject(provider);
+ assertJavaUtilConcurrentFlowSubscriber(provider);
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void testJdk11JavaHome() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK11));
+ assertJavaLangObject(provider);
+ assertJavaUtilConcurrentFlowSubscriber(provider);
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void testJdk11SystemModules() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK11));
+ assertJavaLangObject(provider);
+ assertJavaUtilConcurrentFlowSubscriber(provider);
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ private void assertJavaLangObject(ClassFileResourceProvider provider) throws Exception {
+ assertTrue(provider.getClassDescriptors().contains("Ljava/lang/Object;"));
+ assertTrue(
+ ByteStreams.toByteArray(provider.getProgramResource("Ljava/lang/Object;").getByteStream())
+ .length
+ > 0);
+ }
+
+ private void assertJavaUtilConcurrentFlowSubscriber(ClassFileResourceProvider provider)
+ throws Exception {
+ assertTrue(provider.getClassDescriptors().contains("Ljava/util/concurrent/Flow$Subscriber;"));
+ assertTrue(
+ ByteStreams.toByteArray(
+ provider
+ .getProgramResource("Ljava/util/concurrent/Flow$Subscriber;")
+ .getByteStream())
+ .length
+ > 0);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 4c17ac7..d834ebc 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -69,6 +69,10 @@
return this.ordinal() <= other.ordinal();
}
+ public boolean hasModularRuntime() {
+ return this != JDK8;
+ }
+
@Override
public String toString() {
return name;
@@ -109,10 +113,12 @@
return CHECKED_IN_JDKS.containsKey(jdk);
}
- public static Path getCheckInJDKPathFor(CfVm jdk) {
- return Paths.get("third_party", "openjdk")
- .resolve(CHECKED_IN_JDKS.get(jdk))
- .resolve(Paths.get("bin", "java"));
+ public static Path getCheckedInJDKHome(CfVm jdk) {
+ return Paths.get("third_party", "openjdk").resolve(CHECKED_IN_JDKS.get(jdk));
+ }
+
+ public static Path getCheckedInJDKPathFor(CfVm jdk) {
+ return getCheckedInJDKHome(jdk).resolve(Paths.get("bin", "java"));
}
public static TestRuntime getDefaultJavaRuntime() {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 38fb7c7..0445166 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1273,8 +1273,7 @@
List<String> extraOptions,
Path... filesToCompile)
throws IOException {
- String jvm = runtime == null ? getSystemJavaExecutable() : getJavaExecutable(runtime);
- List<String> cmdline = new ArrayList<String>(Arrays.asList(jvm));
+ List<String> cmdline = new ArrayList<>(Arrays.asList(getJavaExecutable(runtime)));
cmdline.add("-jar");
cmdline.add(KT_PRELOADER);
cmdline.add("org.jetbrains.kotlin.preloading.Preloader");
@@ -1419,7 +1418,7 @@
public static String getJavaExecutable(CfVm runtime) {
if (TestRuntime.isCheckedInJDK(runtime)) {
- return TestRuntime.getCheckInJDKPathFor(runtime).toString();
+ return TestRuntime.getCheckedInJDKPathFor(runtime).toString();
} else {
// TODO(b/127785410): Always assume a non-null runtime.
assert runtime == null || TestParametersBuilder.isSystemJdk(runtime);
@@ -1427,6 +1426,10 @@
}
}
+ public static Path getJavaHome(CfVm runtime) {
+ return TestRuntime.getCheckedInJDKHome(runtime);
+ }
+
public static ProcessResult runArtRaw(ArtCommandBuilder builder) throws IOException {
return runArtProcessRaw(builder);
}