Fix invoke-super of a default method in desugared library for cf to cf

This forces IR processing for a method when there is an invoke-super
targeting a default method in the desugared library. The rewriting
of that currently requires IR processing.

Bug: 177221295
Change-Id: I8f15adde05f0b348517fc48e49fd230a64d29889
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
index a9bbb90..a89e3f9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
@@ -56,14 +56,14 @@
   public void registerInvokeVirtual(DexMethod method) {
     registerBackportedMethodRewriting(method);
     registerLibraryRetargeting(method, false);
-    registerInterfaceMethodRewriting(method);
+    registerInterfaceMethodRewriting(method, false);
     registerDesugaredLibraryAPIConverter(method);
   }
 
   @Override
   public void registerInvokeDirect(DexMethod method) {
     registerLibraryRetargeting(method, false);
-    registerInterfaceMethodRewriting(method);
+    registerInterfaceMethodRewriting(method, false);
     registerDesugaredLibraryAPIConverter(method);
   }
 
@@ -80,10 +80,11 @@
     }
   }
 
-  private void registerInterfaceMethodRewriting(DexMethod method) {
+  private void registerInterfaceMethodRewriting(DexMethod method, boolean isInvokeSuper) {
     if (!needsDesugarging) {
       needsDesugarging =
-          interfaceMethodRewriter != null && interfaceMethodRewriter.needsRewriting(method);
+          interfaceMethodRewriter != null
+              && interfaceMethodRewriter.needsRewriting(method, isInvokeSuper, appView);
     }
   }
 
@@ -108,14 +109,14 @@
     registerTwrCloseResourceRewriting(method);
     registerBackportedMethodRewriting(method);
     registerLibraryRetargeting(method, false);
-    registerInterfaceMethodRewriting(method);
+    registerInterfaceMethodRewriting(method, false);
     registerDesugaredLibraryAPIConverter(method);
   }
 
   @Override
   public void registerInvokeInterface(DexMethod method) {
     registerLibraryRetargeting(method, true);
-    registerInterfaceMethodRewriting(method);
+    registerInterfaceMethodRewriting(method, false);
     registerDesugaredLibraryAPIConverter(method);
   }
 
@@ -136,7 +137,7 @@
   @Override
   public void registerInvokeSuper(DexMethod method) {
     registerLibraryRetargeting(method, false);
-    registerInterfaceMethodRewriting(method);
+    registerInterfaceMethodRewriting(method, true);
     registerDesugaredLibraryAPIConverter(method);
   }
 
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 27216c1..9ea3aa3 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
@@ -216,7 +216,16 @@
     return emulatedInterfaces.containsKey(itf);
   }
 
-  public boolean needsRewriting(DexMethod method) {
+  public boolean needsRewriting(DexMethod method, boolean isInvokeSuper, AppView<?> appView) {
+    if (isInvokeSuper) {
+      DexClass clazz = appView.appInfo().definitionFor(method.getHolderType());
+      if (clazz != null
+          && clazz.isLibraryClass()
+          && clazz.isInterface()
+          && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
+        return true;
+      }
+    }
     return emulatedMethods.contains(method.getName());
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
index f6e0d0a..f998b89 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
@@ -4,16 +4,23 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.CodeMatchers;
+import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 import org.jetbrains.annotations.NotNull;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -25,12 +32,13 @@
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
   private static final String EXPECTED_OUTPUT =
-      StringUtils.lines("1", "2", "3", "4", "5", "Count: 4");
+      StringUtils.lines("1", "2", "3", "4", "5", "Count: 4", "1", "2", "3", "4", "5");
 
   @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
   public static List<Object[]> data() {
     return buildParameters(
-        BooleanUtils.values(), getTestParameters().withAllRuntimes().withAllApiLevels().build());
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
   }
 
   public IterableTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
@@ -38,6 +46,54 @@
     this.parameters = parameters;
   }
 
+  private void inspect(CodeInspector inspector) {
+    if (parameters
+        .getApiLevel()
+        .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport())) {
+      assertThat(
+          inspector.clazz(MyIterableSub.class).uniqueMethodWithFinalName("myForEach"),
+          CodeMatchers.invokesMethod(null, MyIterable.class.getTypeName(), "forEach", null));
+    } else {
+      assertThat(
+          inspector.clazz(MyIterableSub.class).uniqueMethodWithFinalName("myForEach"),
+          CodeMatchers.invokesMethod(null, "j$.lang.Iterable$-CC", "$default$forEach", null));
+    }
+  }
+
+  @Test
+  public void testIterableD8Cf() throws Exception {
+    // Only test without shrinking desugared library.
+    Assume.assumeFalse(shrinkDesugaredLibrary);
+    // Use D8 to desugar with Java classfile output.
+    Path jar =
+        testForD8(Backend.CF)
+            .addInnerClasses(IterableTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel())
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+
+    if (parameters.getRuntime().isDex()) {
+      // Convert to DEX without desugaring and run.
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .compile()
+          .addRunClasspathFiles(buildDesugaredLibrary(parameters.getApiLevel()))
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+    } else {
+      // Run on the JVM with desugared library on classpath.
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(buildDesugaredLibraryClassFile(parameters.getApiLevel()))
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+    }
+  }
+
   @Test
   public void testIterable() throws Exception {
     if (parameters.isCfRuntime()) {
@@ -69,6 +125,8 @@
       iterable.forEach(System.out::println);
       Stream<Integer> stream = StreamSupport.stream(iterable.spliterator(), false);
       System.out.println("Count: " + stream.filter(x -> x != 3).count());
+      MyIterableSub<Integer> iterableSub = new MyIterableSub<>(Arrays.asList(1, 2, 3, 4, 5));
+      iterableSub.myForEach(System.out::println);
     }
   }
 
@@ -86,4 +144,15 @@
       return collection.iterator();
     }
   }
+
+  static class MyIterableSub<E> extends MyIterable<E> {
+
+    public MyIterableSub(Collection<E> collection) {
+      super(collection);
+    }
+
+    public void myForEach(Consumer<E> consumer) {
+      super.forEach(consumer);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
index efb77c9..31bbcbd 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
@@ -5,15 +5,15 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -22,6 +22,7 @@
 import com.android.tools.r8.utils.codeinspector.CheckCastInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.CodeMatchers;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -58,8 +59,8 @@
           "GMT",
           "GMT",
           "true",
+          "true",
           "Hello, world");
-  boolean canUseDefaultAndStaticInterfaceMethods;
 
   @Parameters(name = "{2}, shrinkDesugaredLibrary: {0}, traceReferencesKeepRules {1}")
   public static List<Object[]> data() {
@@ -78,13 +79,17 @@
     this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
     this.traceReferencesKeepRules = traceReferencesKeepRules;
     this.parameters = parameters;
-    this.canUseDefaultAndStaticInterfaceMethods =
-        parameters
-            .getApiLevel()
-            .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport());
   }
 
-  private void checkRewrittenInvokes(CodeInspector inspector) {
+  private void checkRewrittenInvokesForD8(CodeInspector inspector) {
+    checkRewrittenInvokes(inspector, false);
+  }
+
+  private void checkRewrittenInvokesForR8(CodeInspector inspector) {
+    checkRewrittenInvokes(inspector, true);
+  }
+
+  private void checkRewrittenInvokes(CodeInspector inspector, boolean isR8) {
     Set<String> expectedInvokeHolders;
     Set<String> expectedCatchGuards;
     Set<String> expectedCheckCastType;
@@ -133,6 +138,31 @@
             .map(TypeSubject::toString)
             .collect(Collectors.toSet());
     assertEquals(expectedCatchGuards, foundCatchGuards);
+
+    if (isR8) {
+      assertThat(
+          inspector.clazz(TemporalAccessorImpl.class).uniqueMethodWithFinalName("query"),
+          not(isPresent()));
+    } else {
+      assertThat(
+          inspector.clazz(TemporalAccessorImplSub.class).uniqueMethodWithFinalName("query"),
+          CodeMatchers.invokesMethod(
+              null, TemporalAccessorImpl.class.getTypeName(), "query", null));
+    }
+    if (parameters
+        .getApiLevel()
+        .isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport())) {
+      assertThat(
+          inspector.clazz(TemporalAccessorImpl.class).uniqueMethodWithName("query"),
+          not(isPresent()));
+    } else {
+      assertThat(
+          inspector
+              .clazz(isR8 ? TemporalAccessorImplSub.class : TemporalAccessorImpl.class)
+              .uniqueMethodWithFinalName("query"),
+          CodeMatchers.invokesMethod(
+              null, "j$.time.temporal.TemporalAccessor$-CC", "$default$query", null));
+    }
   }
 
   private String desugaredLibraryKeepRules(
@@ -164,7 +194,7 @@
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
-            .inspect(this::checkRewrittenInvokes)
+            .inspect(this::checkRewrittenInvokesForD8)
             .writeToZip();
 
     String desugaredLibraryKeepRules;
@@ -177,37 +207,27 @@
     }
 
     // Determine desugared library keep rules.
-    TestRunResult<?> result;
     if (parameters.getRuntime().isDex()) {
       // Convert to DEX without desugaring and run.
-      result =
-          testForD8()
-              .addProgramFiles(jar)
-              .setMinApi(parameters.getApiLevel())
-              .disableDesugaring()
-              .compile()
-              .addDesugaredCoreLibraryRunClassPath(
-                  this::buildDesugaredLibrary,
-                  parameters.getApiLevel(),
-                  desugaredLibraryKeepRules,
-                  shrinkDesugaredLibrary)
-              .run(parameters.getRuntime(), TestClass.class);
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary,
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(expectedOutput);
     } else {
       // Run on the JVM with desugared library on classpath.
-      result =
-          testForJvm()
-              .addProgramFiles(jar)
-              .addRunClasspathFiles(buildDesugaredLibraryClassFile(parameters.getApiLevel()))
-              .run(parameters.getRuntime(), TestClass.class);
-    }
-    if (canUseDefaultAndStaticInterfaceMethods) {
-      result.assertSuccessWithOutput(expectedOutput);
-    } else {
-      result.assertFailureWithErrorThatMatches(
-          anyOf(
-              containsString(VerifyError.class.getName()),
-              containsString(IncompatibleClassChangeError.class.getName()),
-              containsString(AbstractMethodError.class.getName())));
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(buildDesugaredLibraryClassFile(parameters.getApiLevel()))
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(expectedOutput);
     }
   }
 
@@ -223,7 +243,7 @@
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
-            .inspect(this::checkRewrittenInvokes);
+            .inspect(this::checkRewrittenInvokesForD8);
     result
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
@@ -244,11 +264,12 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(JavaTimeTest.class)
             .addKeepMainRule(TestClass.class)
+            .enableNoVerticalClassMergingAnnotations()
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .enableInliningAnnotations()
             .compile()
-            .inspect(this::checkRewrittenInvokes);
+            .inspect(this::checkRewrittenInvokesForR8);
     result
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
@@ -259,6 +280,31 @@
         .assertSuccessWithOutput(expectedOutput);
   }
 
+  @NoVerticalClassMerging
+  static class TemporalAccessorImpl implements TemporalAccessor {
+    @Override
+    public boolean isSupported(TemporalField field) {
+      return false;
+    }
+
+    @Override
+    public long getLong(TemporalField field) {
+      throw new DateTimeException("Mock");
+    }
+  }
+
+  @NoVerticalClassMerging
+  static class TemporalAccessorImplSub extends TemporalAccessorImpl {
+    @SuppressWarnings("unchecked")
+    @Override
+    public <R> R query(TemporalQuery<R> query) {
+      if (query == TemporalQueries.zoneId()) {
+        return (R) ZoneId.of("GMT");
+      }
+      return super.query(query);
+    }
+  }
+
   static class TestClass {
 
     @NeverInline
@@ -296,6 +342,11 @@
       System.out.println(ZoneId.from(mock).equals(ZoneId.of("GMT")));
     }
 
+    public static void superInvokeOnLibraryDesugaredDefaultMethodFromSubclass() {
+      TemporalAccessor mock = new TemporalAccessorImplSub();
+      System.out.println(ZoneId.from(mock).equals(ZoneId.of("GMT")));
+    }
+
     public static void main(String[] args) {
       java.time.Clock.systemDefaultZone();
       try {
@@ -317,6 +368,7 @@
       System.out.println(timeZone.toZoneId().getId());
 
       superInvokeOnLibraryDesugaredDefaultMethod();
+      superInvokeOnLibraryDesugaredDefaultMethodFromSubclass();
 
       System.out.println("Hello, world");
     }