Implement constructor pruning for XML-referenced framework subclasses
Bug: b/325884671
Change-Id: I96e21cbd265b65eaa222997e10c9ac034255dd5d
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 1c9b922..bd4c85d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -719,6 +719,12 @@
public final DexType androidUtilPropertyType =
createStaticallyKnownType("Landroid/util/Property;");
public final DexType androidViewViewType = createStaticallyKnownType("Landroid/view/View;");
+ public final DexType androidUtilAttributeSetType =
+ createStaticallyKnownType("Landroid/util/AttributeSet;");
+ public final DexType androidPreferencePreferenceType =
+ createStaticallyKnownType("Landroid/preference/Preference;");
+ public final DexType androidTransitionTransitionType =
+ createStaticallyKnownType("Landroid/transition/Transition;");
public final DexType androidUtilSparseArrayType =
createStaticallyKnownType(androidUtilSparseArrayDescriptorString);
public final DexType androidContentResTypedArrayType =
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 7da4163..61ed436 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -51,6 +51,7 @@
import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexItemFactory.ClassMethods;
import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMember;
@@ -61,6 +62,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
@@ -724,8 +726,22 @@
ReflectiveUseFromXml reason = KeepReason.reflectiveUseFromXml(origin);
ensureClassKeptForResourceLookup(clazz, reason, markAsLive);
+ DexItemFactory factory = appView.dexItemFactory();
+ boolean isFrameworkClass =
+ appInfo
+ .traverseSuperClasses(
+ clazz,
+ (supertype, superclass, subclass) ->
+ TraversalContinuation.breakIf(
+ supertype.isIdenticalTo(factory.androidViewViewType)
+ || supertype.isIdenticalTo(factory.androidPreferencePreferenceType)
+ || supertype.isIdenticalTo(factory.androidTransitionTransitionType)))
+ .isBreak();
+
for (ProgramMethod programInstanceInitializer : clazz.programInstanceInitializers()) {
- // TODO(b/325884671): Only keep the actually framework targeted constructors.
+ if (isFrameworkClass && !isFrameworkTargetedConstructor(programInstanceInitializer)) {
+ continue;
+ }
applyMinimumKeepInfoWhenLiveOrTargeted(
programInstanceInitializer, KeepMethodInfo.newEmptyJoiner().disallowOptimization());
if (markAsLive) {
@@ -741,6 +757,25 @@
return true;
}
+ private boolean isFrameworkTargetedConstructor(ProgramMethod programInstanceInitializer) {
+ DexTypeList parameters = programInstanceInitializer.getParameters();
+ int size = parameters.size();
+ if (size == 0) {
+ return true;
+ }
+ DexItemFactory factory = appView.dexItemFactory();
+ DexType contextType = factory.androidContentContextType;
+ if (size == 1) {
+ return parameters.values[0].isIdenticalTo(contextType);
+ }
+ if (size == 2) {
+ DexType attributeSetType = factory.androidUtilAttributeSetType;
+ return parameters.values[0].isIdenticalTo(contextType)
+ && parameters.values[1].isIdenticalTo(attributeSetType);
+ }
+ return false;
+ }
+
private void ensureClassKeptForResourceLookup(
DexProgramClass clazz, ReflectiveUseFromXml reason, boolean markAsLive) {
applyMinimumKeepInfoWhenLive(
diff --git a/src/test/java/com/android/tools/r8/androidresources/XmlFilesWithClassReferences.java b/src/test/java/com/android/tools/r8/androidresources/XmlFilesWithClassReferences.java
index cdaa7cd..ecfed4f 100644
--- a/src/test/java/com/android/tools/r8/androidresources/XmlFilesWithClassReferences.java
+++ b/src/test/java/com/android/tools/r8/androidresources/XmlFilesWithClassReferences.java
@@ -105,9 +105,9 @@
codeInspector -> {
ClassSubject barClass = codeInspector.clazz(Bar.class);
assertThat(barClass, isPresentAndNotRenamed());
- // We should have two and only two methods, the two constructors.
- assertEquals(barClass.allMethods(MethodSubject::isInstanceInitializer).size(), 2);
- if (!parameters.isRandomPartialCompilation()) {
+ if (parameters.getPartialCompilationTestParameters().isNone()) {
+ // We should have two and only two methods, the two constructors.
+ assertEquals(barClass.allMethods(MethodSubject::isInstanceInitializer).size(), 2);
assertEquals(barClass.allMethods().size(), 2);
assertThat(codeInspector.clazz(BarFoo.class), assertFoo ? isPresent() : isAbsent());
}
@@ -145,6 +145,68 @@
public static class BarFoo {}
+ public static class MockContext {}
+
+ public static class MockAttributeSet {}
+
+ public static class MockView {
+ public MockView(MockContext context, MockAttributeSet attrs) {}
+ }
+
+ // Custom View class referenced from XML file
+ public static class BarView extends MockView {
+ public BarView(MockContext context, MockAttributeSet attrs) {
+ super(context, attrs);
+ System.out.println("init view");
+ }
+
+ public BarView(MockContext context, MockAttributeSet attrs, String x) {
+ super(context, attrs);
+ System.out.println("init view with string");
+ }
+ }
+
+ private byte[] getBarViewClassFileData() throws Exception {
+ return transformer(BarView.class)
+ .setSuper("Landroid/view/View;")
+ .replaceClassDescriptorInMembers(descriptor(MockContext.class), "Landroid/content/Context;")
+ .replaceClassDescriptorInMembers(
+ descriptor(MockAttributeSet.class), "Landroid/util/AttributeSet;")
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MockView.class), "Landroid/view/View;")
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MockContext.class), "Landroid/content/Context;")
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MockAttributeSet.class), "Landroid/util/AttributeSet;")
+ .transform();
+ }
+
+ @Test
+ public void testCustomViewConstructorShrinking() throws Exception {
+ String formattedXmlFile =
+ String.format(VIEW_WITH_CLASS_ATTRIBUTE_REFERENCE, BarView.class.getTypeName());
+ testForR8(parameters)
+ .addProgramClasses(TestClass.class, BarFoo.class)
+ .addProgramClassFileData(getBarViewClassFileData())
+ .addAndroidResources(getTestResources(temp, formattedXmlFile))
+ .addKeepMainRule(TestClass.class)
+ .enableOptimizedShrinking()
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .inspect(
+ codeInspector -> {
+ ClassSubject barViewClass = codeInspector.clazz(BarView.class);
+ assertThat(barViewClass, isPresentAndNotRenamed());
+ if (parameters.getPartialCompilationTestParameters().isNone()) {
+ // The custom constructor (Context, AttributeSet, String) must be stripped, leaving
+ // only the targeted constructor.
+ assertEquals(
+ barViewClass.allMethods(MethodSubject::isInstanceInitializer).size(), 1);
+ }
+ })
+ .assertSuccess();
+ }
+
public static class R {
public static class xml {
public static int xml_with_bar_reference;