Desugar super calls in default methods targeting library

This desugares default interface methods calling default methods
in the ilibrary by inserting code to throw a NoSuchMethodError.

Instead of these super calls causing a compile time error they now
cause a runtime error if used.

Bug: 191690646
Change-Id: Id5d88f9e0d6a534796a0ff923248e6146b3ac460
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 a6384e1..b288d9a 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;
@@ -105,46 +106,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;
@@ -198,4 +199,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 bc8ebd0..8e8541b 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;
@@ -711,7 +712,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/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();
+    }
+  }
+}