Merge "Keep fewer default constructors in Proguard compat mode"
diff --git a/src/main/java/com/android/tools/r8/code/CheckCast.java b/src/main/java/com/android/tools/r8/code/CheckCast.java
index d3576c1..9416a7b 100644
--- a/src/main/java/com/android/tools/r8/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/code/CheckCast.java
@@ -39,7 +39,7 @@
@Override
public void registerUse(UseRegistry registry) {
- registry.registerTypeReference(getType());
+ registry.registerCheckCast(getType());
}
public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/code/ConstClass.java b/src/main/java/com/android/tools/r8/code/ConstClass.java
index f140022..ef8b257 100644
--- a/src/main/java/com/android/tools/r8/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/code/ConstClass.java
@@ -39,7 +39,7 @@
@Override
public void registerUse(UseRegistry registry) {
- registry.registerTypeReference(getType());
+ registry.registerConstClass(getType());
}
public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 78b2f56..48ef91d 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -27,6 +27,14 @@
public abstract boolean registerTypeReference(DexType type);
+ public boolean registerConstClass(DexType type) {
+ return registerTypeReference(type);
+ }
+
+ public boolean registerCheckCast(DexType type) {
+ return registerTypeReference(type);
+ }
+
public void registerMethodHandle(DexMethodHandle methodHandle) {
switch (methodHandle.type) {
case INSTANCE_GET:
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
index f9a7f49..37c9aea 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -35,6 +35,8 @@
DexType type = application.getTypeFromName(name);
if (opcode == org.objectweb.asm.Opcodes.NEW) {
registry.registerNewInstance(type);
+ } else if (opcode == Opcodes.CHECKCAST) {
+ registry.registerCheckCast(type);
} else {
registry.registerTypeReference(type);
}
@@ -51,7 +53,7 @@
// Nothing to register for method type, it represents only a prototype not associated with a
// method name.
if (((Type) cst).getSort() != Type.METHOD) {
- registry.registerTypeReference(application.getType((Type) cst));
+ registry.registerConstClass(application.getType((Type) cst));
}
} else if (cst instanceof Handle) {
registerMethodHandleType((Handle) cst);
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 f36f8a6..9aa73de 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -203,11 +203,30 @@
}
private void enqueueRootItems(Map<DexItem, ProguardKeepRule> items) {
- workList.addAll(
- items.entrySet().stream().map(Action::forRootItem).collect(Collectors.toList()));
+ items.entrySet().forEach(this::enqueueRootItem);
pinnedItems.addAll(items.keySet());
}
+ private void enqueueRootItem(Map.Entry<DexItem, ProguardKeepRule> root) {
+ DexItem item = root.getKey();
+ KeepReason reason = KeepReason.dueToKeepRule(root.getValue());
+ if (item instanceof DexClass) {
+ DexClass clazz = (DexClass) item;
+ workList.add(Action.markInstantiated(clazz, reason));
+ if (options.forceProguardCompatibility && clazz.hasDefaultInitializer()) {
+ ProguardKeepRule rule = ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
+ proguardCompatibilityWorkList.add(Action.markMethodLive(
+ clazz.getDefaultInitializer(), KeepReason.dueToProguardCompatibilityKeepRule(rule)));
+ }
+ } else if (item instanceof DexEncodedField) {
+ workList.add(Action.markFieldKept((DexEncodedField) item, reason));
+ } else if (item instanceof DexEncodedMethod) {
+ workList.add(Action.markMethodKept((DexEncodedMethod) item, reason));
+ } else {
+ throw new IllegalArgumentException(item.toString());
+ }
+ }
+
//
// Things to do with registering events. This is essentially the interface for byte-code
// traversals.
@@ -372,6 +391,16 @@
}
@Override
+ public boolean registerConstClass(DexType type) {
+ return registerConstClassOrCheckCast(type);
+ }
+
+ @Override
+ public boolean registerCheckCast(DexType type) {
+ return registerConstClassOrCheckCast(type);
+ }
+
+ @Override
public boolean registerTypeReference(DexType type) {
DexType baseType = type.toBaseType(appInfo.dexItemFactory);
if (baseType.isClassType()) {
@@ -380,6 +409,25 @@
}
return false;
}
+
+ private boolean registerConstClassOrCheckCast(DexType type) {
+ if (options.forceProguardCompatibility) {
+ DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isClassType()) {
+ DexClass baseClass = appInfo.definitionFor(baseType);
+ if (baseClass != null) {
+ markClassAsInstantiatedWithCompatRule(baseClass);
+ } else {
+ // This handles reporting of missing classes.
+ markTypeAsLive(baseType);
+ }
+ return true;
+ }
+ return false;
+ } else {
+ return registerTypeReference(type);
+ }
+ }
}
private DexMethod getInvokeSuperTarget(DexMethod method, DexEncodedMethod currentMethod) {
@@ -439,14 +487,12 @@
annotations.forEach(this::handleAnnotationOfLiveType);
}
- // Add all dependent static members to the workqueue.
- enqueueRootItems(rootSet.getDependentStaticMembers(type));
-
- // For Proguard compatibility keep the default initializer for live types.
if (options.forceProguardCompatibility) {
- if (holder.isProgramClass() && holder.hasDefaultInitializer()) {
- markClassAsInstantiatedWithCompatRule(holder);
- }
+ // Add all dependent members to the workqueue.
+ enqueueRootItems(rootSet.getDependentItems(type));
+ } else {
+ // Add all dependent static members to the workqueue.
+ enqueueRootItems(rootSet.getDependentStaticMembers(type));
}
}
}
@@ -566,6 +612,7 @@
if (!instantiatedTypes.add(clazz.type, reason)) {
return;
}
+
collectProguardCompatibilityRule(reason);
if (Log.ENABLED) {
Log.verbose(getClass(), "Class `%s` is instantiated, processing...", clazz);
@@ -1286,20 +1333,6 @@
return new Action(Kind.MARK_FIELD_KEPT, method, null, reason);
}
- public static Action forRootItem(Map.Entry<DexItem, ProguardKeepRule> root) {
- DexItem item = root.getKey();
- KeepReason reason = KeepReason.dueToKeepRule(root.getValue());
- if (item instanceof DexClass) {
- return markInstantiated((DexClass) item, reason);
- } else if (item instanceof DexEncodedField) {
- return markFieldKept((DexEncodedField) item, reason);
- } else if (item instanceof DexEncodedMethod) {
- return markMethodKept((DexEncodedMethod) item, reason);
- } else {
- throw new IllegalArgumentException(item.toString());
- }
- }
-
private enum Kind {
MARK_REACHABLE_VIRTUAL,
MARK_REACHABLE_INTERFACE,
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
index 39e804a..4cb7750 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -40,6 +40,13 @@
}
}
+class MainCheckCastSubClass {
+
+ public static void main(String[] args) {
+ System.out.println((SubClass) null);
+ }
+}
+
class MainClassForNameSubClass {
public static void main(String[] args) throws Exception{
@@ -164,6 +171,31 @@
}
@Test
+ public void testCheckCast() throws Exception {
+ // Reference to the class constant keeps the default constructor.
+ Class mainClass = MainCheckCastSubClass.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
+ keepMainProguardConfiguration(mainClass),
+ // TODO(74423424): Proguard eliminates the check-cast on null.
+ this::checkAllClassesPresentWithDefaultConstructor,
+ this::checkOnlyMainPresent);
+ }
+
+
+ @Test
+ public void testCheckCastWithoutInlining() throws Exception {
+ // Reference to the class constant keeps the default constructor.
+ Class mainClass = MainCheckCastSubClass.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
+ keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+ this::checkAllClassesPresentWithDefaultConstructor);
+ }
+
+ @Test
public void testClassForName() throws Exception {
// Class.forName with a constant string keeps the default constructor.
Class mainClass = MainClassForNameSubClass.class;
@@ -230,7 +262,7 @@
ImmutableList.of(mainClass, StaticFieldNotInitialized.class),
keepMainProguardConfiguration(mainClass),
// TODO(74379749): Proguard does not keep the class with the un-initialized static field.
- this::checkAllClassesPresentWithDefaultConstructor,
+ this::checkAllClassesPresentOnlyMainWithDefaultConstructor,
this::checkOnlyMainPresent);
}
@@ -241,9 +273,6 @@
mainClass,
ImmutableList.of(mainClass, StaticFieldInitialized.class),
keepMainProguardConfiguration(mainClass),
- // TODO(74233021): Proguard does not keep the default constructor for the class with the
- // un-initialized static field.
- this::checkAllClassesPresentWithDefaultConstructor,
this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
}
@@ -265,9 +294,6 @@
mainClass,
ImmutableList.of(mainClass, StaticFieldNotInitialized.class),
keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
- // TODO(74233021): Proguard does not keep the default constructor for the class with the
- // initialized static field.
- this::checkAllClassesPresentWithDefaultConstructor,
this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
}
@@ -278,9 +304,6 @@
mainClass,
ImmutableList.of(mainClass, StaticFieldInitialized.class),
keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
- // TODO(74233021): Proguard does not keep the default constructor for the class with the
- // un-initialized static field.
- this::checkAllClassesPresentWithDefaultConstructor,
this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
}
@@ -291,9 +314,6 @@
mainClass,
ImmutableList.of(mainClass, StaticMethod.class),
keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
- // TODO(74233021): Proguard does not keep the default constructor for the class with the
- // initialized static field.
- this::checkAllClassesPresentWithDefaultConstructor,
this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
index 7904400..7f8e5fb 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
@@ -4,26 +4,61 @@
package com.android.tools.r8.shaking.keepclassmembers;
-import static org.junit.Assert.assertEquals;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isAbstract;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.TestBase;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
-public class KeepClassMembersTest extends TestBase {
+public class KeepClassMembersTest extends ProguardCompatabilityTestBase {
- public void runTest(Class mainClass, Class<?> staticClass,
- boolean forceProguardCompatibility) throws Exception {
- boolean staticClassHasDefaultConstructor = true;
- try {
- staticClass.getDeclaredConstructor();
- } catch (NoSuchMethodException e) {
- staticClassHasDefaultConstructor = false;
+ private void check(DexInspector inspector, Class mainClass, Class<?> staticClass,
+ boolean forceProguardCompatibility, boolean fromProguard) {
+ assertTrue(inspector.clazz(mainClass).isPresent());
+ ClassSubject staticClassSubject = inspector.clazz(staticClass);
+ assertThat(staticClassSubject, isPresent());
+ assertThat(staticClassSubject.method("int", "getA", ImmutableList.of()), isPresent());
+ assertThat(staticClassSubject.method("int", "getB", ImmutableList.of()), not(isPresent()));
+ assertThat(staticClassSubject.field("int", "a"), isPresent());
+ assertThat(staticClassSubject.field("int", "b"), isPresent());
+ assertThat(staticClassSubject.field("int", "c"), not(isPresent()));
+ // Neither Proguard not R8 keeps any constructors.
+ staticClassSubject.forAllMethods(method -> assertFalse(method.isInstanceInitializer()));
+ assertThat(staticClassSubject.init(ImmutableList.of()), not(isPresent()));
+ MethodSubject getIMethod = staticClassSubject.method("int", "getI", ImmutableList.of());
+ FieldSubject iField = staticClassSubject.field("int", "i");
+ if (forceProguardCompatibility) {
+ if (fromProguard) {
+ // Proguard keeps the instance method and it code even though the class does not have a
+ // constructor, and therefore cannot be instantiated.
+ assertThat(getIMethod, isPresent());
+ assertThat(iField, isPresent());
+ } else {
+ // Force Proguard compatibility keeps the instance method, but makes it abstract as
+ // the class does not have a constructor, and therefore cannot be instantiated.
+ assertThat(getIMethod, isAbstract());
+ // As the method is abstract the referenced field is not present.
+ assertThat(iField, not(isPresent()));
+ }
+ } else {
+ assertThat(getIMethod, not(isPresent()));
+ assertThat(iField, not(isPresent()));
}
+ assertThat(staticClassSubject.method("int", "getJ", ImmutableList.of()), not(isPresent()));
+ assertThat(staticClassSubject.field("int", "j"), not(isPresent()));
+ }
+
+ private void runTest(Class mainClass, Class<?> staticClass,
+ boolean forceProguardCompatibility) throws Exception {
String proguardConfig = String.join("\n", ImmutableList.of(
"-keepclassmembers class **.PureStatic* {",
" public static int b;",
@@ -35,27 +70,16 @@
"}",
"-dontoptimize", "-dontobfuscate"
));
- DexInspector inspector = new DexInspector(
- compileWithR8(ImmutableList.of(mainClass, staticClass), proguardConfig,
- options -> options.forceProguardCompatibility = forceProguardCompatibility));
- assertTrue(inspector.clazz(mainClass).isPresent());
- ClassSubject staticClassSubject = inspector.clazz(staticClass);
- assertTrue(staticClassSubject.isPresent());
- assertTrue(staticClassSubject.method("int", "getA", ImmutableList.of()).isPresent());
- assertFalse(staticClassSubject.method("int", "getB", ImmutableList.of()).isPresent());
- assertTrue(staticClassSubject.field("int", "a").isPresent());
- assertTrue(staticClassSubject.field("int", "b").isPresent());
- assertFalse(staticClassSubject.field("int", "c").isPresent());
- // Force Proguard compatibility keeps the default constructor if present and then assumes
- // instantiated, hence keeps the instance method as well.
- assertEquals(forceProguardCompatibility && staticClassHasDefaultConstructor,
- staticClassSubject.init(ImmutableList.of()).isPresent());
- assertEquals(forceProguardCompatibility && staticClassHasDefaultConstructor,
- staticClassSubject.method("int", "getI", ImmutableList.of()).isPresent());
- assertEquals(forceProguardCompatibility && staticClassHasDefaultConstructor,
- staticClassSubject.field("int", "i").isPresent());
- assertFalse(staticClassSubject.method("int", "getJ", ImmutableList.of()).isPresent());
- assertFalse(staticClassSubject.field("int", "j").isPresent());
+ DexInspector inspector;
+ inspector = new DexInspector(
+ compileWithR8(ImmutableList.of(mainClass, staticClass), proguardConfig,
+ options -> options.forceProguardCompatibility = forceProguardCompatibility));
+ check(inspector, mainClass, staticClass, forceProguardCompatibility, false);
+
+ if (isRunProguard()) {
+ inspector = runProguard(ImmutableList.of(mainClass, staticClass), proguardConfig);
+ check(inspector, mainClass, staticClass, true, true);
+ }
}
@Test
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java b/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
index 8e1db2c..fc9bc29 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
@@ -5,6 +5,9 @@
package com.android.tools.r8.utils;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.DexInspector.Subject;
import com.google.common.collect.ImmutableList;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
@@ -12,22 +15,46 @@
public class DexInspectorMatchers {
- public static Matcher<ClassSubject> isPresent() {
- return new TypeSafeMatcher<ClassSubject>() {
+ public static Matcher<Subject> isPresent() {
+ return new TypeSafeMatcher<Subject>() {
+ private String type(Subject subject) {
+ String type = "<unknown subject type>";
+ if (subject instanceof ClassSubject) {
+ type = "class";
+ } else if (subject instanceof MethodSubject) {
+ type = "method";
+ } else if (subject instanceof FieldSubject) {
+ type = "field";
+ }
+ return type;
+ }
+
+ private String name(Subject subject) {
+ String name = "<unknown>";
+ if (subject instanceof ClassSubject) {
+ name = ((ClassSubject) subject).getOriginalName();
+ } else if (subject instanceof MethodSubject) {
+ name = ((MethodSubject) subject).getOriginalName();
+ } else if (subject instanceof FieldSubject) {
+ name = ((FieldSubject) subject).getOriginalName();
+ }
+ return name;
+ }
+
@Override
- public boolean matchesSafely(final ClassSubject clazz) {
+ public boolean matchesSafely(final Subject clazz) {
return clazz.isPresent();
}
@Override
public void describeTo(final Description description) {
- description.appendText("class present");
+ description.appendText(" present");
}
@Override
- public void describeMismatchSafely(final ClassSubject clazz, Description description) {
+ public void describeMismatchSafely(final Subject subject, Description description) {
description
- .appendText("class ").appendValue(clazz.getOriginalName()).appendText(" was not");
+ .appendText(type(subject) + " ").appendValue(name(subject)).appendText(" was not");
}
};
}
@@ -51,4 +78,24 @@
}
};
}
+
+ public static Matcher<MethodSubject> isAbstract() {
+ return new TypeSafeMatcher<MethodSubject>() {
+ @Override
+ public boolean matchesSafely(final MethodSubject method) {
+ return method.isPresent() && method.isAbstract();
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("method abstract");
+ }
+
+ @Override
+ public void describeMismatchSafely(final MethodSubject method, Description description) {
+ description
+ .appendText("method ").appendValue(method.getOriginalName()).appendText(" was not");
+ }
+ };
+ }
}