Add test for pruning signatures for not kept members in R8

Bug: 170077516
Change-Id: Ie4da6eb7b13eb4a3a527dcb76c8849859dfcd894
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
new file mode 100644
index 0000000..b441e6b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
@@ -0,0 +1,184 @@
+// 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.attributes;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MemberSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@SuppressWarnings("ALL")
+@RunWith(Parameterized.class)
+public class KeepSignatureTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String[] EXPECTED = new String[] {"Hello", "World", "Hello", "World"};
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public KeepSignatureTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(transformer(KeptClass.class).removeInnerClasses().transform())
+        .addProgramClassFileData(transformer(NotKeptClass.class).removeInnerClasses().transform())
+        .run(parameters.getRuntime(), KeptClass.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testKeptClassFieldAndMethodFull() throws Exception {
+    // TODO(b/170077516): The below should pass in false.
+    runTest(testForR8(parameters.getBackend()), true);
+  }
+
+  @Test
+  public void testKeptClassFieldAndMethodCompat() throws Exception {
+    runTest(testForR8Compat(parameters.getBackend()), true);
+  }
+
+  private void runTest(R8TestBuilder<?> testBuilder, boolean keptForNotKept) throws Exception {
+    testBuilder
+        .addProgramClassFileData(transformer(KeptClass.class).removeInnerClasses().transform())
+        .addProgramClassFileData(transformer(NotKeptClass.class).removeInnerClasses().transform())
+        .addKeepMainRule(KeptClass.class)
+        .addKeepRules(
+            StringUtils.lines(
+                "-keep class " + KeptClass.class.getTypeName() + " {",
+                "  *** keptMethod(...);",
+                "  *** keptField;",
+                "}"))
+        .addKeepAttributes(
+            ProguardKeepAttributes.SIGNATURE,
+            ProguardKeepAttributes.INNER_CLASSES,
+            ProguardKeepAttributes.ENCLOSING_METHOD)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .run(parameters.getRuntime(), KeptClass.class)
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(inspector -> inspect(inspector, keptForNotKept));
+  }
+
+  private void inspect(CodeInspector inspector, boolean keepForNotKept) {
+    ClassSubject keptClass = inspector.clazz(KeptClass.class);
+    assertThat(keptClass, isPresent());
+    assertEquals(
+        "<T:Ljava/lang/Object;>Ljava/lang/Object;", keptClass.getFinalSignatureAttribute());
+    FieldSubject keptField = keptClass.uniqueFieldWithName("keptField");
+    assertThat(keptField, isPresent());
+    assertEquals("TT;", keptField.getFinalSignatureAttribute());
+    MethodSubject keptMethod = keptClass.uniqueMethodWithName("keptMethod");
+    assertThat(keptMethod, isPresent());
+    assertEquals("<R:Ljava/lang/Object;>(TT;)TR;", keptMethod.getFinalSignatureAttribute());
+
+    // For all remaining classes and members, we should only keep signatures if in compat mode.
+    checkMemberSignature(keptClass.uniqueFieldWithName("notKeptField"), keepForNotKept, "TT;");
+    checkMemberSignature(
+        keptClass.uniqueMethodWithName("notKeptMethod"),
+        keepForNotKept,
+        "<R:Ljava/lang/Object;>(TT;)TR;");
+
+    ClassSubject notKeptClass = inspector.clazz(NotKeptClass.class);
+    assertThat(notKeptClass, isPresent());
+    if (keepForNotKept) {
+      assertEquals(
+          "<P:Ljava/lang/Object;>Ljava/lang/Object;", notKeptClass.getFinalSignatureAttribute());
+    } else {
+      assertNull(notKeptClass.getFinalSignatureAttribute());
+    }
+    checkMemberSignature(
+        notKeptClass.uniqueFieldWithName("notKeptField"), keepForNotKept, "Ljava/util/List<TP;>;");
+    checkMemberSignature(
+        notKeptClass.uniqueMethodWithName("notKeptMethod"),
+        keepForNotKept,
+        "(TP;TP;)Ljava/util/List<TP;>;");
+  }
+
+  private void checkMemberSignature(
+      MemberSubject member, boolean keepForNotKept, String signature) {
+    assertThat(member, isPresent());
+    if (keepForNotKept) {
+      assertEquals(signature, member.getFinalSignatureAttribute());
+    } else {
+      assertNull(member.getFinalSignatureAttribute());
+    }
+  }
+
+  @NeverClassInline
+  public static class NotKeptClass<P> {
+
+    public List<P> notKeptField;
+
+    @NeverInline
+    public List<P> notKeptMethod(P p1, P p2) {
+      if (notKeptField != null) {
+        return notKeptField;
+      }
+      ArrayList<P> ps = new ArrayList<>();
+      ps.add(p1);
+      ps.add(p2);
+      notKeptField = ps;
+      return ps;
+    }
+  }
+
+  public static class KeptClass<T> {
+
+    private T keptField;
+    private T notKeptField;
+
+    public <R> R keptMethod(T t) {
+      if (keptField == null) {
+        keptField = t;
+      }
+      return (R) keptField;
+    }
+
+    @NeverInline
+    public <R> R notKeptMethod(T t) {
+      if (notKeptField == null) {
+        notKeptField = t;
+      }
+      return (R) notKeptField;
+    }
+
+    public static void main(String[] args) {
+      KeptClass<String> keptClass = new KeptClass<>();
+      System.out.println(keptClass.<String>keptMethod("Hello"));
+      System.out.println(keptClass.<String>notKeptMethod("World"));
+      NotKeptClass<String> stringNotKeptClass = new NotKeptClass<>();
+      List<String> strings = stringNotKeptClass.notKeptMethod("Hello", "World");
+      System.out.println(strings.get(0));
+      System.out.println(strings.get(1));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
index 2fd8d37..ef44c64 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
@@ -25,6 +25,7 @@
 
   public abstract String getOriginalSignatureAttribute();
 
+  @Override
   public abstract String getFinalSignatureAttribute();
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index 7afc9f4..847675f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -46,6 +46,8 @@
     return finalSignature == null ? null : finalSignature.name;
   }
 
+  public abstract String getFinalSignatureAttribute();
+
   public abstract AnnotationSubject annotation(String name);
 
   public FieldSubject asFieldSubject() {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 3c86361..c6a63e2 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -42,6 +42,7 @@
 
   public abstract String getOriginalSignatureAttribute();
 
+  @Override
   public abstract String getFinalSignatureAttribute();
 
   public abstract DexEncodedMethod getMethod();