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()),