Revert "Revert "Desugar super calls in default methods targeting library""

This reverts commit 33ae86d80351efc4d632452331d06cb97e42f2a7.

Bug: 191690646
Change-Id: I90d5777a130c7e6780be53be3ab22b1f94c8bb8d
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
index 5c89290..2f5144a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.desugar.DesugarDescription.ScanCallback;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
@@ -104,46 +105,46 @@
                 getThrowInstructions(
                     appView,
                     invoke,
-                    resolutionResult,
                     localStackAllocator,
                     eventConsumer,
                     context,
-                    methodProcessingContext))
+                    methodProcessingContext,
+                    getMethodSynthesizerForThrowing(appView, invoke, resolutionResult, context)))
         .build();
   }
 
+  public static DesugarDescription computeInvokeAsThrowNSMERewrite(
+      AppView<?> appView, CfInvoke invoke, ScanCallback scanCallback) {
+    DesugarDescription.Builder builder =
+        DesugarDescription.builder()
+            .setDesugarRewrite(
+                (freshLocalProvider,
+                    localStackAllocator,
+                    eventConsumer,
+                    context,
+                    methodProcessingContext,
+                    dexItemFactory) ->
+                    getThrowInstructions(
+                        appView,
+                        invoke,
+                        localStackAllocator,
+                        eventConsumer,
+                        context,
+                        methodProcessingContext,
+                        UtilityMethodsForCodeOptimizations
+                            ::synthesizeThrowNoSuchMethodErrorMethod));
+    builder.addScanEffect(scanCallback);
+    return builder.build();
+  }
+
   private static Collection<CfInstruction> getThrowInstructions(
       AppView<?> appView,
       CfInvoke invoke,
-      MethodResolutionResult resolutionResult,
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
-    MethodSynthesizerConsumer methodSynthesizerConsumer = null;
-    if (resolutionResult == null) {
-      methodSynthesizerConsumer =
-          UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
-    } else if (resolutionResult.isSingleResolution()) {
-      if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
-        methodSynthesizerConsumer =
-            UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
-      }
-    } else if (resolutionResult.isFailedResolution()) {
-      FailedResolutionResult failedResolutionResult = resolutionResult.asFailedResolution();
-      AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
-      if (failedResolutionResult.isIllegalAccessErrorResult(context.getHolder(), appInfo)) {
-        methodSynthesizerConsumer =
-            UtilityMethodsForCodeOptimizations::synthesizeThrowIllegalAccessErrorMethod;
-      } else if (failedResolutionResult.isNoSuchMethodErrorResult(context.getHolder(), appInfo)) {
-        methodSynthesizerConsumer =
-            UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
-      } else if (failedResolutionResult.isIncompatibleClassChangeErrorResult()) {
-        methodSynthesizerConsumer =
-            UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
-      }
-    }
-
+      MethodProcessingContext methodProcessingContext,
+      MethodSynthesizerConsumer methodSynthesizerConsumer) {
     if (methodSynthesizerConsumer == null) {
       assert false;
       return null;
@@ -197,4 +198,32 @@
     }
     return replacement;
   }
+
+  private static MethodSynthesizerConsumer getMethodSynthesizerForThrowing(
+      AppView<?> appView,
+      CfInvoke invoke,
+      MethodResolutionResult resolutionResult,
+      ProgramMethod context) {
+    if (resolutionResult == null) {
+      return UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
+    } else if (resolutionResult.isSingleResolution()) {
+      if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
+        return UtilityMethodsForCodeOptimizations
+            ::synthesizeThrowIncompatibleClassChangeErrorMethod;
+      }
+    } else if (resolutionResult.isFailedResolution()) {
+      FailedResolutionResult failedResolutionResult = resolutionResult.asFailedResolution();
+      AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
+      if (failedResolutionResult.isIllegalAccessErrorResult(context.getHolder(), appInfo)) {
+        return UtilityMethodsForCodeOptimizations::synthesizeThrowIllegalAccessErrorMethod;
+      } else if (failedResolutionResult.isNoSuchMethodErrorResult(context.getHolder(), appInfo)) {
+        return UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
+      } else if (failedResolutionResult.isIncompatibleClassChangeErrorResult()) {
+        return UtilityMethodsForCodeOptimizations
+            ::synthesizeThrowIncompatibleClassChangeErrorMethod;
+      }
+    }
+
+    return null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index ce7e4f8..ba07e77 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -44,6 +44,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.structural.Ordered;
 import com.google.common.collect.Iterables;
@@ -707,7 +708,23 @@
       }
     }
 
-    return computeEmulatedInterfaceInvokeSpecial(clazz, invokedMethod, context);
+    DesugarDescription emulatedInterfaceDesugaring =
+        computeEmulatedInterfaceInvokeSpecial(clazz, invokedMethod, context);
+    if (!emulatedInterfaceDesugaring.needsDesugaring() && context.isDefaultMethod()) {
+      return AlwaysThrowingInstructionDesugaring.computeInvokeAsThrowNSMERewrite(
+          appView,
+          invoke,
+          () ->
+              appView
+                  .reporter()
+                  .warning(
+                      new StringDiagnostic(
+                          "Interface method desugaring has inserted NoSuchMethodError replacing a"
+                              + " super call in "
+                              + context.toSourceString(),
+                          context.getOrigin())));
+    }
+    return emulatedInterfaceDesugaring;
   }
 
   private DesugarDescription computeEmulatedInterfaceInvokeSpecial(
diff --git a/src/main/java/com/android/tools/r8/utils/QuadConsumer.java b/src/main/java/com/android/tools/r8/utils/QuadConsumer.java
new file mode 100644
index 0000000..3249ce0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/QuadConsumer.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2022, 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.utils;
+
+public interface QuadConsumer<S, T, U, V> {
+
+  void accept(S s, T t, U u, V v);
+}
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index 2e7cd20..480f7b4 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.QuadConsumer;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
@@ -48,6 +49,36 @@
     Assert.assertEquals(javaResult.stdout, r8ShakenResult.stdout);
   }
 
+  protected void ensureCustomCheck(
+      QuadConsumer<ProcessResult, ProcessResult, ProcessResult, ProcessResult> checker,
+      String main,
+      AndroidApiLevel apiLevel,
+      List<String> args,
+      byte[]... classes)
+      throws Exception {
+    AndroidApp app = buildAndroidApp(classes);
+    Consumer<InternalOptions> setMinApiLevel = o -> o.setMinApiLevel(apiLevel);
+    ProcessResult javaResult = runOnJavaRaw(main, Arrays.asList(classes), args);
+    Consumer<ArtCommandBuilder> cmdBuilder =
+        builder -> {
+          for (String arg : args) {
+            builder.appendProgramArgument(arg);
+          }
+        };
+    ProcessResult d8Result =
+        runOnArtRaw(compileWithD8(app, setMinApiLevel), main, cmdBuilder, null);
+    ProcessResult r8Result =
+        runOnArtRaw(compileWithR8(app, setMinApiLevel), main, cmdBuilder, null);
+    ProcessResult r8ShakenResult =
+        runOnArtRaw(
+            compileWithR8(
+                app, keepMainProguardConfiguration(main) + "-dontobfuscate\n", setMinApiLevel),
+            main,
+            cmdBuilder,
+            null);
+    checker.accept(javaResult, d8Result, r8Result, r8ShakenResult);
+  }
+
   protected void ensureSameOutput(String main, byte[]... classes) throws Exception {
     AndroidApp app = buildAndroidApp(classes);
     ensureSameOutput(main, app, false, classes);
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultMethodInvokeSuperOnDefaultLibraryMethodTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultMethodInvokeSuperOnDefaultLibraryMethodTest.java
new file mode 100644
index 0000000..0cb64c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultMethodInvokeSuperOnDefaultLibraryMethodTest.java
@@ -0,0 +1,175 @@
+// Copyright (c) 2022, 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.desugaring.interfacemethods;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DefaultMethodInvokeSuperOnDefaultLibraryMethodTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().build();
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("1", "2");
+
+  private boolean runtimeHasConsumerInterface(TestParameters parameters) {
+    // java,util.function.Consumer was introduced at API level 24.
+    return parameters.asDexRuntime().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+  }
+
+  @Test
+  public void testD8WithDefaultInterfaceMethodDesugaringWithAPIInLibrary() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertOnlyWarnings()
+                    .assertWarningsMatch(
+                        allOf(
+                            diagnosticType(StringDiagnostic.class),
+                            diagnosticMessage(
+                                containsString(
+                                    "Interface method desugaring has inserted NoSuchMethodError"
+                                        + " replacing a super call in")),
+                            diagnosticMessage(containsString("forEachPrint")))))
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            // If the platform does not have java.util.function.Consumer the lambda instantiation
+            // will throw NoClassDefFoundError as it implements java.util.function.Consumer.
+            // Otherwise, the generated code will throw NoSuchMethodError.
+            runtimeHasConsumerInterface(parameters),
+            b -> b.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testD8WithDefaultInterfaceMethodDesugaringWithoutAPIInLibrary() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.M))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertOnlyWarnings()
+                    .assertWarningsMatch(
+                        diagnosticType(InterfaceDesugarMissingTypeDiagnostic.class)))
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            // If the platform does not have java.util.function.Consumer the lambda instantiation
+            // will throw NoClassDefFoundError as it implements java.util.function.Consumer.
+            // Otherwise, the generated code will throw NoSuchMethodError.
+            runtimeHasConsumerInterface(parameters),
+            b -> b.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testD8WithDefaultInterfaceMethodSupport() throws Exception {
+    assumeTrue(
+        parameters.asDexRuntime().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.N)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8WithDefaultInterfaceMethodDesugaringWithAPIInLibrary() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  @Test
+  public void testR8WithDefaultInterfaceMethodDesugaringWithoutAPIInLibrary() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.M))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .addKeepMainRule(TestClass.class)
+        .addDontWarn(Consumer.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            // If the platform does not have java.util.function.Consumer the lambda instantiation
+            // will throw NoClassDefFoundError as it implements java.util.function.Consumer.
+            // Otherwise, the generated code will throw NoSuchMethodError.
+            runtimeHasConsumerInterface(parameters),
+            b -> b.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testR8WithDefaultInterfaceMethodSupport() throws Exception {
+    assumeTrue(
+        parameters.asDexRuntime().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.N)
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  interface IntegerIterable extends Iterable<Integer> {
+    default void forEachPrint() {
+      Iterable.super.forEach(System.out::println);
+    }
+  }
+
+  static class IntegerIterable1And2 implements IntegerIterable {
+
+    @Override
+    public Iterator<Integer> iterator() {
+      List<Integer> result = new ArrayList<>();
+      result.add(1);
+      result.add(2);
+      return result.iterator();
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new IntegerIterable1And2().forEachPrint();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
index 5b700d5..bdbd2b3 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
@@ -7,7 +7,6 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.VmTestRunner;
@@ -28,6 +27,7 @@
 import java.io.InputStream;
 import java.util.Collections;
 import java.util.List;
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.objectweb.asm.ClassReader;
@@ -137,10 +137,18 @@
         ToolHelper.getClassAsBytes(TestMainDefault0.class));
   }
 
-  @Test(expected = CompilationFailedException.class)
+  @Test
   @IgnoreForRangeOfVmVersions(from = Version.V7_0_0, to = Version.V13_MASTER) // No desugaring
   public void testInvokeDefault1() throws Exception {
-    ensureSameOutput(
+    ensureCustomCheck(
+        (javaResult, d8Result, r8Result, r8ShakenResult) -> {
+          Assert.assertEquals(1, d8Result.exitCode);
+          Assert.assertTrue(d8Result.stderr.contains("NoSuchMethodError"));
+          Assert.assertEquals(1, r8Result.exitCode);
+          Assert.assertTrue(r8Result.stderr.contains("NoSuchMethodError"));
+          // R8 can determine that the super interface invokes are all overridden in the program
+          Assert.assertEquals(javaResult.stdout, r8ShakenResult.stdout);
+        },
         TestMainDefault1.class.getCanonicalName(),
         ToolHelper.getMinApiLevelForDexVm(),
         getArgs(AndroidApiLevel.N.getLevel()),