Revert "Revert "Support CONSTANT_Dynamic in interface default methods (part 1)""
This reverts commit 70f5b0d0addfd498ed11c774a51be99b131df103.
Change-Id: If55bb36a2f79d91469caa4697fae058a354512d8
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
index e9fa936..6bd38bb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -54,6 +54,7 @@
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.MethodSynthesizerConsumer;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
@@ -85,8 +86,9 @@
public final DexField constantValueField;
private final DexMethod getConstMethod;
private final Behaviour behaviour;
- private DexEncodedMethod bootstrapMethodImpl;
+ private DexMethod bootstrapMethodReference;
private DexMethod finalBootstrapMethodReference;
+ private boolean isFinalBootstrapMethodReferenceOnInterface;
// Considered final but is set after due to circularity in allocation.
private DexProgramClass clazz = null;
@@ -115,7 +117,7 @@
factory.createString("get"));
DexMethodHandle bootstrapMethodHandle = reference.getBootstrapMethod();
- DexMethod bootstrapMethodReference = bootstrapMethodHandle.asMethod();
+ bootstrapMethodReference = bootstrapMethodHandle.asMethod();
MethodResolutionResult resolution =
appView
.appInfoForDesugaring()
@@ -123,22 +125,34 @@
if (resolution.isSingleResolution()
&& resolution.asSingleResolution().getResolvedMethod().isStatic()) {
SingleResolutionResult result = resolution.asSingleResolution();
- bootstrapMethodImpl = result.getResolvedMethod();
+ if (bootstrapMethodHandle.isInterface
+ && appView.options().isInterfaceMethodDesugaringEnabled()) {
+ bootstrapMethodReference =
+ bootstrapMethodReference.withHolder(
+ InterfaceDesugaringSyntheticHelper.getCompanionClassType(
+ bootstrapMethodReference.getHolderType(), factory),
+ factory);
+ isFinalBootstrapMethodReferenceOnInterface = false;
+ } else {
+ assert bootstrapMethodReference.getHolderType() == resolution.getResolvedHolder().getType();
+ isFinalBootstrapMethodReferenceOnInterface = bootstrapMethodHandle.isInterface;
+ }
if (shouldRewriteBootstrapMethodSignature()) {
// The bootstrap method will have its signature modified to have type Object as its first
// argument.
this.finalBootstrapMethodReference =
factory.createMethod(
- result.getResolvedHolder().getType(),
+ bootstrapMethodReference.getHolderType(),
factory.createProto(
bootstrapMethodReference.getReturnType(),
factory.objectType,
factory.stringType,
factory.classType),
- bootstrapMethodImpl.getName());
+ bootstrapMethodReference.getName());
} else {
this.finalBootstrapMethodReference = bootstrapMethodReference;
// Ensure that the bootstrap method is accessible from the generated class.
+ DexEncodedMethod bootstrapMethodImpl = result.getResolvedMethod();
MethodAccessFlags flags = bootstrapMethodImpl.getAccessFlags();
flags.unsetPrivate();
flags.setPublic();
@@ -253,7 +267,11 @@
instructions.add(new CfConstNull());
instructions.add(new CfConstString(reference.getName()));
instructions.add(new CfConstClass(reference.getType()));
- instructions.add(new CfInvoke(INVOKESTATIC, finalBootstrapMethodReference, false));
+ instructions.add(
+ new CfInvoke(
+ INVOKESTATIC,
+ finalBootstrapMethodReference,
+ isFinalBootstrapMethodReferenceOnInterface));
instructions.add(new CfCheckCast(reference.getType()));
}
@@ -356,12 +374,12 @@
return;
}
DexProgramClass bootstrapMethodHolder =
- appView.definitionFor(bootstrapMethodImpl.getHolderType()).asProgramClass();
+ appView.definitionFor(bootstrapMethodReference.getHolderType()).asProgramClass();
DexEncodedMethod replacement =
bootstrapMethodHolder
.getMethodCollection()
.replaceDirectMethod(
- bootstrapMethodImpl.getReference(),
+ bootstrapMethodReference,
encodedMethod -> {
MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
// Ensure that the bootstrap method is accessible from the generated class.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
index 9ee3606..8db4171 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -156,7 +156,7 @@
}
// Gets the companion class for the interface `type`.
- static DexType getCompanionClassType(DexType type, DexItemFactory factory) {
+ public static DexType getCompanionClassType(DexType type, DexItemFactory factory) {
assert type.isClassType();
String descriptor = type.descriptor.toString();
String ccTypeDescriptor = getCompanionClassDescriptor(descriptor);
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 656955e..63eeb87 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -61,6 +61,23 @@
return self;
}
+ public T applyIf(
+ boolean value,
+ ThrowableConsumer<T> trueConsumer,
+ boolean value2,
+ ThrowableConsumer<T> trueConsumer2,
+ ThrowableConsumer<T> falseConsumer) {
+ T self = self();
+ if (value) {
+ trueConsumer.acceptWithRuntimeException(self);
+ } else if (value2) {
+ trueConsumer2.acceptWithRuntimeException(self);
+ } else {
+ falseConsumer.acceptWithRuntimeException(self);
+ }
+ return self;
+ }
+
@Deprecated
public RR run(String mainClass)
throws CompilationFailedException, ExecutionException, IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicInDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicInDefaultInterfaceMethodTest.java
new file mode 100644
index 0000000..a09ae6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicInDefaultInterfaceMethodTest.java
@@ -0,0 +1,151 @@
+// 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.desugar.constantdynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+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 ConstantDynamicInDefaultInterfaceMethodTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+ }
+
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("true", "true");
+ private static final Class<?> MAIN_CLASS = A.class;
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+ assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+ testForJvm()
+ .addProgramClasses(MAIN_CLASS)
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testDesugaring() throws Exception {
+ testForDesugaring(parameters)
+ .addProgramClasses(MAIN_CLASS)
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .applyIf(
+ // When not desugaring the CF code requires JDK 11.
+ DesugarTestConfiguration::isNotDesugared,
+ r -> {
+ if (parameters.isCfRuntime()
+ && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+ r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+ } else {
+ r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+ }
+ })
+ .applyIf(
+ DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MAIN_CLASS)
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_CLASS)
+ // TODO(b/198142613): There should not be a warnings on class references which are
+ // desugared away.
+ .applyIf(
+ parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+ b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+ // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+ .applyIf(
+ parameters.isCfRuntime(),
+ b -> {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ b.compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (not desugaring)")));
+ }));
+ },
+ // TODO(b/210485236): This should not fail for R8.
+ !parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring(),
+ b ->
+ b.run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+ b ->
+ b.run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT));
+ }
+
+ private byte[] getTransformedClasses() throws Exception {
+ return transformer(I.class)
+ .setVersion(CfVersion.V11)
+ .transformConstStringToConstantDynamic(
+ "condy1", I.class, "myConstant", "constantName", Object.class)
+ .transformConstStringToConstantDynamic(
+ "condy2", I.class, "myConstant", "constantName", Object.class)
+ .setPrivate(
+ I.class.getDeclaredMethod(
+ "myConstant", MethodHandles.Lookup.class, String.class, Class.class))
+ .transform();
+ }
+
+ public interface I {
+
+ default Object f() {
+ return "condy1"; // Will be transformed to Constant_DYNAMIC.
+ }
+
+ default Object g() {
+ return "condy2"; // Will be transformed to Constant_DYNAMIC.
+ }
+
+ /* private */ static Object myConstant(
+ MethodHandles.Lookup lookup, String name, Class<?> type) {
+ return new Object();
+ }
+ }
+
+ public static class A implements I {
+ public static void main(String[] args) {
+ A a = new A();
+ System.out.println(a.f() != null);
+ System.out.println(a.f() == a.g());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
index 9c33f50..46261e5 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.jacoco.JacocoClasses;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -50,7 +51,7 @@
public JacocoClasses testClasses;
private static final String MAIN_CLASS = TestRunner.class.getTypeName();
- private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!", "Hello from I!");
@BeforeClass
public static void setUpInput() throws IOException {
@@ -85,7 +86,7 @@
.run(parameters.getRuntime(), MAIN_CLASS)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
List<String> onTheFlyReport = testClasses.generateReport(agentOutputOnTheFly);
- assertEquals(2, onTheFlyReport.size());
+ assertEquals(3, onTheFlyReport.size());
// Run the instrumented code.
Path agentOutputOffline = output.resolve("offline");
@@ -114,7 +115,7 @@
// TODO(sgjesse): Need to figure out why there is no instrumentation output for newer VMs.
if (parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
List<String> report = testClasses.generateReport(agentOutput);
- assertEquals(2, report.size());
+ assertEquals(3, report.size());
} else {
assertFalse(Files.exists(agentOutput));
}
@@ -130,16 +131,23 @@
private static JacocoClasses testClasses(TemporaryFolder temp, CfVersion version)
throws IOException {
return new JacocoClasses(
- transformer(TestRunner.class)
- .setVersion(version) /*.setClassDescriptor("LTestRunner;")*/
- .transform(),
+ ImmutableList.of(
+ transformer(TestRunner.class).setVersion(version).transform(),
+ transformer(I.class).setVersion(version).transform()),
temp);
}
- static class TestRunner {
+ interface I {
+ default void m() {
+ System.out.println("Hello from I!");
+ }
+ }
+
+ static class TestRunner implements I {
public static void main(String[] args) {
System.out.println("Hello, world!");
+ new TestRunner().m();
}
}
}
diff --git a/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java b/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java
index b98cc63..dd2700f 100644
--- a/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java
+++ b/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -28,22 +29,29 @@
private final Path originalJar;
private final Path instrumentedJar;
- // Create JacocoClasses with just one class provided as bytes.
+ // Create JacocoClasses with just one class provided as class file bytes.
public JacocoClasses(byte[] clazz, TemporaryFolder temp) throws IOException {
+ this(ImmutableList.of(clazz), temp);
+ }
+
+ // Create JacocoClasses with multiple classes provided as class file bytes.
+ public JacocoClasses(List<byte[]> classes, TemporaryFolder temp) throws IOException {
this.temp = temp;
dir = temp.newFolder().toPath();
// Write the class to a .class file with package sub-directories.
- String typeName = TestBase.extractClassName(clazz);
- int lastDotIndex = typeName.lastIndexOf('.');
- String pkg = typeName.substring(0, lastDotIndex);
- String baseFileName = typeName.substring(lastDotIndex + 1) + CLASS_EXTENSION;
Path original = dir.resolve("original");
- Files.createDirectories(original);
- Path packageDir = original.resolve(pkg.replace(JAVA_PACKAGE_SEPARATOR, File.separatorChar));
- Files.createDirectories(packageDir);
- Path classFile = packageDir.resolve(baseFileName);
- Files.write(classFile, clazz);
+ for (byte[] clazz : classes) {
+ String typeName = TestBase.extractClassName(clazz);
+ int lastDotIndex = typeName.lastIndexOf('.');
+ String pkg = typeName.substring(0, lastDotIndex);
+ String baseFileName = typeName.substring(lastDotIndex + 1) + CLASS_EXTENSION;
+ Files.createDirectories(original);
+ Path packageDir = original.resolve(pkg.replace(JAVA_PACKAGE_SEPARATOR, File.separatorChar));
+ Files.createDirectories(packageDir);
+ Path classFile = packageDir.resolve(baseFileName);
+ Files.write(classFile, clazz);
+ }
// Run offline instrumentation.
Path instrumented = dir.resolve("instrumented");
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index a38f6f2..d9d0a54 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -976,7 +976,7 @@
DescriptorUtils.getClassBinaryName(bootstrapMethodHolder),
bootstrapMethodName,
bootstrapMethodSignature,
- false),
+ bootstrapMethodHolder.isInterface()),
new Object[] {}));
} else {
super.visitLdcInsn(value);