Desugared library: rewrite super invoke

- Make sure super invoke are rewritten correctly.
- Make sure the spliterator case is tested entirely.
- Fix dispatch on library overrides in general.
  Subclasses of lib classes had sometimes invalid emulated dispatch.

Change-Id: I024d2187c83f0dc4cf9bf238ec50820a50c0545a
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 d52f02e..511b971 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
@@ -47,6 +47,7 @@
 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;
@@ -1232,6 +1233,10 @@
               if (!encodedMethod.isStatic()) {
                 virtualRewrites.putIfAbsent(encodedMethod.method.name, new ArrayList<>());
                 virtualRewrites.get(encodedMethod.method.name).add(encodedMethod.method);
+                if (isEmulatedInterfaceDispatch(appView, encodedMethod)) {
+                  // In this case interface method rewriter takes care of it.
+                  continue;
+                }
               }
               DexProto proto = encodedMethod.method.proto;
               DexMethod method = appView.dexItemFactory().createMethod(inType, proto, methodName);
@@ -1244,6 +1249,37 @@
       }
     }
 
+    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<>();
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 54ac5c0..ae14474 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
@@ -49,6 +49,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
@@ -254,27 +255,6 @@
             // exception but we can not report it as error since it can also be the intended
             // behavior.
             warnMissingType(encodedMethod.method, invokedMethod.holder);
-          } else if (clazz.isInterface() && clazz.isLibraryClass() && isInDesugaredLibrary(clazz)) {
-            // Here we try to avoid doing the expensive look-up on all invokes.
-            boolean rewritten = false;
-            if (emulatedMethods.contains(invokedMethod.name)) {
-              DexType dexType = nearestEmulatedInterfaceImplementingWithCache(invokedMethod);
-              if (dexType != null) {
-                rewriteCurrentInstructionToEmulatedInterfaceCall(
-                    dexType, invokedMethod, invokeSuper, instructions);
-                rewritten = true;
-              }
-            }
-            if (!rewritten) {
-              DexMethod amendedMethod =
-                  amendDefaultMethod(
-                      appInfo.definitionFor(encodedMethod.method.holder), invokedMethod);
-              instructions.replaceCurrentInstruction(
-                  new InvokeStatic(
-                      defaultAsMethodOfCompanionClass(amendedMethod),
-                      invokeSuper.outValue(),
-                      invokeSuper.arguments()));
-            }
           } else if (clazz.isInterface() && !clazz.isLibraryClass()) {
             // NOTE: we intentionally don't desugar super calls into interface methods
             // coming from android.jar since it is only possible in case v24+ version
@@ -291,6 +271,54 @@
             instructions.replaceCurrentInstruction(
                 new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
                     invokeSuper.outValue(), invokeSuper.arguments()));
+          } else {
+            DexType dexType = nearestEmulatedInterfaceOrNull(invokedMethod);
+            if (dexType != null) {
+              // That invoke super may not resolve since the super method may not be present
+              // since it's in the emulated interface. We need to force resolution. If it resolves
+              // to a library method, then it needs to be rewritten.
+              // If it resolves to a program overrides, the invoke-super can remain.
+              DexEncodedMethod dexEncodedMethod =
+                  appView
+                      .appInfo()
+                      .lookupSuperTarget(invokeSuper.getInvokedMethod(), code.method.method.holder);
+              if (dexEncodedMethod != null) {
+                DexClass dexClass = appView.definitionFor(dexEncodedMethod.method.holder);
+                if (dexClass != null && dexClass.isLibraryClass()) {
+                  // Rewriting is required because the super invoke resolves into a missing
+                  // method (method is on desugared library). Find out if it needs to be
+                  // retarget or if it just calls a companion class method and rewrite.
+                  Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+                      options.desugaredLibraryConfiguration.getRetargetCoreLibMember();
+                  Map<DexType, DexType> typeMap =
+                      retargetCoreLibMember.get(dexEncodedMethod.method.name);
+                  if (typeMap == null || !typeMap.containsKey(dexEncodedMethod.method.holder)) {
+                    DexMethod originalCompanionMethod =
+                        instanceAsMethodOfCompanionClass(
+                            dexEncodedMethod.method, DEFAULT_METHOD_PREFIX, factory);
+                    DexMethod companionMethod =
+                        factory.createMethod(
+                            getCompanionClassType(dexType),
+                            factory.protoWithDifferentFirstParameter(
+                                originalCompanionMethod.proto, dexType),
+                            originalCompanionMethod.name);
+                    instructions.replaceCurrentInstruction(
+                        new InvokeStatic(
+                            companionMethod, invokeSuper.outValue(), invokeSuper.arguments()));
+                  } else {
+                    DexMethod retargetMethod =
+                        factory.createMethod(
+                            typeMap.get(dexEncodedMethod.method.holder),
+                            factory.prependTypeToProto(
+                                dexEncodedMethod.method.holder, dexEncodedMethod.method.proto),
+                            dexEncodedMethod.method.name);
+                    instructions.replaceCurrentInstruction(
+                        new InvokeStatic(
+                            retargetMethod, invokeSuper.outValue(), invokeSuper.arguments()));
+                  }
+                }
+              }
+            }
           }
           continue;
         }
@@ -345,39 +373,42 @@
         if (instruction.isInvokeVirtual() || instruction.isInvokeInterface()) {
           InvokeMethod invokeMethod = instruction.asInvokeMethod();
           DexMethod invokedMethod = invokeMethod.getInvokedMethod();
-          // Here we try to avoid doing the expensive look-up on all invokes.
-          if (!emulatedMethods.contains(invokedMethod.name)) {
-            continue;
+          DexType dexType = nearestEmulatedInterfaceOrNull(invokedMethod);
+          if (dexType != null) {
+            rewriteCurrentInstructionToEmulatedInterfaceCall(
+                dexType, invokedMethod, invokeMethod, instructions);
           }
-          DexClass dexClass = appView.definitionFor(invokedMethod.holder);
-          // We cannot rewrite the invoke we do not know what the class is.
-          if (dexClass == null) {
-            continue;
-          }
-          // TODO(b/120884788): Make sure program class are looked up before library class for
-          // CoreLib compilation or look again into all desugared library emulation.
-          // Outside of core libraries, only library classes are rewritten. In core libraries,
-          // some classes are present both as program and library class, and definitionFor
-          // answers the program class so this is not true.
-          if (!appView.options().isDesugaredLibraryCompilation() && !dexClass.isLibraryClass()) {
-            continue;
-          }
-          // We always rewrite interfaces, but classes are rewritten only if they are not already
-          // desugared (CoreLibrary classes efficient implementation).
-          if (!dexClass.isInterface() && isInDesugaredLibrary(dexClass)) {
-            continue;
-          }
-          DexType dexType = nearestEmulatedInterfaceImplementingWithCache(invokedMethod);
-          if (dexType == null) {
-            continue;
-          }
-          rewriteCurrentInstructionToEmulatedInterfaceCall(
-              dexType, invokedMethod, invokeMethod, instructions);
         }
       }
     }
   }
 
+  private DexType nearestEmulatedInterfaceOrNull(DexMethod invokedMethod) {
+    // Here we try to avoid doing the expensive look-up on all invokes.
+    if (!emulatedMethods.contains(invokedMethod.name)) {
+      return null;
+    }
+    DexClass dexClass = appView.definitionFor(invokedMethod.holder);
+    // We cannot rewrite the invoke we do not know what the class is.
+    if (dexClass == null) {
+      return null;
+    }
+    // TODO(b/120884788): Make sure program class are looked up before library class for
+    // CoreLib compilation or look again into all desugared library emulation.
+    // Outside of core libraries, only library classes are rewritten. In core libraries,
+    // some classes are present both as program and library class, and definitionFor
+    // answers the program class so this is not true.
+    if (!appView.options().isDesugaredLibraryCompilation() && !dexClass.isLibraryClass()) {
+      return null;
+    }
+    // We always rewrite interfaces, but classes are rewritten only if they are not already
+    // desugared (CoreLibrary classes efficient implementation).
+    if (!dexClass.isInterface() && isInDesugaredLibrary(dexClass)) {
+      return null;
+    }
+    return nearestEmulatedInterfaceImplementingWithCache(invokedMethod);
+  }
+
   private void rewriteCurrentInstructionToEmulatedInterfaceCall(
       DexType emulatedItf,
       DexMethod invokedMethod,
@@ -933,7 +964,7 @@
 
   private void duplicateEmulatedInterfaces() {
     // All classes implementing an emulated interface now implements the interface and the
-    // emulated one.
+    // emulated one, as well as hidden overrides, for correct emulated dispatch.
     for (DexClass clazz : appView.appInfo().classes()) {
       List<DexType> extraInterfaces = new ArrayList<>();
       for (DexType type : clazz.interfaces.values) {
@@ -941,6 +972,17 @@
           extraInterfaces.add(emulatedInterfaces.get(type));
         }
       }
+      if (!appView.options().isDesugaredLibraryCompilation()) {
+        DexClass superClazz = appView.definitionFor(clazz.superType);
+        if (superClazz != null && superClazz.isLibraryClass()) {
+          List<DexType> itfs = emulatedInterfacesOf(superClazz);
+          for (DexType itf : itfs) {
+            extraInterfaces.add(emulatedInterfaces.get(itf));
+          }
+        }
+        // Remove duplicates.
+        extraInterfaces = new ArrayList<>(new LinkedHashSet<>(extraInterfaces));
+      }
       if (!extraInterfaces.isEmpty()) {
         DexType[] newInterfaces =
             Arrays.copyOf(
@@ -953,6 +995,32 @@
     }
   }
 
+  private List<DexType> emulatedInterfacesOf(DexClass superClazz) {
+    if (superClazz.type == factory.objectType) {
+      return Collections.emptyList();
+    }
+    ArrayList<DexType> itfs = new ArrayList<>();
+    LinkedList<DexType> workList = new LinkedList<>();
+    workList.add(superClazz.type);
+    while (!workList.isEmpty()) {
+      DexType dexType = workList.removeFirst();
+      DexClass dexClass = appView.definitionFor(dexType);
+      if (dexClass != null) {
+        if (dexClass.superType != factory.objectType) {
+          workList.add(dexClass.superType);
+        }
+        for (DexType itf : dexClass.interfaces.values) {
+          if (emulatedInterfaces.containsKey(itf)) {
+            itfs.add(itf);
+          } else {
+            workList.add(itf);
+          }
+        }
+      }
+    }
+    return itfs;
+  }
+
   /**
    * Move static and default interface methods to companion classes, add missing methods to forward
    * to moved default methods implementation.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
new file mode 100644
index 0000000..5b5b420
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CustomCollectionSuperCallsTest extends CoreLibDesugarTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public CustomCollectionSuperCallsTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testCustomCollectionSuperCallsD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    D8TestRunResult d8TestRunResult =
+        testForD8()
+            .addInnerClasses(CustomCollectionSuperCallsTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .addDesugaredCoreLibraryRunClassPath(
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
+            .run(parameters.getRuntime(), Executor.class)
+            .assertSuccess();
+    assertLines2By2Correct(d8TestRunResult.getStdOut());
+  }
+
+  static class Executor {
+
+    // ArrayList spliterator is Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED.
+
+    public static void main(String[] args) {
+      rawTypes();
+      inheritedTypes();
+    }
+
+    public static void rawTypes() {
+      Spliterator<String> stringSpliterator;
+
+      stringSpliterator = new MyArrayListOverride().superSpliterator();
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println(true);
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println(false);
+
+      stringSpliterator = new MyArrayListOverrideSubclass().superSpliterator();
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println(false);
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println(true);
+
+      stringSpliterator = new MyArrayListNoOverride().superSpliterator();
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println(true);
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println(false);
+
+      stringSpliterator = new MyArrayListSubclassNoOverride().superSpliterator();
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println(true);
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println(false);
+    }
+
+    public static void inheritedTypes() {
+      Spliterator<String> stringSpliterator;
+
+      stringSpliterator =
+          ((MyArrayListOverride) new MyArrayListOverrideSubclass()).superSpliterator();
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println(false);
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println(true);
+
+      stringSpliterator =
+          ((MyArrayListNoOverride) new MyArrayListSubclassNoOverride()).superSpliterator();
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println(true);
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println(false);
+    }
+  }
+
+  static class MyArrayListOverride extends ArrayList<String> {
+
+    @Override
+    public Spliterator<String> spliterator() {
+      return Spliterators.spliterator(this, Spliterator.IMMUTABLE);
+    }
+
+    public Spliterator<String> superSpliterator() {
+      return super.spliterator();
+    }
+  }
+
+  static class MyArrayListOverrideSubclass extends MyArrayListOverride {
+
+    @Override
+    public Spliterator<String> superSpliterator() {
+      return super.spliterator();
+    }
+
+    // Unused, but prove the super invoke won't resolve into it.
+    @Override
+    public Spliterator<String> spliterator() {
+      return Spliterators.spliterator(this, Spliterator.IMMUTABLE | Spliterator.ORDERED);
+    }
+  }
+
+  static class MyArrayListNoOverride extends ArrayList<String> {
+
+    public Spliterator<String> superSpliterator() {
+      return super.spliterator();
+    }
+  }
+
+  static class MyArrayListSubclassNoOverride extends MyArrayListNoOverride {
+    public Spliterator<String> superSpliterator() {
+      return super.spliterator();
+    }
+
+    // Unused, but prove the super invoke won't resolve into it.
+    @Override
+    public Spliterator<String> spliterator() {
+      return Spliterators.spliterator(this, Spliterator.IMMUTABLE | Spliterator.ORDERED);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
index afae3b0..e9f221d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import java.util.LinkedHashSet;
+import java.util.Set;
 import java.util.Spliterator;
 import java.util.Spliterators;
 import org.junit.Test;
@@ -43,15 +44,22 @@
     assertLines2By2Correct(stdOut);
   }
 
+  @SuppressWarnings("WeakerAccess")
   static class Executor {
-    @SuppressWarnings("RedundantOperationOnEmptyContainer")
-    public static void main(String[] args) {
-      Spliterator<String> spliterator;
 
+    public static void main(String[] args) {
       // Spliterator of Set is only distinct.
       // Spliterator of LinkedHashSet is distinct and ordered.
       // Spliterator of CustomLinkedHashSetOverride is distinct, ordered and immutable.
       // If an incorrect method is found, characteristics are incorrect.
+      rawTypes();
+      linkedHashSetType();
+      setType();
+    }
+
+    @SuppressWarnings("RedundantOperationOnEmptyContainer")
+    public static void rawTypes() {
+      Spliterator<String> spliterator;
 
       spliterator = new LinkedHashSet<String>().spliterator();
       System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
@@ -92,6 +100,70 @@
       System.out.println("true");
       System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
       System.out.println("true");
+
+      spliterator = new SubclassNoOverride<String>().superSpliterator();
+      System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println("false");
+    }
+
+    @SuppressWarnings("RedundantOperationOnEmptyContainer")
+    public static void linkedHashSetType() {
+      System.out.println("-");
+      System.out.println("-");
+      Spliterator<String> spliterator;
+
+      spliterator =
+          ((LinkedHashSet<String>) new CustomLinkedHashSetOverride<String>()).spliterator();
+      System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println("true");
+
+      spliterator =
+          ((LinkedHashSet<String>) new CustomLinkedHashSetNoOverride<String>()).spliterator();
+      System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println("false");
+    }
+
+    @SuppressWarnings("RedundantOperationOnEmptyContainer")
+    public static void setType() {
+      System.out.println("-");
+      System.out.println("-");
+      Spliterator<String> spliterator;
+
+      spliterator = ((Set<String>) new LinkedHashSet<String>()).spliterator();
+      System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println("false");
+
+      spliterator = ((Set<String>) new CustomLinkedHashSetOverride<String>()).spliterator();
+      System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println("true");
+
+      spliterator = ((Set<String>) new CustomLinkedHashSetNoOverride<String>()).spliterator();
+      System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println("false");
     }
   }
 
@@ -109,6 +181,7 @@
   }
 
   static class SubclassOverride<E> extends CustomLinkedHashSetOverride<E> {
+
     @Override
     public Spliterator<E> superSpliterator() {
       return super.spliterator();
@@ -117,4 +190,11 @@
 
   @SuppressWarnings("WeakerAccess")
   static class CustomLinkedHashSetNoOverride<E> extends LinkedHashSet<E> {}
+
+  static class SubclassNoOverride<E> extends CustomLinkedHashSetNoOverride<E> {
+
+    public Spliterator<E> superSpliterator() {
+      return super.spliterator();
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index f2889ca..59db52d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -159,7 +159,7 @@
     assertInvokeStaticMatching(invokes, 2, "Collection$-EL;->stream");
     assertInvokeInterfaceMatching(invokes, 3, "Set;->iterator");
     assertInvokeStaticMatching(invokes, 4, "Collection$-EL;->stream");
-    assertInvokeStaticMatching(invokes, 5, "DesugarLinkedHashSet;->spliterator");
+    assertInvokeStaticMatching(invokes, 5, "Set$-EL;->spliterator");
     assertInvokeInterfaceMatching(invokes, 9, "Iterator;->remove");
     assertInvokeStaticMatching(invokes, 10, "DesugarArrays;->spliterator");
     assertInvokeStaticMatching(invokes, 11, "DesugarArrays;->spliterator");
@@ -213,9 +213,6 @@
             "    j$.util.Spliterator spliterator(java.lang.Object[], int, int);",
             "}",
             "-keep class j$.util.stream.IntStream",
-            "-keep class j$.util.DesugarLinkedHashSet {",
-            "    j$.util.Spliterator spliterator(java.util.LinkedHashSet);",
-            "}",
             "-keep class j$.util.stream.Stream",
             "-keep class j$.util.Spliterator",
             "-keep class j$.util.function.ToIntFunction { *; }");
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 bc6054a..b20a716 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
@@ -6,9 +6,12 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import java.time.Instant;
+import java.time.ZonedDateTime;
 import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.IntUnaryOperator;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -65,6 +68,7 @@
 
     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");
 
@@ -82,8 +86,12 @@
       MyCalendarNoOverride myCalN = new MyCalendarNoOverride(1990, 2, 22);
       System.out.println(myCalN.toZonedDateTime());
       System.out.println("1990-03-22T00:00Z[GMT]");
+      System.out.println(myCalN.superToZonedDateTime());
+      System.out.println("1990-03-22T00:00Z[GMT]");
       System.out.println(myCalN.toInstant());
       System.out.println("1990-03-22T00:00:00Z");
+      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);
@@ -93,16 +101,16 @@
       MyDateNoOverride myDateN = new MyDateNoOverride(123456789);
       System.out.println(myDateN.toInstant());
       System.out.println("1970-01-02T10:17:36.789Z");
+      System.out.println(myDateN.superToInstant());
+      System.out.println("1970-01-02T10:17:36.789Z");
 
       MyAtomicInteger myAtomicInteger = new MyAtomicInteger(42);
       System.out.println(myAtomicInteger.getAndUpdate(x -> x + 1));
       System.out.println("42");
-      System.out.println(myAtomicInteger.getAndUpdate(x -> x + 5));
+      System.out.println(myAtomicInteger.superGetAndUpdate(x -> x + 2));
       System.out.println("43");
       System.out.println(myAtomicInteger.updateAndGet(x -> x + 100));
-      System.out.println("148");
-      System.out.println(myAtomicInteger.updateAndGet(x -> x + 500));
-      System.out.println("648");
+      System.out.println("145");
     }
   }
 
@@ -124,6 +132,14 @@
     public MyCalendarNoOverride(int year, int month, int dayOfMonth) {
       super(year, month, dayOfMonth);
     }
+
+    public Instant superToInstant() {
+      return super.toInstant();
+    }
+
+    public ZonedDateTime superToZonedDateTime() {
+      return super.toZonedDateTime();
+    }
   }
 
   // static class MyDateOverride extends Date {
@@ -143,6 +159,10 @@
     public MyDateNoOverride(long date) {
       super(date);
     }
+
+    public Instant superToInstant() {
+      return super.toInstant();
+    }
   }
 
   static class MyAtomicInteger extends AtomicInteger {
@@ -150,5 +170,9 @@
     public MyAtomicInteger(int initialValue) {
       super(initialValue);
     }
+
+    public int superGetAndUpdate(IntUnaryOperator op) {
+      return super.getAndUpdate(op);
+    }
   }
 }