Keep default ctor of a class that implements Externalizable.
Bug: 116735204
Change-Id: I25750f25c5462e9e4c54611fb4f866e8bdbd7c08
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 3df31fd..22790b4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -412,6 +412,10 @@
return null;
}
+ public boolean isExternalizable(AppInfo appInfo) {
+ return type.implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.externalizableType);
+ }
+
public boolean defaultValuesForStaticFieldsMayTriggerAllocation() {
return Arrays.stream(staticFields())
.anyMatch(field -> !field.getStaticValue().mayTriggerAllocation());
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 7254577..90a098a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -256,6 +256,7 @@
public final DexType callSiteType = createType("Ljava/lang/invoke/CallSite;");
public final DexType lookupType = createType("Ljava/lang/invoke/MethodHandles$Lookup;");
public final DexType serializableType = createType("Ljava/io/Serializable;");
+ public final DexType externalizableType = createType("Ljava/io/Externalizable;");
public final DexType comparableType = createType("Ljava/lang/Comparable;");
public final DexMethod metafactoryMethod =
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index fe63f46..138e07e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -37,6 +37,9 @@
*/
private Set<DexType> directSubtypes = NO_DIRECT_SUBTYPE;
+ // Caching what interfaces this type is implementing. This includes super-interface hierarchy.
+ private Set<DexType> implementedInterfaces;
+
DexType(DexString descriptor) {
assert !descriptor.toString().contains(".");
this.descriptor = descriptor;
@@ -234,9 +237,12 @@
* @return a set of interfaces of {@link DexType}.
*/
public Set<DexType> implementedInterfaces(AppInfo appInfo) {
- Set<DexType> interfaces = Sets.newIdentityHashSet();
- implementedInterfaces(appInfo, interfaces);
- return interfaces;
+ if (implementedInterfaces == null) {
+ Set<DexType> interfaces = Sets.newIdentityHashSet();
+ implementedInterfaces(appInfo, interfaces);
+ implementedInterfaces = interfaces;
+ }
+ return implementedInterfaces;
}
private void implementedInterfaces(AppInfo appInfo, Set<DexType> interfaces) {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index bfb2b95..de42c18 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -265,13 +265,18 @@
if (item.isDexClass()) {
DexClass clazz = item.asDexClass();
workList.add(Action.markInstantiated(clazz, reason));
- if (forceProguardCompatibility && clazz.hasDefaultInitializer()) {
- ProguardKeepRule compatRule =
+ if (clazz.hasDefaultInitializer()) {
+ if (forceProguardCompatibility) {
+ ProguardKeepRule compatRule =
ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
- proguardCompatibilityWorkList.add(
- Action.markMethodLive(
- clazz.getDefaultInitializer(),
- KeepReason.dueToProguardCompatibilityKeepRule(compatRule)));
+ proguardCompatibilityWorkList.add(
+ Action.markMethodLive(
+ clazz.getDefaultInitializer(),
+ KeepReason.dueToProguardCompatibilityKeepRule(compatRule)));
+ }
+ if (clazz.isExternalizable(appInfo)) {
+ workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
+ }
}
} else if (item.isDexEncodedField()) {
workList.add(Action.markFieldKept(item.asDexEncodedField(), reason));
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 0898891..8d956a8 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -456,7 +456,7 @@
DexType collection = factory.createType("Ljava/util/Collection;");
DexType set = factory.createType("Ljava/util/Set;");
DexType list = factory.createType("Ljava/util/List;");
- DexType serializable = factory.createType("Ljava/io/Serializable;");
+ DexType serializable = factory.serializableType;
Set<DexType> lub = computeLeastUpperBoundOfInterfaces(appInfo,
ImmutableSet.of(set), ImmutableSet.of(list));
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
index 952bae6..cf14d8b 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -88,13 +88,13 @@
case PROGUARD6_THEN_D8:
return runProguard6AndD8(programClasses, proguardConfig, proguardMap);
case R8_COMPAT:
- return runR8Compat(programClasses, proguardConfig, Backend.DEX);
+ return runR8Compat(programClasses, proguardConfig, proguardMap, Backend.DEX);
case R8_COMPAT_CF:
- return runR8Compat(programClasses, proguardConfig, Backend.CF);
+ return runR8Compat(programClasses, proguardConfig, proguardMap, Backend.CF);
case R8:
- return runR8(programClasses, proguardConfig, Backend.DEX);
+ return runR8(programClasses, proguardConfig, proguardMap, Backend.DEX);
case R8_CF:
- return runR8(programClasses, proguardConfig, Backend.CF);
+ return runR8(programClasses, proguardConfig, proguardMap, Backend.CF);
}
throw new IllegalArgumentException("Unknown shrinker: " + mode);
}
@@ -126,34 +126,45 @@
throw new IllegalArgumentException("Unknown shrinker: " + mode);
}
- protected AndroidApp runR8(List<Class> programClasses, String proguardConfig, Backend backend)
+ protected AndroidApp runR8(
+ List<Class> programClasses,
+ String proguardConfig,
+ Path proguardMap,
+ Backend backend)
throws Exception {
- return runR8(programClasses, proguardConfig, null, backend);
+ return runR8(programClasses, proguardConfig, proguardMap, null, backend);
}
protected AndroidApp runR8(
List<Class> programClasses,
String proguardConfig,
+ Path proguardMap,
Consumer<InternalOptions> configure,
Backend backend)
throws Exception {
AndroidApp app = readClassesAndRuntimeJar(programClasses, backend);
R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend));
ToolHelper.allowTestProguardOptions(builder);
- builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+ builder.addProguardConfiguration(
+ ImmutableList.of(proguardConfig, toPrintMappingRule(proguardMap)), Origin.unknown());
return ToolHelper.runR8(builder.build(), configure);
}
protected CodeInspector inspectR8Result(
List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
- return new CodeInspector(runR8(programClasses, proguardConfig, backend));
+ return new CodeInspector(runR8(programClasses, proguardConfig, null, backend));
}
protected AndroidApp runR8Compat(
- List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
+ List<Class> programClasses,
+ String proguardConfig,
+ Path proguardMap,
+ Backend backend)
+ throws Exception {
CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
ToolHelper.allowTestProguardOptions(builder);
- builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+ builder.addProguardConfiguration(
+ ImmutableList.of(proguardConfig, toPrintMappingRule(proguardMap)), Origin.unknown());
programClasses.forEach(
clazz -> builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz)));
if (backend == Backend.DEX) {
@@ -169,7 +180,7 @@
protected CodeInspector inspectR8CompatResult(
List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
- return new CodeInspector(runR8Compat(programClasses, proguardConfig, backend));
+ return new CodeInspector(runR8Compat(programClasses, proguardConfig, null, backend));
}
protected AndroidApp runProguard5(
@@ -306,4 +317,8 @@
assertThat(c, not(isPresent()));
}
}
+
+ private String toPrintMappingRule(Path proguardMap) {
+ return proguardMap == null ? "" : "-printmapping " + proguardMap.toAbsolutePath();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
index dcb1970c..e20b80a 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
@@ -264,7 +264,9 @@
@Parameterized.Parameters(name = "Shrinker: {0}")
public static Collection<Object> data() {
return ImmutableList.of(
- Shrinker.PROGUARD6_THEN_D8, Shrinker.PROGUARD6, Shrinker.R8, Shrinker.R8_CF);
+ Shrinker.PROGUARD6_THEN_D8, Shrinker.PROGUARD6,
+ Shrinker.R8_COMPAT, Shrinker.R8_COMPAT_CF,
+ Shrinker.R8, Shrinker.R8_CF);
}
public ExternalizableTest(Shrinker shrinker) {
@@ -273,11 +275,6 @@
@Test
public void testExternalizable() throws Exception {
- // TODO(b/116735204): R8 should keep default ctor() of classes that implement Externalizable
- if (shrinker.isR8()) {
- return;
- }
-
String javaOutput = runOnJava(ExternalizableTestMain.class);
List<String> config = ImmutableList.of(
@@ -300,6 +297,12 @@
assertEquals(javaOutput.trim(), output.trim());
}
+ // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
+ // 1.11 The Externalizable Interface
+ // ...
+ // The class of an Externalizable object must do the following:
+ // ...
+ // * Have a public no-arg constructor
CodeInspector codeInspector = new CodeInspector(processedApp, proguardMap);
ClassSubject classSubject = codeInspector.clazz(ExternalizableDataClass.class);
assertThat(classSubject, isPresent());
@@ -336,6 +339,12 @@
assertEquals(javaOutput.trim(), output.trim());
}
+ // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
+ // 1.10 The Serializable Interface
+ // ...
+ // A Serializable class must do the following:
+ // ...
+ // * Have access to the no-arg constructor of its first non-serializable superclass
CodeInspector codeInspector = new CodeInspector(processedApp, proguardMap);
ClassSubject classSubject = codeInspector.clazz(NonSerializableSuperClass.class);
assertThat(classSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index 5ea7769..89d5079 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import org.junit.Test;
@@ -45,10 +46,12 @@
}
@Override
- protected AndroidApp runR8(List<Class> programClasses, String proguardConfig, Backend backend)
+ protected AndroidApp runR8(
+ List<Class> programClasses, String proguardConfig, Path proguardMap, Backend backend)
throws Exception {
// Disable inlining, otherwise classes can be pruned away if all their methods are inlined.
- return runR8(programClasses, proguardConfig, o -> o.enableInlining = false, backend);
+ return runR8(
+ programClasses, proguardConfig, proguardMap, o -> o.enableInlining = false, backend);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
index b003b7b..165a68f 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -89,9 +89,10 @@
}
@Override
- protected AndroidApp runR8(List<Class> programClasses, String proguardConfig, Backend backend)
+ protected AndroidApp runR8(
+ List<Class> programClasses, String proguardConfig, Path proguardMap, Backend backend)
throws Exception {
- return super.runR8(programClasses, proguardConfig, this::configure, backend);
+ return super.runR8(programClasses, proguardConfig, proguardMap, this::configure, backend);
}
private void check(AndroidApp app) throws Exception {