Don't do default interface method processing when merging DEX
This will now report UnsupportedStaticInterfaceMethodDiagnostic/
UnsupportedDefaultInterfaceMethodDiagnostic
when a v35 DEX has "hidden" static/default interface methods and
min API level is below 24.
Allow upgrading DEX v35 with "hidden" static/default interface
methods to v37 (min API level 24 and above).
Bug: b/507323858
Change-Id: I2783490b593b9b97f8b8663356676d91d9b10298
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index a388e9e..857ee4c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -867,6 +867,10 @@
classFileVersion = Ordered.minIgnoreNull(classFileVersion, version);
}
+ public boolean dexCodeOnInput() {
+ return hasCode() && getCode().isDexCode() && !hasClassFileVersion() && !isD8R8Synthesized();
+ }
+
public String qualifiedName() {
checkIfObsolete();
return getReference().qualifiedName();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 279c3f0..8a5b25c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -109,6 +109,9 @@
if (!method.getHolder().isInterface()) {
return;
}
+ if (method.getDefinition().dexCodeOnInput() && appView.options().passthroughDexCode) {
+ return;
+ }
if (desugaringMode == LIBRARY_DESUGARING_N_PLUS) {
processEmulatedInterfaceOnly(method, eventConsumer);
return;
diff --git a/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeV35WithIllegalStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeV35WithIllegalStaticInterfaceMethodTest.java
index d8d904e..6ac52c5 100644
--- a/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeV35WithIllegalStaticInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeV35WithIllegalStaticInterfaceMethodTest.java
@@ -4,54 +4,64 @@
package com.android.tools.r8.dexfilemerger;
+import static com.android.tools.r8.ToolHelper.DexVm.Version.V7_0_0;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DiagnosticsMatcher;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.UnsupportedDefaultInterfaceMethodDiagnostic;
+import com.android.tools.r8.errors.UnsupportedStaticInterfaceMethodDiagnostic;
import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
// Reproduction of b/507323858.
@RunWith(Parameterized.class)
public class DexMergeV35WithIllegalStaticInterfaceMethodTest extends TestBase {
- private final TestParameters parameters;
+ @Parameter(0)
+ public TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withNoneRuntime().build();
+ return getTestParameters()
+ .withNoneRuntime()
+ .withDexRuntimesStartingFromIncluding(V7_0_0)
+ .build();
}
- public DexMergeV35WithIllegalStaticInterfaceMethodTest(TestParameters parameters) {
- this.parameters = parameters;
+ private Path fakeDexV35(Path dex) throws IOException {
+ Path dex35 = temp.newFile("dex35.dex").toPath();
+ byte[] dexBytes = Files.readAllBytes(dex);
+ dexBytes[6] = 0x35;
+ Files.write(dex35, dexBytes, StandardOpenOption.CREATE);
+ return dex35;
}
@Test
- public void test() throws Exception {
- // Create DEX v37 with an interface with a static method and class initializer.
+ public void testStaticMethod() throws Exception {
+ assumeTrue(parameters.isNoneRuntime());
+ // Create DEX v37 with an interface with a static method.
Path dexDir =
testForD8()
.setMinApi(AndroidApiLevel.N)
- .addInnerClasses(getClass())
+ .addProgramClasses(I1.class)
.compile()
.writeToDirectory();
- // Fake DEX v35 for the v37 DEX
- Path dex35 = temp.newFile("dex35.dex").toPath();
- byte[] dexBytes = Files.readAllBytes(dexDir.resolve("classes.dex"));
- dexBytes[6] = 0x35;
- Files.write(dex35, dexBytes, StandardOpenOption.CREATE);
-
- // Run the v35 DEX with an interface with a static method and class initializer through DEX
- // merging.
+ // Run v35 DEX with an interface with a static method through DEX merging.
+ Path dex35 = fakeDexV35(dexDir.resolve("classes.dex"));
assertThrows(
CompilationFailedException.class,
() ->
@@ -63,22 +73,76 @@
diagnostics
.assertOnlyErrors()
.assertAllErrorsMatch(
- DiagnosticsMatcher.diagnosticException(
- NullPointerException.class))));
+ DiagnosticsMatcher.diagnosticType(
+ UnsupportedStaticInterfaceMethodDiagnostic.class))));
}
- interface I {
- Object o = new Object();
+ @Test
+ public void testDefaultMethod() throws Exception {
+ assumeTrue(parameters.isNoneRuntime());
+ // Create DEX v37 with an interface with a default method.
+ Path dexDir =
+ testForD8()
+ .setMinApi(AndroidApiLevel.N)
+ .addProgramClasses(I2.class)
+ .compile()
+ .writeToDirectory();
+ // Run v35 DEX with an interface with a default method through DEX merging.
+ Path dex35 = fakeDexV35(dexDir.resolve("classes.dex"));
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForD8(Backend.DEX)
+ .addProgramFiles(dex35)
+ .setMinApi(AndroidApiLevel.K)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertOnlyErrors()
+ .assertAllErrorsMatch(
+ DiagnosticsMatcher.diagnosticType(
+ UnsupportedDefaultInterfaceMethodDiagnostic.class))));
+ }
+
+ @Test
+ public void testAllowV35WithStaticAndDefaultInterfaceMethodsWhenCompilingToV37()
+ throws Exception {
+ assumeFalse(parameters.isNoneRuntime());
+ // Create DEX v37 with an interface with a static method and an interface with a default method.
+ Path dexDir =
+ testForD8()
+ .setMinApi(AndroidApiLevel.N)
+ .addInnerClasses(getClass())
+ .compile()
+ .writeToDirectory();
+
+ // Run the v35 DEX with an interface with a static method and an interface with a default method
+ // through DEX merging.
+ Path dex35 = fakeDexV35(dexDir.resolve("classes.dex"));
+ testForD8(Backend.DEX)
+ .addProgramFiles(dex35)
+ .setMinApi(AndroidApiLevel.N)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ interface I1 {
static String f() {
- return "Hello, world!";
+ return "Hello,";
+ }
+ }
+
+ interface I2 {
+ default String f() {
+ return " world!";
}
}
static class TestClass {
public static void main(String[] args) {
- System.out.println(I.f());
+ System.out.println(I1.f() + (new I2() {}).f());
}
}
}