Also consider referenced fields in if rule evaluator

Bug: 150189783
Change-Id: If18800c138048a2e803589160f3cb645ea7f346f
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 7d57def..dba8d9c 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.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.DirectMappedDexApplication.Builder;
+import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.InnerClassAttribute;
@@ -2040,6 +2041,11 @@
     }
   }
 
+  public boolean isFieldReferenced(DexEncodedField field) {
+    FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.field);
+    return info != null;
+  }
+
   public boolean isFieldLive(DexEncodedField field) {
     return liveFields.contains(field);
   }
@@ -2071,6 +2077,18 @@
     return directAndIndirectlyInstantiatedTypes.contains(clazz);
   }
 
+  public boolean isMethodLive(DexEncodedMethod method) {
+    return liveMethods.contains(method);
+  }
+
+  public boolean isMethodTargeted(DexEncodedMethod method) {
+    return targetedMethods.contains(method);
+  }
+
+  public boolean isTypeLive(DexProgramClass clazz) {
+    return liveTypes.contains(clazz);
+  }
+
   // Package protected due to entry point from worklist.
   void markInstanceFieldAsReachable(DexEncodedField encodedField, KeepReason reason) {
     DexField field = encodedField.field;
@@ -2770,14 +2788,10 @@
           IfRuleEvaluator ifRuleEvaluator =
               new IfRuleEvaluator(
                   appView,
+                  this,
                   executorService,
                   activeIfRules,
-                  liveFields.getItems(),
-                  liveMethods.getItems(),
-                  liveTypes.getItems(),
-                  mode,
-                  consequentSetBuilder,
-                  targetedMethods.getItems());
+                  consequentSetBuilder);
           addConsequentRootSet(ifRuleEvaluator.run(), false);
           assert getNumberOfLiveItems() == numberOfLiveItemsAfterProcessing;
           if (!workList.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 951faf1..fe81bb7 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.shaking.Enqueuer.Mode;
 import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions.ProguardIfRuleEvaluationData;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -37,35 +36,23 @@
 public class IfRuleEvaluator {
 
   private final AppView<? extends AppInfoWithSubtyping> appView;
+  private final Enqueuer enqueuer;
   private final ExecutorService executorService;
   private final List<Future<?>> futures = new ArrayList<>();
   private final Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules;
-  private final Set<DexEncodedField> liveFields;
-  private final Set<DexEncodedMethod> liveMethods;
-  private final Set<DexProgramClass> liveTypes;
-  private final Mode mode;
   private final ConsequentRootSetBuilder rootSetBuilder;
-  private final Set<DexEncodedMethod> targetedMethods;
 
   IfRuleEvaluator(
       AppView<? extends AppInfoWithSubtyping> appView,
+      Enqueuer enqueuer,
       ExecutorService executorService,
       Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules,
-      Set<DexEncodedField> liveFields,
-      Set<DexEncodedMethod> liveMethods,
-      Set<DexProgramClass> liveTypes,
-      Mode mode,
-      ConsequentRootSetBuilder rootSetBuilder,
-      Set<DexEncodedMethod> targetedMethods) {
+      ConsequentRootSetBuilder rootSetBuilder) {
     this.appView = appView;
+    this.enqueuer = enqueuer;
     this.executorService = executorService;
     this.ifRules = ifRules;
-    this.liveFields = liveFields;
-    this.liveMethods = liveMethods;
-    this.liveTypes = liveTypes;
-    this.mode = mode;
     this.rootSetBuilder = rootSetBuilder;
-    this.targetedMethods = targetedMethods;
   }
 
   public ConsequentRootSet run() throws ExecutionException {
@@ -184,7 +171,7 @@
     // A type is effectively live if (1) it is truly live, (2) the value of one of its fields has
     // been inlined by the member value propagation, or (3) the return value of one of its methods
     // has been forwarded by the member value propagation.
-    if (liveTypes.contains(clazz)) {
+    if (enqueuer.isTypeLive(clazz)) {
       return true;
     }
     for (DexEncodedField field : clazz.fields()) {
@@ -236,15 +223,15 @@
         filteredMembers,
         targetClass.fields(
             f ->
-                (liveFields.contains(f) || f.getOptimizationInfo().valueHasBeenPropagated())
+                (enqueuer.isFieldReferenced(f) || f.getOptimizationInfo().valueHasBeenPropagated())
                     && appView.graphLense().getOriginalFieldSignature(f.field).holder
                         == sourceClass.type));
     Iterables.addAll(
         filteredMembers,
         targetClass.methods(
             m ->
-                (liveMethods.contains(m)
-                        || targetedMethods.contains(m)
+                (enqueuer.isMethodLive(m)
+                        || enqueuer.isMethodTargeted(m)
                         || m.getOptimizationInfo().returnValueHasBeenPropagated())
                     && appView.graphLense().getOriginalMethodSignature(m.method).holder
                         == sourceClass.type));
@@ -287,7 +274,7 @@
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     ProguardIfRule materializedRule = rule.materialize(dexItemFactory, preconditions);
 
-    if (mode.isInitialTreeShaking() && !rule.isUsed()) {
+    if (enqueuer.getMode().isInitialTreeShaking() && !rule.isUsed()) {
       // We need to abort class inlining of classes that could be matched by the condition of this
       // -if rule.
       ClassInlineRule neverClassInlineRuleForCondition =
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/KeepClassesWithAnnotatedFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/KeepClassesWithAnnotatedFieldsTest.java
new file mode 100644
index 0000000..a802383
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/KeepClassesWithAnnotatedFieldsTest.java
@@ -0,0 +1,84 @@
+// 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.shaking.ifrule;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@RunWith(Parameterized.class)
+public class KeepClassesWithAnnotatedFieldsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public KeepClassesWithAnnotatedFieldsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepClassesWithAnnotatedFieldsTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(
+            "-if class * { @" + typeName(MyAnnotation.class) + " <fields>; }",
+            "-keep class <1> { <init>(); }",
+            "-keepclassmembers class * {",
+            "  @" + typeName(MyAnnotation.class) + " <fields>;",
+            "}")
+        .addKeepRuntimeVisibleAnnotations()
+        .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      DataClass obj = (DataClass) getDataClass().getDeclaredConstructor().newInstance();
+      setField(obj, "Hello world!");
+      System.out.println(obj.field);
+    }
+
+    @NeverInline
+    @NeverPropagateValue
+    static Class<?> getDataClass() {
+      return DataClass.class;
+    }
+
+    @NeverInline
+    static void setField(Object object, String value) throws Exception {
+      getDataClass().getDeclaredField("field").set(object, value);
+    }
+  }
+
+  static class DataClass {
+
+    @MyAnnotation
+    String field;
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.FIELD)
+  @interface MyAnnotation {}
+}