Internalize java stubs to simplify lookup

Bug: b/270105162
Change-Id: I91c1b0b602aaa7fbc947da2142952d4a81fb974e
diff --git a/src/test/java/com/android/tools/r8/Jdk11TestUtils.java b/src/test/java/com/android/tools/r8/Jdk11TestUtils.java
deleted file mode 100644
index 26da974..0000000
--- a/src/test/java/com/android/tools/r8/Jdk11TestUtils.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2021, 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 static com.android.tools.r8.TestRuntime.getCheckedInJdk8;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import org.junit.Assume;
-import org.junit.rules.TemporaryFolder;
-
-public class Jdk11TestUtils {
-
-  public static ThrowableConsumer<R8FullTestBuilder> addJdk11LibraryFiles(
-      TemporaryFolder temporaryFolder) {
-    return builder -> builder.addLibraryFiles(getJdk11LibraryFiles(temporaryFolder));
-  }
-
-  public static Path[] getJdk11LibraryFiles(TemporaryFolder temp) throws IOException {
-    Assume.assumeFalse(ToolHelper.isWindows());
-    // TODO(b/180553597): Add JDK-11 runtime jar instead. As a temporary solution we use the JDK-8
-    //  runtime with additional stubs.
-    Path stubs =
-        JavaCompilerTool.create(getCheckedInJdk8(), temp)
-            .addSourceFiles(Paths.get("src", "test", "javaStubs", "StringConcatFactory.java"))
-            .addSourceFiles(Paths.get("src", "test", "javaStubs", "VarHandle.java"))
-            .compile();
-    return new Path[] {stubs, ToolHelper.getJava8RuntimeJar()};
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/Jdk9TestUtils.java b/src/test/java/com/android/tools/r8/Jdk9TestUtils.java
index 891ba09..8b6bfbb 100644
--- a/src/test/java/com/android/tools/r8/Jdk9TestUtils.java
+++ b/src/test/java/com/android/tools/r8/Jdk9TestUtils.java
@@ -4,12 +4,8 @@
 
 package com.android.tools.r8;
 
-import static com.android.tools.r8.TestRuntime.getCheckedInJdk8;
+import static com.android.tools.r8.desugar.LibraryFilesHelper.getJdk9LibraryFiles;
 
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import org.junit.Assume;
 import org.junit.rules.TemporaryFolder;
 
 public class Jdk9TestUtils {
@@ -19,15 +15,4 @@
     return builder -> builder.addLibraryFiles(getJdk9LibraryFiles(temporaryFolder));
   }
 
-  public static Path[] getJdk9LibraryFiles(TemporaryFolder temp) throws IOException {
-    Assume.assumeFalse(ToolHelper.isWindows());
-    // TODO(b/180553597): Add JDK-9 runtime jar instead. As a temporary solution we use the JDK-8
-    //  runtime with additional stubs.
-    Path stubs =
-        JavaCompilerTool.create(getCheckedInJdk8(), temp)
-            .addSourceFiles(Paths.get("src", "test", "javaStubs", "StringConcatFactory.java"))
-            .addSourceFiles(Paths.get("src", "test", "javaStubs", "VarHandle.java"))
-            .compile();
-    return new Path[] {stubs, ToolHelper.getJava8RuntimeJar()};
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
index 79b8799..050fe1c 100644
--- a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.desugar.LibraryFilesHelper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -50,7 +51,7 @@
       for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
         builder = transformation.apply(builder);
       }
-      builder.addLibraryFiles(Jdk9TestUtils.getJdk9LibraryFiles(temp));
+      builder.addLibraryFiles(LibraryFilesHelper.getJdk9LibraryFiles(temp));
       R8Command command =
           builder.addProgramFiles(inputFile).setOutput(out, OutputMode.ClassFile).build();
       ToolHelper.runR8(command, this::combinedOptionConsumer);
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 596a13d..ea8e96c 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1123,7 +1123,8 @@
 
   public static Path getJdwpTestsCfJarPath(AndroidApiLevel minSdk) {
     if (minSdk.getLevel() >= AndroidApiLevel.N.getLevel()) {
-      return Paths.get("third_party", "jdwp-tests", "apache-harmony-jdwp-tests-host.jar");
+      return Paths.get(
+          ToolHelper.THIRD_PARTY_DIR, "jdwp-tests", "apache-harmony-jdwp-tests-host.jar");
     } else {
       return Paths.get(ToolHelper.BUILD_DIR, "libs", "jdwp-tests-preN.jar");
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/LibraryFilesHelper.java b/src/test/java/com/android/tools/r8/desugar/LibraryFilesHelper.java
new file mode 100644
index 0000000..0e07bf4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/LibraryFilesHelper.java
@@ -0,0 +1,193 @@
+// Copyright (c) 2023, 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.desugar;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.ConsumerUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ObjectArrays;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import org.junit.Assume;
+import org.junit.rules.TemporaryFolder;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+
+public class LibraryFilesHelper implements Opcodes {
+
+  public static Path[] getJdk9LibraryFiles(TemporaryFolder temp) throws Exception {
+    // TODO(b/270105162): We should be able to run this on windows.
+    Assume.assumeFalse(ToolHelper.isWindows());
+    // TODO(b/180553597): Add JDK-9 runtime jar instead. As a temporary solution we use the JDK-8
+    //  runtime with additional stubs.
+    // We use jdk-8 for compilation because in jdk-9 and higher we would need to deal with the
+    // module patching logic.
+    Path generatedJar = temp.newFolder().toPath().resolve("stubs.jar");
+    ZipBuilder builder = ZipBuilder.builder(generatedJar);
+    addStringConcatFactory(builder);
+    addVarHandle(builder);
+    builder.build();
+    return new Path[] {generatedJar, ToolHelper.getJava8RuntimeJar()};
+  }
+
+  public static Path[] getJdk11LibraryFiles(TemporaryFolder temp) throws Exception {
+    Assume.assumeFalse(ToolHelper.isWindows());
+    // TODO(b/180553597): Add JDK-11 runtime jar instead. As a temporary solution we use the JDK-8
+    //  runtime with additional stubs.
+    return getJdk9LibraryFiles(temp);
+  }
+
+  public static Path[] getJdk15LibraryFiles(TemporaryFolder temp) throws Exception {
+    // TODO(b/270105162): We should be able to run this on windows.
+    Assume.assumeFalse(ToolHelper.isWindows());
+    // TODO(b/169645628): Add JDK-15 runtime jar instead. As a temporary solution we use the jdk 8
+    //   runtime with additional stubs.
+    Path generatedJar = temp.newFolder().toPath().resolve("stubs.jar");
+    ZipBuilder builder = ZipBuilder.builder(generatedJar);
+    addRecord(builder);
+    addRecordComponent(builder);
+    addObjectsMethod(builder);
+    addTypeDescriptor(builder);
+    builder.build();
+    return ObjectArrays.concat(getJdk9LibraryFiles(temp), new Path[] {generatedJar}, Path.class);
+  }
+
+  // Generates a class file for:
+  // <pre>
+  // public class ObjectMethods {}
+  // </pre>
+  private static void addObjectsMethod(ZipBuilder builder) throws Exception {
+    addClassToZipBuilder(
+        builder, ACC_PUBLIC, "java/lang/runtime/ObjectMethods", ConsumerUtils.emptyConsumer());
+  }
+
+  // Generates a class file for:
+  // <pre>
+  // public interface TypeDescriptor {
+  //   String descriptorString();
+  // }
+  // </pre>
+  private static void addTypeDescriptor(ZipBuilder builder) throws Exception {
+    addClassToZipBuilder(
+        builder,
+        ACC_PUBLIC | ACC_INTERFACE | ACC_ABSTRACT,
+        "java/lang/invoke/TypeDescriptor",
+        methodAdder ->
+            methodAdder.add(
+                ACC_PUBLIC | ACC_ABSTRACT,
+                "descriptorString",
+                methodDescriptor(false, String.class)));
+  }
+
+  // Generates a class file for:
+  // <pre>
+  // public class StringConcatFactory {}
+  // </pre>
+  private static void addStringConcatFactory(ZipBuilder builder) throws Exception {
+    addClassToZipBuilder(
+        builder, ACC_PUBLIC, "java/lang/invoke/StringConcatFactory", ConsumerUtils.emptyConsumer());
+  }
+
+  // Generates a class file for:
+  // <pre>
+  // public class VarHandle {}
+  // </pre>
+  private static void addVarHandle(ZipBuilder builder) throws Exception {
+    addClassToZipBuilder(
+        builder, ACC_PUBLIC, "java/lang/invoke/VarHandle", ConsumerUtils.emptyConsumer());
+  }
+
+  // Generates a class file for:
+  // <pre>
+  // public abstract class Record {
+  //   protected Record() {}
+  //
+  //   public abstract boolean equals(Object obj);
+  //
+  //   public abstract int hashCode();
+  //
+  //   public abstract String toString();
+  // }
+  // </pre>
+  private static void addRecord(ZipBuilder builder) throws Exception {
+    addClassToZipBuilder(
+        builder,
+        ACC_PUBLIC | ACC_ABSTRACT,
+        "java/lang/Record",
+        methodAdder -> {
+          methodAdder.add(ACC_PROTECTED, "<init>", "()V");
+          methodAdder.add(
+              ACC_PUBLIC | ACC_ABSTRACT,
+              "equals",
+              methodDescriptor(false, Object.class, boolean.class));
+          methodAdder.add(
+              ACC_PUBLIC | ACC_ABSTRACT, "hashCode", methodDescriptor(false, int.class));
+          methodAdder.add(
+              ACC_PUBLIC | ACC_ABSTRACT, "toString", methodDescriptor(false, String.class));
+        });
+  }
+
+  // Generates a class file for:
+  // <pre>
+  // public class RecordComponent {}
+  // </pre>
+  private static void addRecordComponent(ZipBuilder builder) throws Exception {
+    addClassToZipBuilder(
+        builder, ACC_PUBLIC, "java/lang/reflect/RecordComponent", ConsumerUtils.emptyConsumer());
+  }
+
+  // Generates a class file for:
+  // <pre>
+  // public interface Supplier {
+  //   Object get();
+  // }
+  // </pre>
+  public static byte[] getSupplier() {
+    return createClass(
+        ACC_PUBLIC | ACC_INTERFACE | ACC_ABSTRACT,
+        "java/util/function/Supplier",
+        methodAdder ->
+            methodAdder.add(
+                ACC_PUBLIC | ACC_ABSTRACT, "get", methodDescriptor(false, Object.class)));
+  }
+
+  private static void addClassToZipBuilder(
+      ZipBuilder builder, int access, String binaryName, Consumer<MethodAdder> consumer)
+      throws Exception {
+    builder.addBytes(binaryName + ".class", createClass(access, binaryName, consumer));
+  }
+
+  @FunctionalInterface
+  public interface MethodAdder {
+    void add(int access, String name, String descriptor);
+  }
+
+  public static String descriptor(Class<?> clazz) {
+    return DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName());
+  }
+
+  public static String methodDescriptor(boolean isVoid, Class<?>... clazz) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("(");
+    for (int i = 0; i < clazz.length - (isVoid ? 0 : 1); i++) {
+      sb.append(descriptor(clazz[i]));
+    }
+    sb.append(")");
+    if (!isVoid) {
+      sb.append(descriptor(clazz[clazz.length - 1]));
+    }
+    return sb.toString();
+  }
+
+  public static byte[] createClass(int access, String binaryName, Consumer<MethodAdder> consumer) {
+    ClassWriter cw = new ClassWriter(0);
+    cw.visit(V1_8, access, binaryName, null, "java/lang/Object", null);
+
+    consumer.accept(
+        (access1, name, descriptor1) -> cw.visitMethod(access1, name, descriptor1, null, null));
+    cw.visitEnd();
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TestLibraryDesugaringSpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TestLibraryDesugaringSpecification.java
index 6eabf06..3e42517 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TestLibraryDesugaringSpecification.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TestLibraryDesugaringSpecification.java
@@ -9,36 +9,34 @@
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
 
 import com.android.tools.r8.JavaCompilerTool;
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.zip.ZipFile;
 import org.junit.rules.TemporaryFolder;
 
 public class Jdk11TestLibraryDesugaringSpecification {
 
-  private static final String EXTENSION_STRING = "build/libs/java_base_extension.jar";
-
   private static final Path JDK_11_JAVA_BASE_EXTENSION_FILES_DIR =
       Paths.get(
           ToolHelper.THIRD_PARTY_DIR, "openjdk/jdk-11-test/lib/testlibrary/bootlib/java.base");
   private static final Path JDK_11_TESTLIBRARY_FILES_DIR =
       Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk/jdk-11-test/lib/testlibrary/jdk");
 
-  public static Path EXTENSION_PATH;
+  public static Path EXTENSION_PATH = Paths.get(ToolHelper.LIBS_DIR + "java_base_extension.jar");
 
   public static LibraryDesugaringSpecification JDK8_JAVA_BASE_EXT;
   public static LibraryDesugaringSpecification JDK11_JAVA_BASE_EXT;
@@ -60,7 +58,6 @@
       // The library configuration is not available on windows. Do not run anything.
       return;
     }
-    EXTENSION_PATH = Paths.get(EXTENSION_STRING);
     ensureJavaBaseExtensionsCompiled();
     JDK8_JAVA_BASE_EXT = createSpecification("JDK8_JAVA_BASE_EXT", JDK8);
     JDK11_JAVA_BASE_EXT = createSpecification("JDK11_JAVA_BASE_EXT", JDK11);
@@ -86,11 +83,10 @@
     if (Files.exists(EXTENSION_PATH)) {
       return;
     }
-
     TemporaryFolder folder =
         new TemporaryFolder(ToolHelper.isLinux() ? null : Paths.get("build", "tmp").toFile());
     folder.create();
-    Path output = folder.newFolder("jdk11Ext").toPath();
+    Path output = folder.newFolder().toPath().resolve("temp.jar");
     List<String> options =
         Arrays.asList(
             "--add-reads",
@@ -103,30 +99,14 @@
         .addSourceFiles(getExtensionsFiles())
         .setOutputPath(output)
         .compile();
-    Path[] toCompile = getAllFilesWithSuffixInDirectory(output, CLASS_EXTENSION);
-    assert toCompile.length > 0;
-
-    // Jar the contents.
-    List<String> cmdline = new ArrayList<>();
-    cmdline.add(TestRuntime.getCheckedInJdk11().getJavaExecutable().getParent() + "/jar");
-    cmdline.add("cf");
-    cmdline.add("tmp.jar");
-    for (Path compile : toCompile) {
-      cmdline.add(output.relativize(compile).toString());
-    }
-    ProcessBuilder builder = new ProcessBuilder(cmdline);
-    builder.directory(output.toFile());
-    ProcessResult result = ToolHelper.runProcess(builder);
-    assert result.exitCode == 0;
+    assert new ZipFile(output.toFile()).size() > 0;
 
     // Move the result into the build/libs folder.
-    List<String> cmdlineMv = new ArrayList<>();
-    cmdlineMv.add("mv");
-    cmdlineMv.add(output.resolve("tmp.jar").toString());
-    cmdlineMv.add(EXTENSION_STRING);
-    ProcessResult resultMv = ToolHelper.runProcess(new ProcessBuilder(cmdlineMv));
-    assert resultMv.exitCode == 0;
-
+    Files.move(
+        output,
+        EXTENSION_PATH,
+        StandardCopyOption.REPLACE_EXISTING,
+        StandardCopyOption.ATOMIC_MOVE);
     folder.delete();
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java17R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java17R8BootstrapTest.java
index 8d2a21e..70c3f2c 100644
--- a/src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java17R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java17R8BootstrapTest.java
@@ -7,13 +7,13 @@
 import static com.android.tools.r8.desugar.r8bootstrap.JavaBootstrapUtils.MAIN_KEEP;
 import static junit.framework.TestCase.assertEquals;
 
-import com.android.tools.r8.Jdk11TestUtils;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
+import com.android.tools.r8.desugar.LibraryFilesHelper;
 import com.android.tools.r8.examples.hello.HelloTestRunner;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
@@ -70,7 +70,7 @@
   private static Path compileR8(boolean desugar) throws Exception {
     return JavaBootstrapUtils.compileR8(
         ToolHelper.R8_WITH_RELOCATED_DEPS_17_JAR,
-        Jdk11TestUtils.getJdk11LibraryFiles(getStaticTemp()),
+        LibraryFilesHelper.getJdk11LibraryFiles(getStaticTemp()),
         desugar);
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
index 1294b2d..8be5a1d 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.desugar.LibraryFilesHelper;
 import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -83,7 +84,7 @@
             .addKeepMainRule(MAIN_TYPE);
     if (parameters.isCfRuntime()) {
       builder
-          .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+          .addLibraryFiles(LibraryFilesHelper.getJdk15LibraryFiles(temp))
           .compile()
           .inspect(RecordTestUtils::assertRecordsAreRecords)
           .run(parameters.getRuntime(), MAIN_TYPE)
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
index 055eff8..6a63449 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.desugar.LibraryFilesHelper;
 import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -57,9 +58,9 @@
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters)
         .addKeepMainRule(MAIN_TYPE)
-        .addKeepRules("-keepattributes *")
+        .addKeepAllAttributes()
         .addKeepRules("-keep class * extends java.lang.Record { private final <fields>; }")
-        .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+        .addLibraryFiles(LibraryFilesHelper.getJdk15LibraryFiles(temp))
         .compile()
         .inspect(RecordTestUtils::assertRecordsAreRecords)
         .enableJVMPreview()
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
index 7008bfe..35725b8 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
@@ -4,13 +4,12 @@
 
 package com.android.tools.r8.desugar.records;
 
-import static com.android.tools.r8.TestRuntime.getCheckedInJdk8;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.JavaCompilerTool;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.LibraryFilesHelper;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -24,7 +23,6 @@
 import java.util.List;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
-import org.junit.Assume;
 import org.junit.rules.TemporaryFolder;
 
 /**
@@ -40,22 +38,8 @@
     return Paths.get(ToolHelper.TESTS_BUILD_DIR, EXAMPLE_FOLDER, RECORD_FOLDER + ".jar");
   }
 
-
-  public static Path[] getJdk15LibraryFiles(TemporaryFolder temp) throws IOException {
-    Assume.assumeFalse(ToolHelper.isWindows());
-    // TODO(b/169645628): Add JDK-15 runtime jar instead. As a temporary solution we use the jdk 8
-    // runtime with additional stubs.
-    // We use jdk-8 for compilation because in jdk-9 and higher we would need to deal with the
-    // module patching logic.
-    Path recordStubs =
-        JavaCompilerTool.create(getCheckedInJdk8(), temp)
-            .addSourceFiles(Paths.get("src", "test", "javaStubs", "Record.java"))
-            .addSourceFiles(Paths.get("src", "test", "javaStubs", "ObjectMethods.java"))
-            .addSourceFiles(Paths.get("src", "test", "javaStubs", "StringConcatFactory.java"))
-            .addSourceFiles(Paths.get("src", "test", "javaStubs", "TypeDescriptor.java"))
-            .addSourceFiles(Paths.get("src", "test", "javaStubs", "RecordComponent.java"))
-            .compile();
-    return new Path[] {recordStubs, ToolHelper.getJava8RuntimeJar()};
+  public static Path[] getJdk15LibraryFiles(TemporaryFolder temp) throws Exception {
+    return LibraryFilesHelper.getJdk15LibraryFiles(temp);
   }
 
   public static byte[][] getProgramData(String mainClassSimpleName) {
diff --git a/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/GetDeclaredMethodsErrorRemovalTest.java b/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/GetDeclaredMethodsErrorRemovalTest.java
index cd6ac90..c94680d 100644
--- a/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/GetDeclaredMethodsErrorRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/GetDeclaredMethodsErrorRemovalTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.desugar.softverificationerrorremoval;
 
-import static com.android.tools.r8.TestRuntime.CfRuntime.getCheckedInJdk8;
 import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.D8TestRunResult;
@@ -12,8 +11,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
-import java.nio.file.Path;
-import java.nio.file.Paths;
+import com.android.tools.r8.desugar.LibraryFilesHelper;
 import java.util.Arrays;
 import java.util.function.Supplier;
 import org.junit.Test;
@@ -63,13 +61,9 @@
 
   @Test
   public void testWithJavaStub() throws Exception {
-    Path stubs =
-        javac(getCheckedInJdk8())
-            .addSourceFiles(Paths.get("src/test/javaStubs/Supplier.java"))
-            .compile();
     testForD8()
         .addInnerClasses(GetDeclaredMethodsErrorRemovalTest.class)
-        .addProgramFiles(stubs)
+        .addProgramClassFileData(LibraryFilesHelper.getSupplier())
         .setMinApi(parameters)
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/SoftVerificationErrorRemovalTest.java b/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/SoftVerificationErrorRemovalTest.java
index 2fac1a6..ee76b7f 100644
--- a/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/SoftVerificationErrorRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/SoftVerificationErrorRemovalTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.desugar.softverificationerrorremoval;
 
-import static com.android.tools.r8.TestRuntime.CfRuntime.getCheckedInJdk8;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.D8TestRunResult;
@@ -12,8 +11,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
-import java.nio.file.Path;
-import java.nio.file.Paths;
+import com.android.tools.r8.desugar.LibraryFilesHelper;
 import java.util.function.Supplier;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -60,14 +58,10 @@
 
   @Test
   public void testWithJavaStub() throws Exception {
-    Path stubs =
-        javac(getCheckedInJdk8())
-            .addSourceFiles(Paths.get("src/test/javaStubs/Supplier.java"))
-            .compile();
     D8TestRunResult run =
         testForD8()
             .addInnerClasses(SoftVerificationErrorRemovalTest.class)
-            .addProgramFiles(stubs)
+            .addProgramClassFileData(LibraryFilesHelper.getSupplier())
             .setMinApi(parameters)
             .compile()
             .run(parameters.getRuntime(), TestClass.class);
diff --git a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
index d843a4c..80ce84a 100644
--- a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
+++ b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
@@ -49,9 +49,11 @@
   // Print test output for passing tests (failing tests output is always printed).
   static final boolean PRINT_STREAMS = true;
 
-  static final String RUN_SCRIPT = "tools/run-jdwp-tests.py";
-  static final String DEX_LIB = "third_party/jdwp-tests/apache-harmony-jdwp-tests-hostdex.jar";
-  static final String JAR_LIB = "third_party/jdwp-tests/apache-harmony-jdwp-tests-host.jar";
+  static final String RUN_SCRIPT = ToolHelper.TOOLS_DIR + "run-jdwp-tests.py";
+  static final String DEX_LIB =
+      ToolHelper.THIRD_PARTY_DIR + "jdwp-tests/apache-harmony-jdwp-tests-hostdex.jar";
+  static final String JAR_LIB =
+      ToolHelper.THIRD_PARTY_DIR + "jdwp-tests/apache-harmony-jdwp-tests-host.jar";
 
   interface TestPredicate {
     boolean test(DexVm dexVm, Tool tool);
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java
index 6b83be2..8554ae3 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.profile.art.completeness;
 
-import static com.android.tools.r8.Jdk11TestUtils.getJdk11LibraryFiles;
+import static com.android.tools.r8.desugar.LibraryFilesHelper.getJdk11LibraryFiles;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/javaStubs/ObjectMethods.java b/src/test/javaStubs/ObjectMethods.java
deleted file mode 100644
index 21aff6a..0000000
--- a/src/test/javaStubs/ObjectMethods.java
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (c) 2021, 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 java.lang.runtime;
-
-public class ObjectMethods {}
diff --git a/src/test/javaStubs/Record.java b/src/test/javaStubs/Record.java
deleted file mode 100644
index fcf63e6..0000000
--- a/src/test/javaStubs/Record.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2021, 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 java.lang;
-
-public abstract class Record {
-  protected Record() {}
-
-  @Override
-  public abstract boolean equals(Object obj);
-
-  @Override
-  public abstract int hashCode();
-
-  @Override
-  public abstract String toString();
-}
diff --git a/src/test/javaStubs/RecordComponent.java b/src/test/javaStubs/RecordComponent.java
deleted file mode 100644
index b3e0560..0000000
--- a/src/test/javaStubs/RecordComponent.java
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (c) 2021, 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 java.lang.reflect;
-
-public class RecordComponent {}
diff --git a/src/test/javaStubs/StringConcatFactory.java b/src/test/javaStubs/StringConcatFactory.java
deleted file mode 100644
index 9863857..0000000
--- a/src/test/javaStubs/StringConcatFactory.java
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (c) 2021, 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 java.lang.invoke;
-
-public class StringConcatFactory {}
diff --git a/src/test/javaStubs/Supplier.java b/src/test/javaStubs/Supplier.java
deleted file mode 100644
index f9c4860..0000000
--- a/src/test/javaStubs/Supplier.java
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) 2020, 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 java.util.function;
-
-public interface Supplier {
-  Object get();
-}
diff --git a/src/test/javaStubs/TypeDescriptor.java b/src/test/javaStubs/TypeDescriptor.java
deleted file mode 100644
index 643d1e0..0000000
--- a/src/test/javaStubs/TypeDescriptor.java
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) 2021, 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 java.lang.invoke;
-
-public interface TypeDescriptor {
-  String descriptorString();
-}
diff --git a/src/test/javaStubs/VarHandle.java b/src/test/javaStubs/VarHandle.java
deleted file mode 100644
index c12d32a..0000000
--- a/src/test/javaStubs/VarHandle.java
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (c) 2021, 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 java.lang.invoke;
-
-public class VarHandle {}