Desugared library: virtual retargetting

Bug: 142846107
Change-Id: I679fb5d34eb775f288c83991e1a1aa598214fd3b
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index d54bf57..c0ba84f 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -1,6 +1,6 @@
 {
   "configuration_format_version": 3,
-  "version": "0.10.0",
+  "version": "0.10.1",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "library_flags": [
@@ -16,6 +16,10 @@
         "java.lang.Integer8": "java.lang.Integer",
         "java.lang.Long8": "java.lang.Long",
         "java.lang.Math8": "java.lang.Math"
+      },
+      "retarget_lib_member": {
+        "java.util.Date#toInstant": "java.util.DesugarDate",
+        "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar"
       }
     },
     {
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 62fc1a8..97da896 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -913,24 +913,22 @@
     return builder.build();
   }
 
-  public DexEncodedMethod toEmulateInterfaceLibraryMethod(
+  public static DexEncodedMethod toEmulateDispatchLibraryMethod(
+      DexType interfaceType,
       DexMethod newMethod,
       DexMethod companionMethod,
       DexMethod libraryMethod,
       List<Pair<DexType, DexMethod>> extraDispatchCases,
       AppView<?> appView) {
-    assert isDefaultMethod() || isStatic();
-    DexEncodedMethod.Builder builder = DexEncodedMethod.builder(this);
-    builder.setMethod(newMethod);
-    builder.accessFlags.setSynthetic();
-    builder.accessFlags.setStatic();
-    builder.accessFlags.unsetPrivate();
-    builder.accessFlags.setPublic();
-    builder.setCode(
+    MethodAccessFlags accessFlags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false);
+    CfCode code =
         new EmulateInterfaceSyntheticCfCodeProvider(
-                this.method.holder, companionMethod, libraryMethod, extraDispatchCases, appView)
-            .generateCfCode());
-    return builder.build();
+                interfaceType, companionMethod, libraryMethod, extraDispatchCases, appView)
+            .generateCfCode();
+    return new DexEncodedMethod(
+        newMethod, accessFlags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), code);
   }
 
   public DexEncodedMethod toStaticForwardingBridge(DexClass holder, DexMethod newMethod) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 41995ce..734077e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -15,6 +15,7 @@
 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.DexProgramClass;
 import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
@@ -27,6 +28,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -38,16 +40,19 @@
 import com.android.tools.r8.ir.desugar.backports.LongMethodRewrites;
 import com.android.tools.r8.ir.desugar.backports.NumericMethodRewrites;
 import com.android.tools.r8.ir.desugar.backports.ObjectsMethodRewrites;
+import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
+import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -114,21 +119,31 @@
         if (provider == null) {
           continue;
         }
+      }
 
-        // Since we are rewriting a virtual method into a static invoke in this case, the look-up
-        // logic gets confused. Final methods rewritten in such a way are always or invokes from a
-        // library class are rewritten into the static invoke, which is correct. However,
-        // overrides of the programmer are currently disabled. We still rewrite everything to make
-        // basic cases work.
-        // TODO(b/142846107): Support overrides of retarget virtual methods by uncommenting the
-        // following and implementing doSomethingSmart().
-
-        // DexClass receiverType = appView.definitionFor(invoke.getInvokedMethod().holder);
-        // if (!(dexEncodedMethod.isFinal()
-        //     || (receiverType != null && receiverType.isLibraryClass()))) {
-        //   doSomethingSmart();
-        //   continue;
-        // }
+      // Due to emulated dispatch, we have to rewrite invoke-super differently or we end up in
+      // infinite loops. We do direct resolution. This is a very uncommon case.
+      if (invoke.isInvokeSuper()) {
+        DexEncodedMethod dexEncodedMethod =
+            appView
+                .appInfo()
+                .lookupSuperTarget(invoke.getInvokedMethod(), code.method.method.holder);
+        if (!dexEncodedMethod.isFinal()) { // Final methods can be rewritten as a normal invoke.
+          Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+              appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+          Map<DexType, DexType> typeMap = retargetCoreLibMember.get(dexEncodedMethod.method.name);
+          if (typeMap != null && typeMap.containsKey(dexEncodedMethod.method.holder)) {
+            DexMethod retargetMethod =
+                factory.createMethod(
+                    typeMap.get(dexEncodedMethod.method.holder),
+                    factory.prependTypeToProto(
+                        dexEncodedMethod.method.holder, dexEncodedMethod.method.proto),
+                    dexEncodedMethod.method.name);
+            iterator.replaceCurrentInstruction(
+                new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
+          }
+          continue;
+        }
       }
 
       provider.rewriteInvoke(invoke, iterator, code, appView);
@@ -181,6 +196,11 @@
 
   public void synthesizeUtilityClasses(Builder<?> builder, ExecutorService executorService)
       throws ExecutionException {
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      synthesizeEmulatedDispatchMethods(builder);
+    } else {
+      addInterfacesAndForwardingMethods(executorService);
+    }
     if (holders.isEmpty()) {
       return;
     }
@@ -248,6 +268,210 @@
     }
   }
 
+  private void addInterfacesAndForwardingMethods(ExecutorService executorService)
+      throws ExecutionException {
+    assert !appView.options().isDesugaredLibraryCompilation();
+    Map<DexType, List<DexMethod>> map = Maps.newIdentityHashMap();
+    for (DexMethod emulatedDispatchMethod : rewritableMethods.getEmulatedDispatchMethods()) {
+      map.putIfAbsent(emulatedDispatchMethod.holder, new ArrayList<>(1));
+      map.get(emulatedDispatchMethod.holder).add(emulatedDispatchMethod);
+    }
+    List<DexEncodedMethod> addedMethods = new ArrayList<>();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      DexClass dexClass = appView.definitionFor(clazz.superType);
+      // Only performs computation if superclass is a library class, but not object to filter out
+      // the most common case.
+      if (dexClass != null
+          && dexClass.isLibraryClass()
+          && dexClass.type != appView.dexItemFactory().objectType) {
+        for (DexType dexType : map.keySet()) {
+          if (inherit(dexClass.asLibraryClass(), dexType)) {
+            addedMethods.addAll(addInterfacesAndForwardingMethods(clazz, map.get(dexType)));
+          }
+        }
+      }
+    }
+    if (addedMethods.isEmpty()) {
+      return;
+    }
+    converter.processMethodsConcurrently(addedMethods, executorService);
+  }
+
+  private boolean inherit(DexLibraryClass clazz, DexType typeToInherit) {
+    DexLibraryClass current = clazz;
+    while (current.type != appView.dexItemFactory().objectType) {
+      if (current.type == typeToInherit) {
+        return true;
+      }
+      current = appView.definitionFor(current.superType).asLibraryClass();
+    }
+    return false;
+  }
+
+  private List<DexEncodedMethod> addInterfacesAndForwardingMethods(
+      DexProgramClass clazz, List<DexMethod> dexMethods) {
+    // BackportedMethodRewriter emulate dispatch: insertion of a marker interface & forwarding
+    // methods.
+    // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
+    // applies up to 24.
+    List<DexEncodedMethod> newForwardingMethods = new ArrayList<>();
+    for (DexMethod dexMethod : dexMethods) {
+      DexType[] newInterfaces = Arrays.copyOf(clazz.interfaces.values, clazz.interfaces.size() + 1);
+      newInterfaces[newInterfaces.length - 1] =
+          BackportedMethodRewriter.dispatchInterfaceTypeFor(appView, dexMethod);
+      clazz.interfaces = new DexTypeList(newInterfaces);
+      DexEncodedMethod dexEncodedMethod = clazz.lookupVirtualMethod(dexMethod);
+      if (dexEncodedMethod == null) {
+        DexEncodedMethod newMethod = createForwardingMethod(dexMethod, clazz);
+        clazz.addVirtualMethod(newMethod);
+        newForwardingMethods.add(newMethod);
+      }
+    }
+    return newForwardingMethods;
+  }
+
+  private DexEncodedMethod createForwardingMethod(DexMethod target, DexClass clazz) {
+    // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
+    // even if this results in invalid code, these classes are never desugared.
+    // In desugared library, emulated interface methods can be overridden by retarget lib members.
+    DexMethod forwardMethod = ClassProcessor.retargetMethod(appView, target);
+    // New method will have the same name, proto, and also all the flags of the
+    // default method, including bridge flag.
+    DexMethod newMethod =
+        appView.dexItemFactory().createMethod(clazz.type, target.proto, target.name);
+    DexEncodedMethod dexEncodedMethod = appView.definitionFor(target);
+    MethodAccessFlags newFlags = dexEncodedMethod.accessFlags.copy();
+    newFlags.setSynthetic();
+    ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
+        ForwardMethodSourceCode.builder(newMethod);
+    forwardSourceCodeBuilder
+        .setReceiver(clazz.type)
+        .setTarget(forwardMethod)
+        .setInvokeType(Invoke.Type.STATIC)
+        .setIsInterface(false);
+    return new DexEncodedMethod(
+        newMethod,
+        newFlags,
+        dexEncodedMethod.annotations,
+        dexEncodedMethod.parameterAnnotationsList,
+        new SynthesizedCode(forwardSourceCodeBuilder::build));
+  }
+
+  private void synthesizeEmulatedDispatchMethods(Builder<?> builder) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    if (rewritableMethods.getEmulatedDispatchMethods().isEmpty()) {
+      return;
+    }
+    ClassAccessFlags itfAccessFlags =
+        ClassAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_PUBLIC
+                | Constants.ACC_SYNTHETIC
+                | Constants.ACC_ABSTRACT
+                | Constants.ACC_INTERFACE);
+    ClassAccessFlags holderAccessFlags =
+        ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
+    for (DexMethod emulatedDispatchMethod : rewritableMethods.getEmulatedDispatchMethods()) {
+      // Dispatch interface.
+      DexType interfaceType = dispatchInterfaceTypeFor(appView, emulatedDispatchMethod);
+      DexEncodedMethod itfMethod =
+          generateInterfaceDispatchMethod(emulatedDispatchMethod, interfaceType);
+      DexProgramClass dispatchInterface =
+          new DexProgramClass(
+              interfaceType,
+              null,
+              new SynthesizedOrigin("desugared library interface dispatch", getClass()),
+              itfAccessFlags,
+              factory.objectType,
+              DexTypeList.empty(),
+              null,
+              null,
+              Collections.emptyList(),
+              null,
+              Collections.emptyList(),
+              DexAnnotationSet.empty(),
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedMethod.EMPTY_ARRAY,
+              new DexEncodedMethod[] {itfMethod},
+              factory.getSkipNameValidationForTesting(),
+              getChecksumSupplier(itfMethod));
+      appView.appInfo().addSynthesizedClass(dispatchInterface);
+      builder.addSynthesizedClass(dispatchInterface, false);
+      // Dispatch holder.
+      DexType holderType = dispatchHolderTypeFor(appView, emulatedDispatchMethod);
+      DexEncodedMethod dispatchMethod =
+          generateHolderDispatchMethod(emulatedDispatchMethod, holderType, itfMethod.method);
+      DexProgramClass dispatchHolder =
+          new DexProgramClass(
+              holderType,
+              null,
+              new SynthesizedOrigin("desugared library dispatch holder class", getClass()),
+              holderAccessFlags,
+              factory.objectType,
+              DexTypeList.empty(),
+              null,
+              null,
+              Collections.emptyList(),
+              null,
+              Collections.emptyList(),
+              DexAnnotationSet.empty(),
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedField.EMPTY_ARRAY,
+              new DexEncodedMethod[] {dispatchMethod},
+              DexEncodedMethod.EMPTY_ARRAY,
+              factory.getSkipNameValidationForTesting(),
+              getChecksumSupplier(dispatchMethod));
+      appView.appInfo().addSynthesizedClass(dispatchHolder);
+      builder.addSynthesizedClass(dispatchHolder, false);
+    }
+  }
+
+  private DexEncodedMethod generateInterfaceDispatchMethod(
+      DexMethod emulatedDispatchMethod, DexType interfaceType) {
+    MethodAccessFlags flags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC, false);
+    DexMethod newMethod =
+        factory.createMethod(
+            interfaceType, emulatedDispatchMethod.proto, emulatedDispatchMethod.name);
+    return new DexEncodedMethod(
+        newMethod, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null);
+  }
+
+  private DexEncodedMethod generateHolderDispatchMethod(
+      DexMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
+    // The method should look like:
+    // static foo(rcvr, arg0, arg1) {
+    //    if (rcvr instanceof interfaceType) {
+    //      return invoke-interface receiver.foo(arg0, arg1);
+    //    } else {
+    //      return DesugarX.foo(rcvr, arg0, arg1)
+    //    }
+    // We do not deal with complex cases (multiple retargeting of the same signature in the
+    // same inheritance tree, etc., since they do not happen in the most common desugared library.
+    DexItemFactory factory = appView.dexItemFactory();
+    DexProto newProto =
+        factory.prependTypeToProto(emulatedDispatchMethod.holder, emulatedDispatchMethod.proto);
+    DexMethod newMethod =
+        factory.createMethod(dispatchHolder, newProto, emulatedDispatchMethod.name);
+    DexType desugarType =
+        appView
+            .options()
+            .desugaredLibraryConfiguration
+            .getRetargetCoreLibMember()
+            .get(emulatedDispatchMethod.name)
+            .get(emulatedDispatchMethod.holder);
+    DexMethod desugarMethod =
+        factory.createMethod(desugarType, newProto, emulatedDispatchMethod.name);
+    return DexEncodedMethod.toEmulateDispatchLibraryMethod(
+        emulatedDispatchMethod.holder,
+        newMethod,
+        desugarMethod,
+        itfMethod,
+        Collections.emptyList(),
+        appView);
+  }
+
   private ChecksumSupplier getChecksumSupplier(DexEncodedMethod method) {
     if (!appView.options().encodeChecksums) {
       return DexProgramClass::invalidChecksumRequest;
@@ -255,6 +479,31 @@
     return c -> method.method.hashCode();
   }
 
+  public static DexType dispatchInterfaceTypeFor(AppView<?> appView, DexMethod method) {
+    return dispatchTypeFor(appView, method, "dispatchInterface");
+  }
+
+  static DexType dispatchHolderTypeFor(AppView<?> appView, DexMethod method) {
+    return dispatchTypeFor(appView, method, "dispatchHolder");
+  }
+
+  private static DexType dispatchTypeFor(AppView<?> appView, DexMethod method, String suffix) {
+    String desugaredLibPrefix =
+        appView.options().desugaredLibraryConfiguration.getSynthesizedLibraryClassesPackagePrefix();
+    String descriptor =
+        "L"
+            + desugaredLibPrefix
+            + UTILITY_CLASS_NAME_PREFIX
+            + '$'
+            + method.holder.getName()
+            + '$'
+            + method.name
+            + '$'
+            + suffix
+            + ';';
+    return appView.dexItemFactory().createType(descriptor);
+  }
+
   private MethodProvider getMethodProviderOrNull(DexMethod method) {
     DexMethod original = appView.graphLense().getOriginalMethodSignature(method);
     assert original != null;
@@ -285,6 +534,8 @@
     // rewritten while the holder is non final but no superclass implement the method. In this case
     // d8 needs to force resolution of given methods to see if the invoke needs to be rewritten.
     private final Map<DexString, List<DexMethod>> virtualRewrites = new IdentityHashMap<>();
+    // non final virtual library methods requiring generation of emulated dispatch.
+    private final Set<DexMethod> emulatedDispatchMethods = Sets.newHashSet();
 
     RewritableMethods(InternalOptions options, AppView<?> appView) {
       DexItemFactory factory = options.itemFactory;
@@ -330,6 +581,10 @@
       return false;
     }
 
+    public Set<DexMethod> getEmulatedDispatchMethods() {
+      return emulatedDispatchMethods;
+    }
+
     boolean isEmpty() {
       return rewritable.isEmpty();
     }
@@ -1232,9 +1487,14 @@
               if (!encodedMethod.isStatic()) {
                 virtualRewrites.putIfAbsent(encodedMethod.method.name, new ArrayList<>());
                 virtualRewrites.get(encodedMethod.method.name).add(encodedMethod.method);
-                if (isEmulatedInterfaceDispatch(appView, encodedMethod)) {
+                if (InterfaceMethodRewriter.isEmulatedInterfaceDispatch(appView, encodedMethod)) {
                   // In this case interface method rewriter takes care of it.
                   continue;
+                } else if (!encodedMethod.isFinal()) {
+                  // Virtual rewrites require emulated dispatch for inheritance.
+                  // The call is rewritten to the dispatch holder class instead.
+                  handleEmulateDispatch(appView, encodedMethod.method);
+                  newHolder = dispatchHolderTypeFor(appView, encodedMethod.method);
                 }
               }
               DexProto proto = encodedMethod.method.proto;
@@ -1248,37 +1508,6 @@
       }
     }
 
-    private boolean isEmulatedInterfaceDispatch(AppView<?> appView, DexEncodedMethod method) {
-      // Answers true if this method is already managed through emulated interface dispatch.
-      Map<DexType, DexType> emulateLibraryInterface =
-          appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
-      if (emulateLibraryInterface.isEmpty()) {
-        return false;
-      }
-      DexMethod methodToFind = method.method;
-
-      // Look-up all superclass and interfaces, if an emulated interface is found, and it implements
-      // the method, answers true.
-      LinkedList<DexType> workList = new LinkedList<>();
-      workList.add(methodToFind.holder);
-      while (!workList.isEmpty()) {
-        DexType dexType = workList.removeFirst();
-        DexClass dexClass = appView.definitionFor(dexType);
-        assert dexClass != null; // It is a library class, or we are doing L8 compilation.
-        if (dexClass.isInterface() && emulateLibraryInterface.containsKey(dexType)) {
-          DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(methodToFind);
-          if (dexEncodedMethod != null) {
-            return true;
-          }
-        }
-        Collections.addAll(workList, dexClass.interfaces.values);
-        if (dexClass.superType != appView.dexItemFactory().objectType) {
-          workList.add(dexClass.superType);
-        }
-      }
-      return false;
-    }
-
     private List<DexEncodedMethod> findDexEncodedMethodsWithName(
         DexString methodName, DexClass clazz) {
       List<DexEncodedMethod> found = new ArrayList<>();
@@ -1291,6 +1520,17 @@
       return found;
     }
 
+    private void handleEmulateDispatch(AppView<?> appView, DexMethod method) {
+      emulatedDispatchMethods.add(method);
+      if (!appView.options().isDesugaredLibraryCompilation()) {
+        // Add rewrite rules so keeps rules are correctly generated in the program.
+        DexType dispatchInterfaceType = dispatchInterfaceTypeFor(appView, method);
+        appView.rewritePrefix.rewriteType(dispatchInterfaceType, dispatchInterfaceType);
+        DexType dispatchHolderType = dispatchHolderTypeFor(appView, method);
+        appView.rewritePrefix.rewriteType(dispatchHolderType, dispatchHolderType);
+      }
+    }
+
     private void addProvider(MethodProvider generator) {
       MethodProvider replaced = rewritable.put(generator.method, generator);
       assert replaced == null;
@@ -1457,8 +1697,8 @@
   }
 
   // Specific subclass to transform virtual methods into static desugared methods.
-  // To be correct, the method has to be on a final class, and be implemented directly
-  // on the class (no overrides).
+  // To be correct, the method has to be on a final class or be a final method, and to be
+  // implemented directly on the class (no overrides).
   private static class StatifyingMethodGenerator extends MethodGenerator {
 
     private final DexType receiverType;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 3168dde..c2f5ae9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -391,7 +391,7 @@
     DexMethod forwardMethod =
         targetHolder.isInterface()
             ? rewriter.defaultAsMethodOfCompanionClass(method)
-            : retargetMethod(method);
+            : retargetMethod(appView, method);
     // New method will have the same name, proto, and also all the flags of the
     // default method, including bridge flag.
     DexMethod newMethod = dexItemFactory.createMethod(clazz.type, method.proto, method.name);
@@ -415,16 +415,18 @@
     addSyntheticMethod(clazz.asProgramClass(), newEncodedMethod);
   }
 
-  private DexMethod retargetMethod(DexMethod method) {
+  static DexMethod retargetMethod(AppView<?> appView, DexMethod method) {
     Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
         appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
     Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.name);
     assert typeMap != null;
     assert typeMap.get(method.holder) != null;
-    return dexItemFactory.createMethod(
-        typeMap.get(method.holder),
-        dexItemFactory.prependTypeToProto(method.holder, method.proto),
-        method.name);
+    return appView
+        .dexItemFactory()
+        .createMethod(
+            typeMap.get(method.holder),
+            appView.dexItemFactory().prependTypeToProto(method.holder, method.proto),
+            method.name);
   }
 
   // Topological order traversal and its helpers.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index 1c11d78..fd954e5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -114,6 +114,10 @@
         : "";
   }
 
+  public String getSynthesizedLibraryClassesPackagePrefix() {
+    return synthesizedLibraryClassesPackagePrefix;
+  }
+
   public Map<String, String> getRewritePrefix() {
     return rewritePrefix;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index ff7576a..679f927 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -610,7 +610,8 @@
           }
         }
         emulationMethods.add(
-            method.toEmulateInterfaceLibraryMethod(
+            DexEncodedMethod.toEmulateDispatchLibraryMethod(
+                method.method.holder,
                 emulateInterfaceLibraryMethod(method.method, method.method.holder, factory),
                 companionMethod,
                 libraryMethod,
@@ -1012,6 +1013,37 @@
     return true;
   }
 
+  public static boolean isEmulatedInterfaceDispatch(AppView<?> appView, DexEncodedMethod method) {
+    // Answers true if this method is already managed through emulated interface dispatch.
+    Map<DexType, DexType> emulateLibraryInterface =
+        appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+    if (emulateLibraryInterface.isEmpty()) {
+      return false;
+    }
+    DexMethod methodToFind = method.method;
+
+    // Look-up all superclass and interfaces, if an emulated interface is found, and it implements
+    // the method, answers true.
+    LinkedList<DexType> workList = new LinkedList<>();
+    workList.add(methodToFind.holder);
+    while (!workList.isEmpty()) {
+      DexType dexType = workList.removeFirst();
+      DexClass dexClass = appView.definitionFor(dexType);
+      assert dexClass != null; // It is a library class, or we are doing L8 compilation.
+      if (dexClass.isInterface() && emulateLibraryInterface.containsKey(dexType)) {
+        DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(methodToFind);
+        if (dexEncodedMethod != null) {
+          return true;
+        }
+      }
+      Collections.addAll(workList, dexClass.interfaces.values);
+      if (dexClass.superType != appView.dexItemFactory().objectType) {
+        workList.add(dexClass.superType);
+      }
+    }
+    return false;
+  }
+
   public void warnMissingInterface(
       DexClass classToDesugar, DexClass implementing, DexType missing) {
     // We use contains() on non hashed collection, but we know it's a 8 cases collection.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
index eebea28..04a7258 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
@@ -5,11 +5,12 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
 import java.time.Instant;
 import java.time.ZonedDateTime;
 import java.util.Date;
 import java.util.GregorianCalendar;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.IntUnaryOperator;
 import org.junit.Test;
@@ -21,26 +22,33 @@
 public class RetargetOverrideTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
   }
 
-  public RetargetOverrideTest(TestParameters parameters) {
+  public RetargetOverrideTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
     this.parameters = parameters;
   }
 
   @Test
   public void testRetargetOverrideD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String stdout =
         testForD8()
             .addInnerClasses(RetargetOverrideTest.class)
-            .enableCoreLibraryDesugaring(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .setMinApi(parameters.getApiLevel())
             .compile()
             .addDesugaredCoreLibraryRunClassPath(
-                this::buildDesugaredLibrary, parameters.getApiLevel())
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
             .run(parameters.getRuntime(), Executor.class)
             .assertSuccess()
             .getStdOut();
@@ -49,15 +57,19 @@
 
   @Test
   public void testRetargetOverrideR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String stdout =
         testForR8(Backend.DEX)
             .addKeepMainRule(Executor.class)
             .addInnerClasses(RetargetOverrideTest.class)
-            .enableCoreLibraryDesugaring(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .setMinApi(parameters.getApiLevel())
             .compile()
             .addDesugaredCoreLibraryRunClassPath(
-                this::buildDesugaredLibrary, parameters.getApiLevel())
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
             .run(parameters.getRuntime(), Executor.class)
             .assertSuccess()
             .getStdOut();
@@ -67,22 +79,18 @@
   static class Executor {
 
     public static void main(String[] args) {
-      java.sql.Date date = new java.sql.Date(123456789);
-      // The following one is not working on JVMs, but works on Android...
-      System.out.println(date.toInstant());
-      System.out.println("1970-01-02T10:17:36.789Z");
+      directTypes();
+      polyTypes();
+      baseTypes();
+    }
 
-      GregorianCalendar gregCal = new GregorianCalendar(1990, 2, 22);
-      System.out.println(gregCal.toInstant());
+    public static void directTypes() {
+      MyCalendarOverride myCal = new MyCalendarOverride(1990, 2, 22);
+      System.out.println(myCal.toZonedDateTime());
+      System.out.println("1990-11-22T00:00Z[GMT]");
+      System.out.println(myCal.toInstant());
       System.out.println("1990-03-22T00:00:00Z");
 
-      // TODO(b/142846107): Enable overrides of retarget core members.
-      // MyCalendarOverride myCal = new MyCalendarOverride(1990, 2, 22);
-      // System.out.println(myCal.toZonedDateTime());
-      // System.out.println("1990-11-22T00:00Z[GMT]");
-      // System.out.println(myCal.toInstant());
-      // System.out.println("1990-03-22T00:00:00Z");
-
       MyCalendarNoOverride myCalN = new MyCalendarNoOverride(1990, 2, 22);
       System.out.println(myCalN.toZonedDateTime());
       System.out.println("1990-03-22T00:00Z[GMT]");
@@ -93,10 +101,13 @@
       System.out.println(myCalN.superToInstant());
       System.out.println("1990-03-22T00:00:00Z");
 
-      // TODO(b/142846107): Enable overrides of retarget core members.
-      // MyDateOverride myDate = new MyDateOverride(123456789);
-      // System.out.println(myDate.toInstant());
-      // System.out.println("1970-01-02T10:17:45.789Z");
+      MyDateDoubleOverride myDateCast2 = new MyDateDoubleOverride(123456789);
+      System.out.println(myDateCast2.toInstant());
+      System.out.println("1970-01-02T10:17:48.789Z");
+
+      MyDateOverride myDate = new MyDateOverride(123456789);
+      System.out.println(myDate.toInstant());
+      System.out.println("1970-01-02T10:17:45.789Z");
 
       MyDateNoOverride myDateN = new MyDateNoOverride(123456789);
       System.out.println(myDateN.toInstant());
@@ -112,21 +123,58 @@
       System.out.println(myAtomicInteger.updateAndGet(x -> x + 100));
       System.out.println("145");
     }
+
+    public static void polyTypes() {
+      Date myDateCast = new MyDateOverride(123456789);
+      System.out.println(myDateCast.toInstant());
+      System.out.println("1970-01-02T10:17:45.789Z");
+
+      Date myDateCast2 = new MyDateDoubleOverride(123456789);
+      System.out.println(myDateCast2.toInstant());
+      System.out.println("1970-01-02T10:17:48.789Z");
+
+      Date myDateN = new MyDateNoOverride(123456789);
+      System.out.println(myDateN.toInstant());
+      System.out.println("1970-01-02T10:17:36.789Z");
+
+      GregorianCalendar myCalCast = new MyCalendarOverride(1990, 2, 22);
+      System.out.println(myCalCast.toZonedDateTime());
+      System.out.println("1990-11-22T00:00Z[GMT]");
+      System.out.println(myCalCast.toInstant());
+      System.out.println("1990-03-22T00:00:00Z");
+
+      GregorianCalendar myCalN = new MyCalendarNoOverride(1990, 2, 22);
+      System.out.println(myCalN.toZonedDateTime());
+      System.out.println("1990-03-22T00:00Z[GMT]");
+      System.out.println(myCalN.toInstant());
+      System.out.println("1990-03-22T00:00:00Z");
+    }
+
+    public static void baseTypes() {
+      java.sql.Date date = new java.sql.Date(123456789);
+      // The following one is not working on JVMs, but works on Android...
+      System.out.println(date.toInstant());
+      System.out.println("1970-01-02T10:17:36.789Z");
+
+      GregorianCalendar gregCal = new GregorianCalendar(1990, 2, 22);
+      System.out.println(gregCal.toInstant());
+      System.out.println("1990-03-22T00:00:00Z");
+    }
   }
 
-  // static class MyCalendarOverride extends GregorianCalendar {
-  //
-  //   public MyCalendarOverride(int year, int month, int dayOfMonth) {
-  //     super(year, month, dayOfMonth);
-  //   }
-  //
-  //   // Cannot override toInstant (final).
-  //
-  //   @Override
-  //   public ZonedDateTime toZonedDateTime() {
-  //     return super.toZonedDateTime().withMonth(11);
-  //   }
-  // }
+  static class MyCalendarOverride extends GregorianCalendar {
+
+    public MyCalendarOverride(int year, int month, int dayOfMonth) {
+      super(year, month, dayOfMonth);
+    }
+
+    // Cannot override toInstant (final).
+
+    @Override
+    public ZonedDateTime toZonedDateTime() {
+      return super.toZonedDateTime().withMonth(11);
+    }
+  }
 
   static class MyCalendarNoOverride extends GregorianCalendar {
     public MyCalendarNoOverride(int year, int month, int dayOfMonth) {
@@ -142,17 +190,29 @@
     }
   }
 
-  // static class MyDateOverride extends Date {
-  //
-  //   public MyDateOverride(long date) {
-  //     super(date);
-  //   }
-  //
-  //   @Override
-  //   public Instant toInstant() {
-  //     return super.toInstant().plusSeconds(9);
-  //   }
-  // }
+  static class MyDateOverride extends Date {
+
+    public MyDateOverride(long date) {
+      super(date);
+    }
+
+    @Override
+    public Instant toInstant() {
+      return super.toInstant().plusSeconds(9);
+    }
+  }
+
+  static class MyDateDoubleOverride extends MyDateOverride {
+
+    public MyDateDoubleOverride(long date) {
+      super(date);
+    }
+
+    @Override
+    public Instant toInstant() {
+      return super.toInstant().plusSeconds(3);
+    }
+  }
 
   static class MyDateNoOverride extends Date {