Apply -whyareyoukeeping and -checkdiscard to overridden methods.

Bug: 132204801
Change-Id: Ib4957d94c184c25d0c5c3062d1efc693f7a9d8e2
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 d56efda..9bf2f23 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -178,12 +178,16 @@
           markClass(clazz, rule);
         } else {
           preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
-          markMatchingFields(clazz, memberKeepRules, rule, preconditionSupplier);
-          markMatchingMethods(clazz, memberKeepRules, rule, preconditionSupplier);
+          markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier, true);
+          markMatchingOverriddenMethods(
+              appView.appInfo(), clazz, memberKeepRules, rule, preconditionSupplier, true);
+          markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier, true);
         }
       } else if (rule instanceof ProguardWhyAreYouKeepingRule) {
         markClass(clazz, rule);
         markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true);
+        markMatchingOverriddenMethods(
+            appView.appInfo(), clazz, memberKeepRules, rule, null, true);
         markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true);
       } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule
           || rule instanceof ProguardAssumeNoSideEffectRule) {
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedOverriddenMethodTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedOverriddenMethodTest.java
new file mode 100644
index 0000000..8d44559
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedOverriddenMethodTest.java
@@ -0,0 +1,116 @@
+// 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.checkdiscarded;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CheckDiscardedOverriddenMethodTest extends TestBase {
+
+  @Parameters(name = "{0} minification: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
+  }
+
+  private final TestParameters parameters;
+  private final boolean minification;
+
+  public CheckDiscardedOverriddenMethodTest(TestParameters parameters, boolean minification) {
+    this.parameters = parameters;
+    this.minification = minification;
+  }
+
+  private void test(Class<?> main, Class<?> targetClass) throws Exception {
+    try {
+      testForR8(Backend.DEX)
+          .addInnerClasses(CheckDiscardedOverriddenMethodTest.class)
+          .addKeepMainRule(main)
+          .addKeepRules(
+              "-checkdiscard class **.*$" + targetClass.getSimpleName() + " { void gone(); }")
+          .enableClassInliningAnnotations()
+          .enableInliningAnnotations()
+          .enableMergeAnnotations()
+          .minification(minification)
+          .setMinApi(parameters.getRuntime())
+          .compileWithExpectedDiagnostics(diagnostics -> {
+            diagnostics.assertInfosCount(1);
+            String message = diagnostics.getInfos().get(0).getDiagnosticMessage();
+            assertThat(message, containsString("was not discarded"));
+            assertThat(message, containsString("is invoked from"));
+          });
+      fail("Expect to get compilation failure.");
+    } catch (CompilationFailedException e) {
+      String message = e.getCause().getMessage();
+      assertThat(message, containsString("Discard checks failed."));
+    }
+  }
+
+  @Test
+  public void testExtends() throws Exception {
+    test(TestMain1.class, Base.class);
+  }
+
+  @Test
+  public void testImplements() throws Exception {
+    test(TestMain2.class, Itf.class);
+  }
+
+  @NeverMerge
+  static class Base {
+    @NeverInline
+    void gone() {
+      System.out.println("should be gone");
+    }
+  }
+
+  @NeverClassInline
+  static class Sub extends Base {
+    @NeverInline
+    @Override
+    void gone() {
+      System.out.println("used");
+    }
+  }
+
+  static class TestMain1 {
+    public static void main(String... args) {
+      new Sub().gone();
+    }
+  }
+
+  @NeverMerge
+  interface Itf {
+    void gone();
+  }
+
+  @NeverClassInline
+  static class Impl implements Itf {
+    @NeverInline
+    @Override
+    public void gone() {
+      System.out.println("used");
+    }
+  }
+
+  static class TestMain2 {
+    public static void main(String... args) {
+      new Impl().gone();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java
new file mode 100644
index 0000000..b008d77
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java
@@ -0,0 +1,165 @@
+// 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.whyareyoukeeping;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class WhyAreYouKeepingOverriddenMethodTest extends TestBase {
+  @Parameters(name = "{0} minification: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
+  }
+
+  private final TestParameters parameters;
+  private final boolean minification;
+
+  public WhyAreYouKeepingOverriddenMethodTest(TestParameters parameters, boolean minification) {
+    this.parameters = parameters;
+    this.minification = minification;
+  }
+
+  private void testViaConfig(
+      Class<?> main, Class<?> targetClass, Class<?> subClass) throws Exception {
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    testForR8(Backend.DEX)
+        .addInnerClasses(WhyAreYouKeepingOverriddenMethodTest.class)
+        .addKeepMainRule(main)
+        .addKeepRules(
+            "-whyareyoukeeping class **.*$" + targetClass.getSimpleName() + " { void gone(); }")
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .minification(minification)
+        .setMinApi(parameters.getRuntime())
+        // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
+        .redirectStdOut(new PrintStream(baos))
+        .compile();
+    String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
+    assertEquals(expectedMessageForConfig(main, targetClass, subClass), output);
+  }
+
+  private void testViaConsumer(
+      Class<?> main, Class<?> targetClass, Class<?> subClass) throws Exception {
+    WhyAreYouKeepingConsumer graphConsumer = new WhyAreYouKeepingConsumer(null);
+    testForR8(Backend.DEX)
+        .addInnerClasses(WhyAreYouKeepingOverriddenMethodTest.class)
+        .addKeepMainRule(main)
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .minification(minification)
+        .setMinApi(parameters.getRuntime())
+        .setKeptGraphConsumer(graphConsumer)
+        .compile();
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    graphConsumer.printWhyAreYouKeeping(
+        Reference.methodFromMethod(targetClass.getMethod("gone")), new PrintStream(baos));
+    String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
+    assertEquals(expectedMessageForConsumer(main, targetClass, subClass), output);
+  }
+
+  private String expectedMessageForConfig(
+      Class<?> main, Class<?> targetClass, Class<?> subClass) {
+    return StringUtils.lines(
+        "Nothing is keeping " + targetClass.getTypeName(),
+        "Nothing is keeping void " + targetClass.getTypeName() + ".gone()",
+        "void " + subClass.getTypeName() + ".gone()",
+        "|- is invoked from:",
+        "|  void " + main.getTypeName() + ".main(java.lang.String[])",
+        "|- is referenced in keep rule:",
+        "|  -keep class " + main.getTypeName() + " { public static void main(java.lang.String[]); }"
+    );
+  }
+
+  // TODO(b/120959039): This should be same as configuration output.
+  private String expectedMessageForConsumer(
+      Class<?> main, Class<?> targetClass, Class<?> subClass) {
+    return StringUtils.lines(
+        "Nothing is keeping void " + targetClass.getTypeName() + ".gone()"
+    );
+  }
+
+  @Test
+  public void testExtends_config() throws Exception {
+    testViaConfig(TestMain1.class, Base.class, Sub.class);
+  }
+
+  @Test
+  public void testExtends_consumer() throws Exception {
+    testViaConsumer(TestMain1.class, Base.class, Sub.class);
+  }
+
+  @Test
+  public void testImplements_config() throws Exception {
+    testViaConfig(TestMain2.class, Itf.class, Impl.class);
+  }
+
+  @Test
+  public void testImplements_consumer() throws Exception {
+    testViaConsumer(TestMain2.class, Itf.class, Impl.class);
+  }
+
+  @NeverMerge
+  static class Base {
+    @NeverInline
+    public void gone() {
+      System.out.println("should be gone");
+    }
+  }
+
+  @NeverClassInline
+  static class Sub extends Base {
+    @NeverInline
+    @Override
+    public void gone() {
+      System.out.println("used");
+    }
+  }
+
+  static class TestMain1 {
+    public static void main(String... args) {
+      new Sub().gone();
+    }
+  }
+
+  @NeverMerge
+  interface Itf {
+    void gone();
+  }
+
+  @NeverClassInline
+  static class Impl implements Itf {
+    @NeverInline
+    @Override
+    public void gone() {
+      System.out.println("used");
+    }
+  }
+
+  static class TestMain2 {
+    public static void main(String... args) {
+      new Impl().gone();
+    }
+  }
+}