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();
+ }
+ }
+}