Account for method-return-value propagation in -if rule evaluation

Change-Id: I039d58ccce261cd3688f113573704375fe7b4adb
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index bfb248a..0687442 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1131,6 +1131,11 @@
     }
 
     @Override
+    public boolean returnValueHasBeenPropagated() {
+      return false;
+    }
+
+    @Override
     public UpdatableMethodOptimizationInfo mutableCopy() {
       return new MethodOptimizationInfoImpl();
     }
@@ -1189,6 +1194,7 @@
     // Note that this bit set takes into account the receiver for instance methods.
     private BitSet nonNullParamOnNormalExits = null;
     private boolean reachabilitySensitive = false;
+    private boolean returnValueHasBeenPropagated = false;
 
     private MethodOptimizationInfoImpl() {
       // Intentionally left empty, just use the default values.
@@ -1461,6 +1467,16 @@
     }
 
     @Override
+    public void markAsPropagated() {
+      returnValueHasBeenPropagated = true;
+    }
+
+    @Override
+    public boolean returnValueHasBeenPropagated() {
+      return returnValueHasBeenPropagated;
+    }
+
+    @Override
     public UpdatableMethodOptimizationInfo mutableCopy() {
       assert this != DefaultMethodOptimizationInfoImpl.DEFAULT_INSTANCE;
       return new MethodOptimizationInfoImpl(this);
diff --git a/src/main/java/com/android/tools/r8/graph/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/MethodOptimizationInfo.java
index 977160c..96bbf6d 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodOptimizationInfo.java
@@ -67,5 +67,7 @@
 
   boolean mayHaveSideEffects();
 
+  boolean returnValueHasBeenPropagated();
+
   UpdatableMethodOptimizationInfo mutableCopy();
 }
diff --git a/src/main/java/com/android/tools/r8/graph/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/UpdatableMethodOptimizationInfo.java
index 78b10cd..1aa2129 100644
--- a/src/main/java/com/android/tools/r8/graph/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/UpdatableMethodOptimizationInfo.java
@@ -22,6 +22,8 @@
 
   void markReturnsObjectOfType(TypeLatticeElement type);
 
+  void markAsPropagated();
+
   void markMayNotHaveSideEffects();
 
   void markNeverReturnsNull();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index 6af7bef..488c06b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -36,6 +36,8 @@
 
   void markProcessed(DexEncodedMethod method, ConstraintWithTarget state);
 
+  void markAsPropagated(DexEncodedMethod method);
+
   void markUseIdentifierNameString(DexEncodedMethod method);
 
   void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
index ac143e3..874977b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
@@ -80,6 +80,11 @@
   }
 
   @Override
+  public synchronized void markAsPropagated(DexEncodedMethod method) {
+    getOptimizationInfoForUpdating(method).markAsPropagated();
+  }
+
+  @Override
   public synchronized void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {
     processed.put(method, state);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index 9254711..0946ca3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -43,6 +43,9 @@
   public void methodNeverReturnsNormally(DexEncodedMethod method) {}
 
   @Override
+  public void markAsPropagated(DexEncodedMethod method) {}
+
+  @Override
   public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {}
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
index ab00d6e..313148f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
@@ -59,6 +59,11 @@
   }
 
   @Override
+  public void markAsPropagated(DexEncodedMethod method) {
+    // Ignored.
+  }
+
+  @Override
   public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {
     // Just as processed, don't provide any inlining constraints.
     method.markProcessed(ConstraintWithTarget.NEVER);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 00a1acc..d229fa4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -281,6 +281,7 @@
       } else {
         iterator.add(replacement);
       }
+      target.getMutableOptimizationInfo().markAsPropagated();
       return;
     }
     if (target.getOptimizationInfo().neverReturnsNull()
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 648d8d1..819c4ff 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -377,8 +377,9 @@
     }
 
     private boolean isEffectivelyLive(DexProgramClass clazz) {
-      // A type is effectively live if it is truly live, or if the value of one of its fields has
-      // been inlined by the member value propagation.
+      // 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.type)) {
         return true;
       }
@@ -387,6 +388,11 @@
           return true;
         }
       }
+      for (DexEncodedMethod method : clazz.methods()) {
+        if (method.getOptimizationInfo().returnValueHasBeenPropagated()) {
+          return true;
+        }
+      }
       return false;
     }
 
@@ -434,7 +440,9 @@
           filteredMembers,
           targetClass.methods(
               m ->
-                  (liveMethods.contains(m) || targetedMethods.contains(m))
+                  (liveMethods.contains(m)
+                          || targetedMethods.contains(m)
+                          || m.getOptimizationInfo().returnValueHasBeenPropagated())
                       && appView.graphLense().getOriginalMethodSignature(m.method).holder
                           == sourceClass.type));
 
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithMethodValuePropagationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithMethodValuePropagationTest.java
new file mode 100644
index 0000000..f795067
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithMethodValuePropagationTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2019, 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.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IfWithMethodValuePropagationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public IfWithMethodValuePropagationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, R.class, Layout.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(
+            "-if class " + R.class.getTypeName() + " {",
+            "  static int id();",
+            "}",
+            "-keep class " + Layout.class.getTypeName())
+        .addLibraryClasses(Library.class)
+        .addLibraryFiles(runtimeJar(parameters))
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .inspect(this::verifyOutput)
+        .addRunClasspathFiles(
+            testForR8(parameters.getBackend())
+                .addProgramClasses(Library.class)
+                .addKeepAllClassesRule()
+                .setMinApi(parameters.getRuntime())
+                .compile()
+                .writeToZip())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Layout.toString()");
+  }
+
+  private void verifyOutput(CodeInspector inspector) {
+    // R.ID has been inlined.
+    assertThat(inspector.clazz(R.class), not(isPresent()));
+
+    // Layout is kept by the conditional rule.
+    assertThat(inspector.clazz(Layout.class), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(Library.getViewById(R.id()));
+    }
+  }
+
+  static class Library {
+
+    public static Object getViewById(int viewId) {
+      return new Layout();
+    }
+  }
+
+  static class R {
+
+    public static int id() {
+      return 42;
+    }
+  }
+
+  static class Layout {
+
+    @Override
+    public String toString() {
+      return "Layout.toString()";
+    }
+  }
+}