Record all reachable field edges.
Bug: 123262024, 124428834
Change-Id: Ib6ddc9f4dfece7f44119104e79957528c2ce515e
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index 0e3d988..decd262 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.MapMaker;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
@@ -150,6 +151,17 @@
returnType == Void.TYPE ? null : typeFromClass(returnType));
}
+ /** Get a method reference from a Java reflection constructor. */
+ public static MethodReference methodFromMethod(Constructor<?> method) {
+ Class<?> holderClass = method.getDeclaringClass();
+ Class<?>[] parameterTypes = method.getParameterTypes();
+ ImmutableList.Builder<TypeReference> builder = ImmutableList.builder();
+ for (Class<?> parameterType : parameterTypes) {
+ builder.add(typeFromClass(parameterType));
+ }
+ return method(classFromClass(holderClass), "<init>", builder.build(), null);
+ }
+
/** Get a field reference from its full reference specification. */
public static FieldReference field(
ClassReference holderClass, String fieldName, TypeReference fieldType) {
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 f011931..6d24a8c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -307,6 +307,10 @@
this.options = options;
}
+ private static <T> SetWithReason<T> newSetWithoutReasonReporter() {
+ return new SetWithReason<>((f, r) -> {});
+ }
+
private void enqueueRootItems(Map<DexReference, Set<ProguardKeepRule>> items) {
items.entrySet().forEach(this::enqueueRootItem);
}
@@ -1097,6 +1101,7 @@
SetWithReason<DexEncodedField> reachableFields = reachableInstanceFields.get(type);
if (reachableFields != null) {
for (DexEncodedField field : reachableFields.getItems()) {
+ // TODO(b/120959039): Should the reason this field is reachable come from the set?
markInstanceFieldAsLive(field, KeepReason.reachableFromLiveType(type));
}
}
@@ -1241,14 +1246,14 @@
if (encodedField.accessFlags.isStatic()) {
markStaticFieldAsLive(encodedField.field, reason);
} else {
- SetWithReason<DexEncodedField> reachable =
- reachableInstanceFields.computeIfAbsent(
- encodedField.field.clazz, ignore -> new SetWithReason<>((f, r) -> {}));
- // TODO(b/120959039): The reachable.add test might be hiding other paths to the field.
- if (reachable.add(encodedField, reason)
- && isInstantiatedOrHasInstantiatedSubtype(encodedField.field.clazz)) {
+ if (isInstantiatedOrHasInstantiatedSubtype(encodedField.field.clazz)) {
// We have at least one live subtype, so mark it as live.
markInstanceFieldAsLive(encodedField, reason);
+ } else {
+ // Add the field to the reachable set if the type later becomes instantiated.
+ reachableInstanceFields
+ .computeIfAbsent(encodedField.field.clazz, ignore -> newSetWithoutReasonReporter())
+ .add(encodedField, reason);
}
}
}
@@ -1292,7 +1297,7 @@
// TODO(b/120959039): The reachable.add test might be hiding other paths to the method.
SetWithReason<DexEncodedMethod> reachable =
reachableVirtualMethods.computeIfAbsent(
- encodedMethod.method.holder, (ignore) -> new SetWithReason<>((m, r) -> {}));
+ encodedMethod.method.holder, ignore -> newSetWithoutReasonReporter());
if (reachable.add(encodedMethod, reason)) {
// Abstract methods cannot be live.
if (!encodedMethod.accessFlags.isAbstract()) {
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
index e518181..3ba9b99 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
@@ -8,9 +8,7 @@
@Keep
public class KeptByFieldReflectionTest {
- // TODO(b/123262024): This field must be kept un-initialized. Otherwise the "-whyareyoukeeping"
- // output tested will hit the initialization in <init> and not the reflective access.
- public int foo;
+ public int foo = 42;
public static void main(String[] args) throws Exception {
// Due to b/123210548 the object cannot be created by a reflective newInstance call.
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
index 62dcebe..6ba1f08 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
@@ -30,7 +30,7 @@
private static final Class<?> CLASS = KeptByFieldReflectionTest.class;
private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
- private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 0");
+ private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 42");
private final String EXPECTED_WHYAREYOUKEEPING =
StringUtils.lines(
@@ -55,6 +55,7 @@
public void test() throws Exception {
MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
FieldReference fooField = fieldFromField(CLASS.getDeclaredField("foo"));
+ MethodReference fooInit = methodFromMethod(CLASS.getDeclaredConstructor());
if (backend == Backend.CF) {
testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED_STDOUT);
@@ -80,6 +81,11 @@
inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
+ // The field is primarily kept by the reflective lookup in main.
inspector.field(fooField).assertRenamed().assertReflectedFrom(mainMethod);
+
+ // The field is also kept by the write in Foo.<init>.
+ // We may want to change that behavior. See b/124428834.
+ inspector.field(fooField).assertRenamed().assertKeptBy(inspector.method(fooInit));
}
}