Fix JDK desugared lib 11 build

- Introduce an ASM rewriter to undo pre-desugaring
- Fix EmptyDesugaredLibraryTest

Bug: 203382252
Change-Id: I8937a1d0ee5469b5cae2003a64436fe71656d224
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java
index 072da5a..dc4a247 100644
--- a/src/test/java/com/android/tools/r8/L8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -8,6 +8,7 @@
 import static junit.framework.TestCase.assertTrue;
 
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryJDK11Undesugarer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -38,7 +39,7 @@
   private List<Path> additionalProgramFiles = new ArrayList<>();
   private List<byte[]> additionalProgramClassFileData = new ArrayList<>();
   private Consumer<InternalOptions> optionsModifier = ConsumerUtils.emptyConsumer();
-  private Path desugarJDKLibs = ToolHelper.getDesugarJDKLibs();
+  private Path desugarJDKLibs = DesugaredLibraryJDK11Undesugarer.undesugaredJar();
   private Path desugarJDKLibsConfiguration = null;
   private StringResource desugaredLibraryConfiguration =
       StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting());
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryJDK11Undesugarer.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryJDK11Undesugarer.java
new file mode 100644
index 0000000..f79db70
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryJDK11Undesugarer.java
@@ -0,0 +1,105 @@
+// 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.desugar.desugaredlibrary;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.StreamUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableMap;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.objectweb.asm.Opcodes;
+
+public class DesugaredLibraryJDK11Undesugarer extends DesugaredLibraryTestBase {
+
+  private static Map<String, String> ownerMap =
+      ImmutableMap.of(
+          "java/io/DesugarBufferedReader", "java/io/BufferedReader",
+          "java/io/DesugarInputStream", "java/io/InputStream");
+
+  public static void main(String[] args) throws Exception {
+    setUpDesugaredLibrary();
+    undesugaredJar();
+  }
+
+  public static Path undesugaredJar() {
+    if (!isJDK11DesugaredLibrary()) {
+      return ToolHelper.getDesugarJDKLibs();
+    }
+    Path desugaredLibJDK11Undesugared = Paths.get("build/libs/desugar_jdk_libs_11_undesugared.jar");
+    if (Files.exists(desugaredLibJDK11Undesugared)) {
+      return desugaredLibJDK11Undesugared;
+    }
+    OpenOption[] options =
+        new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+    try (ZipOutputStream out =
+        new ZipOutputStream(
+            new BufferedOutputStream(
+                Files.newOutputStream(desugaredLibJDK11Undesugared, options)))) {
+      new DesugaredLibraryJDK11Undesugarer().undesugar(ToolHelper.getDesugarJDKLibs(), out);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return desugaredLibJDK11Undesugared;
+  }
+
+  private void undesugar(Path desugaredLibraryFiles, ZipOutputStream out) throws IOException {
+    ZipUtils.iter(
+        desugaredLibraryFiles,
+        ((entry, input) -> {
+          if (!entry.getName().endsWith(".class")) {
+            return;
+          }
+          final byte[] bytes = StreamUtils.StreamToByteArrayClose(input);
+          final byte[] rewrittenBytes =
+              transformInvoke(entry.getName().substring(0, entry.getName().length() - 6), bytes);
+          ZipUtils.writeToZipStream(out, entry.getName(), rewrittenBytes, ZipEntry.STORED);
+        }));
+  }
+
+  private byte[] transformInvoke(String descriptor, byte[] bytes) {
+    return transformer(bytes, Reference.classFromDescriptor(descriptor))
+        .addMethodTransformer(getMethodTransformer())
+        .transform();
+  }
+
+  private MethodTransformer getMethodTransformer() {
+    return new MethodTransformer() {
+      @Override
+      public void visitMethodInsn(
+          int opcode, String owner, String name, String descriptor, boolean isInterface) {
+        if (opcode == Opcodes.INVOKESTATIC) {
+          for (String ownerToRewrite : ownerMap.keySet()) {
+            if (ownerToRewrite.equals(owner)) {
+              super.visitMethodInsn(
+                  Opcodes.INVOKEVIRTUAL,
+                  ownerMap.get(owner),
+                  name,
+                  withoutFirstObjectArg(descriptor),
+                  isInterface);
+              return;
+            }
+          }
+        }
+        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+      }
+    };
+  }
+
+  private String withoutFirstObjectArg(String descriptor) {
+    int i = descriptor.indexOf(";");
+    return "(" + descriptor.substring(i + 1);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java
index fc35e73..1352a82 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java
@@ -63,6 +63,13 @@
     }
   }
 
+  private int firstEmptyLevel() {
+    return isJDK11DesugaredLibrary()
+        // Some desugarings are required on all API levels including UNKNOWN.
+        ? AndroidApiLevel.NOT_SET.getLevel()
+        : AndroidApiLevel.O.getLevel();
+  }
+
   @Test
   public void testEmptyDesugaredLibrary() throws Exception {
     for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
@@ -72,8 +79,7 @@
       }
       CountingProgramConsumer programConsumer = new CountingProgramConsumer();
       ToolHelper.runL8(prepareL8Builder(apiLevel).setProgramConsumer(programConsumer).build());
-      assertEquals(
-          apiLevel.getLevel() >= AndroidApiLevel.O.getLevel() ? 0 : 1, programConsumer.count);
+      assertEquals(apiLevel.getLevel() >= firstEmptyLevel() ? 0 : 1, programConsumer.count);
     }
   }
 
@@ -89,7 +95,7 @@
           prepareL8Builder(apiLevel).setOutput(desugaredLibraryZip, OutputMode.DexIndexed).build());
       assertTrue(Files.exists(desugaredLibraryZip));
       assertEquals(
-          apiLevel.getLevel() >= AndroidApiLevel.O.getLevel() ? 0 : 1,
+          apiLevel.getLevel() >= firstEmptyLevel() ? 0 : 1,
           new ZipFile(desugaredLibraryZip.toFile(), StandardCharsets.UTF_8).size());
     }
   }
@@ -107,7 +113,7 @@
               .setOutput(desugaredLibraryDirectory, OutputMode.DexIndexed)
               .build());
       assertEquals(
-          apiLevel.getLevel() >= AndroidApiLevel.O.getLevel() ? 0 : 1,
+          apiLevel.getLevel() >= firstEmptyLevel() ? 0 : 1,
           Files.walk(desugaredLibraryDirectory)
               .filter(path -> path.toString().endsWith(".dex"))
               .count());