Desugared lib: Fix keep rules for empty classes
Bug: 162437880
Change-Id: Ic5588eb973da365553cf7247d62f0d55a7e7da08
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index 9eb7caa..f4e85a8 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.NamingLens;
@@ -36,6 +37,8 @@
abstract void recordClassAllAccesses(DexType type);
+ abstract void recordHierarchyOf(DexProgramClass clazz);
+
abstract boolean isNop();
abstract void generateKeepRules(InternalOptions options);
@@ -116,6 +119,14 @@
}
}
+ @Override
+ void recordHierarchyOf(DexProgramClass clazz) {
+ recordClassAllAccesses(clazz.superType);
+ for (DexType itf : clazz.interfaces.values) {
+ recordClassAllAccesses(itf);
+ }
+ }
+
private void keepClass(DexType type) {
DexType baseType = type.lookupBaseType(options.itemFactory);
toKeep.putIfAbsent(baseType, new KeepStruct());
@@ -193,6 +204,9 @@
void recordClassAllAccesses(DexType type) {}
@Override
+ void recordHierarchyOf(DexProgramClass clazz) {}
+
+ @Override
boolean isNop() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 0047bf8..8d1eac7 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -467,6 +467,8 @@
}
private void writeClassDefItem(DexProgramClass clazz) {
+ desugaredLibraryCodeToKeep.recordHierarchyOf(clazz);
+
dest.putInt(mapping.getOffsetFor(clazz.type));
dest.putInt(clazz.accessFlags.getAsDexAccessFlags());
dest.putInt(
@@ -659,10 +661,6 @@
private void writeClassData(DexProgramClass clazz) {
assert clazz.hasMethodsOrFields();
- desugaredLibraryCodeToKeep.recordClassAllAccesses(clazz.superType);
- for (DexType itf : clazz.interfaces.values) {
- desugaredLibraryCodeToKeep.recordClassAllAccesses(itf);
- }
mixedSectionOffsets.setOffsetFor(clazz, dest.position());
dest.putUleb128(clazz.staticFields().size());
dest.putUleb128(clazz.instanceFields().size());
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
new file mode 100644
index 0000000..0eab37e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, 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.desugaredlibrary;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LibraryEmptySubclassInterfaceTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public LibraryEmptySubclassInterfaceTest(
+ boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addInnerClasses(LibraryEmptySubclassInterfaceTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutputLines(getResult());
+ assertExpectedKeepRules(keepRuleConsumer);
+ }
+
+ private void assertExpectedKeepRules(KeepRuleConsumer keepRuleConsumer) {
+ if (!requiresEmulatedInterfaceCoreLibDesugaring(parameters)) {
+ return;
+ }
+ String keepRules = keepRuleConsumer.get();
+ assertThat(keepRules, containsString("-keep class j$.util.Map"));
+ assertThat(keepRules, containsString("-keep class j$.util.concurrent.ConcurrentHashMap"));
+ assertThat(keepRules, containsString("-keep class j$.util.concurrent.ConcurrentMap"));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(Backend.DEX)
+ .addInnerClasses(LibraryEmptySubclassInterfaceTest.class)
+ .addKeepMainRule(Executor.class)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutputLines(getResult());
+ assertExpectedKeepRules(keepRuleConsumer);
+ }
+
+ private String getResult() {
+ return requiresEmulatedInterfaceCoreLibDesugaring(parameters)
+ ? "class j$.util.concurrent.ConcurrentHashMap"
+ : "class java.util.concurrent.ConcurrentHashMap";
+ }
+
+ @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+ static class Executor {
+
+ public static void main(String[] args) {
+ System.out.println(NullableConcurrentHashMap.class.getSuperclass());
+ }
+ }
+
+ static class NullableConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {
+ NullableConcurrentHashMap() {
+ super();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
index 12073e3..6e300ba 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
@@ -25,7 +25,6 @@
import java.util.concurrent.ConcurrentMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -70,7 +69,6 @@
@Test
public void testCustomCollectionR8() throws Exception {
- Assume.assumeFalse("TODO(b/162437880)", shrinkDesugaredLibrary);
KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
String stdOut =
testForR8(Backend.DEX)