Synthesize retargeted library classes

Bug: 171197204
Bug: 170677722
Change-Id: I37f8f7a95ff8ed865b90926d91e293b32b2af696
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index e9ea099..946a5be 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
@@ -263,6 +264,10 @@
           AppView.createForR8(new AppInfoWithSubtyping(application), options);
       appView.setAppServices(AppServices.builder(appView).build());
 
+      if (!options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
+        application = DesugaredLibraryRetargeter.amendLibraryWithRetargetedMembers(appView);
+      }
+
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
       timing.begin("Strip unused code");
       Set<DexType> classesToRetainInnerClassAttributeFor = null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 8c067cc..9007252 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -258,6 +258,14 @@
     assert parameterAnnotationsList != null;
   }
 
+  public DexString getName() {
+    return getReference().name;
+  }
+
+  public DexMethod getReference() {
+    return method;
+  }
+
   public DexAnnotationSet liveAnnotations(AppView<AppInfoWithLiveness> appView) {
     return annotations.keepIf(
         annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index de97baf..9312434 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -6,13 +6,13 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.naming.NamingLens;
-import com.google.common.collect.Maps;
-import java.util.Map;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
+import com.google.common.collect.Maps;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 public class DexMethod extends Descriptor<DexEncodedMethod, DexMethod>
     implements PresortedComparable<DexMethod> {
@@ -232,4 +232,13 @@
     assert isSingleVirtualMethodCached(receiverType);
     return singleTargetCache.get(receiverType);
   }
+
+  public DexMethod withExtraArgumentPrepended(DexType type, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createMethod(
+        holder, dexItemFactory.prependTypeToProto(type, proto), name);
+  }
+
+  public DexMethod withHolder(DexType holder, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createMethod(holder, proto, name);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index f8aaa42..cd127f0 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -191,7 +191,7 @@
 
   public static class Builder extends DexApplication.Builder<Builder> {
 
-    private final ImmutableList<DexLibraryClass> libraryClasses;
+    private ImmutableList<DexLibraryClass> libraryClasses;
     private final ImmutableList<DexClasspathClass> classpathClasses;
 
     Builder(LazyLoadedDexApplication application) {
@@ -209,13 +209,19 @@
       classpathClasses = application.classpathClasses;
     }
 
+    public Builder addLibraryClasses(Collection<DexLibraryClass> classes) {
+      libraryClasses =
+          ImmutableList.<DexLibraryClass>builder().addAll(libraryClasses).addAll(classes).build();
+      return self();
+    }
+
     @Override
     Builder self() {
       return this;
     }
 
     @Override
-    public DexApplication build() {
+    public DirectMappedDexApplication build() {
       // Rebuild the map. This will fail if keys are not unique.
       // TODO(zerny): Consider not rebuilding the map if no program classes are added.
       Map<DexType, DexClass> allClasses =
diff --git a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
index 5457311..82f34d7 100644
--- a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
@@ -32,6 +32,10 @@
     this.enclosingMethod = enclosingMethod;
   }
 
+  public static EnclosingMethodAttribute none() {
+    return null;
+  }
+
   public void write(ClassWriter writer, NamingLens lens) {
     if (enclosingMethod != null) {
       writer.visitOuterClass(
diff --git a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
index b40f328..52d1220 100644
--- a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.Collections;
+import java.util.List;
 import org.objectweb.asm.ClassWriter;
 
 /** Representation of an entry in the Java InnerClasses attribute table. */
@@ -38,6 +40,10 @@
     this.innerName = innerName;
   }
 
+  public static List<InnerClassAttribute> emptyList() {
+    return Collections.emptyList();
+  }
+
   public boolean isNamed() {
     return innerName != null;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java b/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
index 96fb82b..e1f9da1 100644
--- a/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
@@ -15,6 +15,10 @@
     this.nestHost = nestHost;
   }
 
+  public static NestHostClassAttribute none() {
+    return null;
+  }
+
   public DexType getNestHost() {
     return nestHost;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java b/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
index f88a39f..f9d1a35 100644
--- a/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.naming.NamingLens;
+import java.util.Collections;
+import java.util.List;
 import org.objectweb.asm.ClassWriter;
 
 public class NestMemberClassAttribute {
@@ -15,6 +17,10 @@
     this.nestMember = nestMember;
   }
 
+  public static List<NestMemberClassAttribute> emptyList() {
+    return Collections.emptyList();
+  }
+
   public DexType getNestMember() {
     return nestMember;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
new file mode 100644
index 0000000..b251860
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -0,0 +1,141 @@
+// 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 com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class DesugaredLibraryRetargeter {
+
+  public static DirectMappedDexApplication amendLibraryWithRetargetedMembers(
+      AppView<AppInfoWithSubtyping> appView) {
+    Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+        appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+    Map<DexType, DexLibraryClass> synthesizedLibraryClasses =
+        synthesizeLibraryClassesForRetargetedMembers(appView, retargetCoreLibMember);
+    Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedLibraryMethods =
+        synthesizedMembersForRetargetClasses(
+            appView, retargetCoreLibMember, synthesizedLibraryClasses);
+    synthesizedLibraryMethods.forEach(DexLibraryClass::appendDirectMethods);
+    DirectMappedDexApplication newApplication =
+        appView
+            .appInfo()
+            .app()
+            .asDirect()
+            .builder()
+            .addLibraryClasses(synthesizedLibraryClasses.values())
+            .build();
+    appView.setAppInfo(new AppInfoWithSubtyping(newApplication));
+    return newApplication;
+  }
+
+  private static Map<DexType, DexLibraryClass> synthesizeLibraryClassesForRetargetedMembers(
+      AppView<AppInfoWithSubtyping> appView,
+      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    Map<DexType, DexLibraryClass> synthesizedLibraryClasses = new LinkedHashMap<>();
+    for (Map<DexType, DexType> oldToNewTypeMap : retargetCoreLibMember.values()) {
+      for (DexType newType : oldToNewTypeMap.values()) {
+        if (appView.definitionFor(newType) == null) {
+          synthesizedLibraryClasses.computeIfAbsent(
+              newType,
+              type ->
+                  // Synthesize a library class with the given name. Note that this is assuming that
+                  // the library class inherits directly from java.lang.Object, does not implement
+                  // any interfaces, etc.
+                  new DexLibraryClass(
+                      type,
+                      Kind.CF,
+                      new SynthesizedOrigin(
+                          "Desugared library retargeter", DesugaredLibraryRetargeter.class),
+                      ClassAccessFlags.fromCfAccessFlags(Constants.ACC_PUBLIC),
+                      dexItemFactory.objectType,
+                      DexTypeList.empty(),
+                      dexItemFactory.createString("DesugaredLibraryRetargeter"),
+                      NestHostClassAttribute.none(),
+                      NestMemberClassAttribute.emptyList(),
+                      EnclosingMethodAttribute.none(),
+                      InnerClassAttribute.emptyList(),
+                      DexAnnotationSet.empty(),
+                      DexEncodedField.EMPTY_ARRAY,
+                      DexEncodedField.EMPTY_ARRAY,
+                      DexEncodedMethod.EMPTY_ARRAY,
+                      DexEncodedMethod.EMPTY_ARRAY,
+                      dexItemFactory.getSkipNameValidationForTesting()));
+        }
+      }
+    }
+    return synthesizedLibraryClasses;
+  }
+
+  private static Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedMembersForRetargetClasses(
+      AppView<AppInfoWithSubtyping> appView,
+      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember,
+      Map<DexType, DexLibraryClass> synthesizedLibraryClasses) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedMembers = new IdentityHashMap<>();
+    for (Entry<DexString, Map<DexType, DexType>> entry : retargetCoreLibMember.entrySet()) {
+      DexString methodName = entry.getKey();
+      Map<DexType, DexType> types = entry.getValue();
+      types.forEach(
+          (oldType, newType) -> {
+            DexClass oldClass = appView.definitionFor(oldType);
+            DexLibraryClass newClass = synthesizedLibraryClasses.get(newType);
+            if (oldClass == null || newClass == null) {
+              return;
+            }
+            for (DexEncodedMethod method :
+                oldClass.methods(method -> method.getName() == methodName)) {
+              DexMethod retargetMethod = method.getReference().withHolder(newType, dexItemFactory);
+              if (!method.isStatic()) {
+                retargetMethod = retargetMethod.withExtraArgumentPrepended(oldType, dexItemFactory);
+              }
+              synthesizedMembers
+                  .computeIfAbsent(
+                      newClass,
+                      ignore ->
+                          new TreeSet<>((x, y) -> x.getReference().slowCompareTo(y.getReference())))
+                  .add(
+                      new DexEncodedMethod(
+                          retargetMethod,
+                          MethodAccessFlags.fromCfAccessFlags(
+                              Constants.ACC_PUBLIC | Constants.ACC_STATIC, false),
+                          DexAnnotationSet.empty(),
+                          ParameterAnnotationsList.empty(),
+                          null,
+                          true));
+            }
+          });
+    }
+    return synthesizedMembers;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
index 12b8b09..55b95a9 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
@@ -4,17 +4,14 @@
 
 package com.android.tools.r8.classmerging.vertical;
 
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AbortException;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.Arrays;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,21 +34,19 @@
 
   @Test
   public void test() throws Exception {
-    try {
-      testForR8(parameters.getBackend())
-          .addInnerClasses(getClass())
-          .addKeepMainRule(TestClass.class)
-          .enableCoreLibraryDesugaring(parameters.getApiLevel())
-          .enableNeverClassInliningAnnotations()
-          .setMinApi(parameters.getApiLevel())
-          .compile();
-      assertTrue(parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
-    } catch (CompilationFailedException e) {
-      // TODO(b/170677722): Fix compilation failure.
-      assertTrue(parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
-      assertTrue(e.getCause() instanceof AbortException);
-      assertThat(e.getCause().getMessage(), containsString("FORCE inlining on non-inlinable"));
-    }
+    // Regression test for b/170677722.
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), not(isPresent()));
+              assertThat(inspector.clazz(B.class), isPresent());
+            });
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
index 95fef57..415a868 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
@@ -5,13 +5,12 @@
 package com.android.tools.r8.ir.optimize.inliner;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.Arrays;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -41,14 +40,10 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
-            inspector -> {
-              // TODO(b/171197204): Method should be inlined.
-              assertThat(
-                  inspector.clazz(TestClass.class).uniqueMethodWithName("test"),
-                  notIf(
-                      isPresent(),
-                      parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)));
-            });
+            inspector ->
+                assertThat(
+                    inspector.clazz(TestClass.class).uniqueMethodWithName("test"),
+                    not(isPresent())));
   }
 
   static class TestClass {