Reproduction of trace references missing keep rule on default methods
BUG=b/319190998
Change-Id: I8983616c4aae2f681b42ac510766259777f6af90
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 1792e97..fc90c26 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -280,7 +280,7 @@
"No need to use is/assumeR8TestParameters() when not using api levels for CF",
isCfRuntime() && apiLevel == null);
assertTrue(apiLevel != null || representativeApiLevelForRuntime);
- return isDexRuntime() || representativeApiLevelForRuntime;
+ return (isDexRuntime() || representativeApiLevelForRuntime) && !isNoneRuntime();
}
public TestParameters assumeRuntimeTestParameters() {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 9b4e4fd..2c07a9a 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -4,6 +4,8 @@
package com.android.tools.r8;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
import com.android.tools.r8.references.ClassReference;
@@ -480,6 +482,10 @@
public abstract T addApplyMapping(String proguardMap);
+ public T addApplyMapping(Path proguardMap) throws IOException {
+ return addApplyMapping(FileUtils.readTextFile(proguardMap, UTF_8));
+ }
+
public final T addAlwaysClassInlineAnnotation() {
return addTestingAnnotation(AlwaysClassInline.class);
}
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDefaultMethodInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDefaultMethodInSubInterfaceTest.java
new file mode 100644
index 0000000..95ef480
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDefaultMethodInSubInterfaceTest.java
@@ -0,0 +1,320 @@
+// Copyright (c) 2024, 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.tracereferences;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.DiagnosticsHandler;
+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.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.BeforeClass;
+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;
+
+// From b/319190998.
+@RunWith(Parameterized.class)
+public class TraceReferencesDefaultMethodInSubInterfaceTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withAllRuntimesAndApiLevels()
+ .withAllApiLevelsAlsoForCf()
+ .withNoneRuntime()
+ .build();
+ }
+
+ static Path targetJar;
+ static Path sourceJar;
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ Path dir = getStaticTemp().newFolder().toPath();
+ targetJar =
+ ZipBuilder.builder(dir.resolve("target.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(I.class),
+ ToolHelper.getClassFileForTestClass(J.class))
+ .build();
+ sourceJar =
+ ZipBuilder.builder(dir.resolve("source.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(JImpl.class),
+ ToolHelper.getClassFileForTestClass(Main.class))
+ .build();
+ }
+
+ static class SeenReferencesConsumer implements TraceReferencesConsumer {
+
+ private final Set<MethodReference> seenMethods = new HashSet<>();
+
+ @Override
+ public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) {}
+
+ @Override
+ public void acceptField(TracedField tracedField, DiagnosticsHandler handler) {}
+
+ @Override
+ public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) {
+ seenMethods.add(tracedMethod.getReference());
+ }
+ }
+
+ @Test
+ public void testTracedReferences() throws Exception {
+ assumeTrue(parameters.isNoneRuntime());
+ SeenReferencesConsumer consumer = new SeenReferencesConsumer();
+ TraceReferences.run(
+ TraceReferencesCommand.builder()
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addSourceFiles(sourceJar)
+ .addTargetFiles(targetJar)
+ .setConsumer(consumer)
+ .build());
+
+ // TODO(b/319190998): Just tracing I.m is not enough.
+ ImmutableSet<MethodReference> expectedSet =
+ ImmutableSet.of(
+ Reference.method(
+ Reference.classFromClass(I.class),
+ "m",
+ Collections.emptyList(),
+ Reference.classFromClass(Object.class)));
+ assertEquals(expectedSet, consumer.seenMethods);
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addProgramFiles(sourceJar)
+ .addProgramFiles(targetJar)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ parameters.assumeDexRuntime();
+ Path targetDex =
+ testForD8().setMinApi(parameters).addProgramFiles(targetJar).compile().writeToZip();
+
+ testForD8()
+ .setMinApi(parameters)
+ .addClasspathFiles(targetJar)
+ .addProgramFiles(sourceJar)
+ .addRunClasspathFiles(targetDex)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testGeneratedKeepRulesFollowedByR8() throws Exception {
+ parameters.assumeR8TestParameters();
+
+ Path generatedKeepRules = temp.newFile("keep.rules").toPath();
+ TraceReferencesKeepRules keepRulesConsumer =
+ TraceReferencesKeepRules.builder()
+ // The use of keeper in b/319190998 disables obfuscation of generated keep rules.
+ .setAllowObfuscation(false)
+ .setOutputPath(generatedKeepRules)
+ .build();
+ TraceReferences.run(
+ TraceReferencesCommand.builder()
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addSourceFiles(sourceJar)
+ .addTargetFiles(targetJar)
+ .setConsumer(keepRulesConsumer)
+ .build());
+
+ Path r8CompiledTarget =
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramFiles(targetJar)
+ .addKeepRuleFiles(generatedKeepRules)
+ .compile()
+ .writeToZip();
+
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addClasspathFiles(targetJar)
+ .addProgramFiles(sourceJar)
+ .addRunClasspathFiles(r8CompiledTarget)
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/319190998): This should not fail.
+ .applyIf(
+ hasDefaultInterfaceMethodsSupport(parameters),
+ r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @Test
+ public void testGeneratedKeepRulesWithMissingRuleFollowedByR8() throws Exception {
+ parameters.assumeR8TestParameters();
+
+ Path generatedKeepRules = temp.newFile("keep.rules").toPath();
+ TraceReferencesKeepRules keepRulesConsumer =
+ TraceReferencesKeepRules.builder()
+ .setAllowObfuscation(true)
+ .setOutputPath(generatedKeepRules)
+ .build();
+ TraceReferences.run(
+ TraceReferencesCommand.builder()
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addSourceFiles(sourceJar)
+ .addTargetFiles(targetJar)
+ .setConsumer(keepRulesConsumer)
+ .build());
+
+ Path proguardMap = temp.newFolder().toPath().resolve("mapping.txt");
+ Path r8CompiledTarget =
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramFiles(targetJar)
+ .addKeepRuleFiles(generatedKeepRules)
+ .addKeepRules("-keep class " + J.class.getTypeName() + " { m(); }")
+ .compile()
+ .apply(r -> r.writeProguardMap(proguardMap))
+ .writeToZip();
+
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addClasspathFiles(targetJar)
+ .addProgramFiles(sourceJar)
+ .addApplyMapping(proguardMap)
+ .addKeepMainRule(Main.class)
+ .addRunClasspathFiles(r8CompiledTarget)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testGeneratedKeepRulesWithMissingRuleFollowedByD8() throws Exception {
+ parameters.assumeDexRuntime();
+
+ Path generatedKeepRules = temp.newFile("keep.rules").toPath();
+ TraceReferencesKeepRules keepRulesConsumer =
+ TraceReferencesKeepRules.builder()
+ // Don't obfuscate as D8 does not support apply mapping.
+ .setAllowObfuscation(false)
+ .setOutputPath(generatedKeepRules)
+ .build();
+ TraceReferences.run(
+ TraceReferencesCommand.builder()
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addSourceFiles(sourceJar)
+ .addTargetFiles(targetJar)
+ .setConsumer(keepRulesConsumer)
+ .build());
+
+ Path r8CompiledTarget =
+ testForR8(Backend.DEX)
+ .setMinApi(parameters)
+ .addProgramFiles(targetJar)
+ .addKeepRuleFiles(generatedKeepRules)
+ .addKeepRules("-keep class " + J.class.getTypeName() + " { m(); }")
+ .compile()
+ .writeToZip();
+
+ testForD8(Backend.DEX)
+ .setMinApi(parameters)
+ .addClasspathFiles(targetJar)
+ .addProgramFiles(sourceJar)
+ .addRunClasspathFiles(r8CompiledTarget)
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ hasDefaultInterfaceMethodsSupport(parameters),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+ // TODO(b/319190998): This should not fail.
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @Test
+ public void testGeneratedKeepRulesWithMissingRuleAndDontObfuscateFollowedByD8() throws Exception {
+ parameters.assumeDexRuntime();
+
+ Path generatedKeepRules = temp.newFile("keep.rules").toPath();
+ TraceReferencesKeepRules keepRulesConsumer =
+ TraceReferencesKeepRules.builder()
+ // Don't obfuscate as D8 does not support apply mapping.
+ .setAllowObfuscation(false)
+ .setOutputPath(generatedKeepRules)
+ .build();
+ TraceReferences.run(
+ TraceReferencesCommand.builder()
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addSourceFiles(sourceJar)
+ .addTargetFiles(targetJar)
+ .setConsumer(keepRulesConsumer)
+ .build());
+
+ Path r8CompiledTarget =
+ testForR8(Backend.DEX)
+ .setMinApi(parameters)
+ .addProgramFiles(targetJar)
+ .addKeepRuleFiles(generatedKeepRules)
+ .addKeepRules("-keep class " + J.class.getTypeName() + " { m(); }")
+ // TODO(b/319190998): Adding dont obfuscate should not be needed as trace references is
+ // already asked to not allow obfuscation. Hwing this will cause the CC class to not
+ // get renamed.
+ .addDontObfuscate()
+ .compile()
+ .writeToZip();
+
+ testForD8(Backend.DEX)
+ .setMinApi(parameters)
+ .addClasspathFiles(targetJar)
+ .addProgramFiles(sourceJar)
+ .addRunClasspathFiles(r8CompiledTarget)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ // Interfaces I and J are in the target set for trace references.
+ interface I {
+ Object m();
+ }
+
+ interface J extends I {
+ default Object m() {
+ return "Hello, world!";
+ }
+ }
+
+ // Interfaces JImpl and Main are in the source set for trace references.
+ public static class JImpl implements J {}
+
+ public static class Main {
+
+ public static void m(I i) {
+ System.out.println(i.m());
+ }
+
+ public static void main(String[] args) {
+ m(new JImpl());
+ }
+ }
+}