Add tests for unbound type variables in generic signatures

Bug: 170174911
Change-Id: Icfd12c5ef4c37bf665e8425b7e031adcbc9ebb27
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java
new file mode 100644
index 0000000..58812ee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java
@@ -0,0 +1,169 @@
+// 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.graph.genericsignature;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.lang.reflect.Method;
+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 UnboundedFormalTypeGenericSignatureTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String SUPER_BINARY_NAME =
+      DescriptorUtils.getBinaryNameFromJavaType(Super.class.getTypeName());
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public UnboundedFormalTypeGenericSignatureTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(
+            transformer(Main.class).removeInnerClasses().transform(),
+            transformer(Super.class).removeInnerClasses().transform())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(Super.class.getTypeName() + "<T>", "R", "T");
+  }
+
+  @Test
+  public void testUnboundParametersInClassRuntime() throws Exception {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters)
+            .addProgramClassFileData(
+                transformer(Main.class)
+                    .removeInnerClasses()
+                    .setGenericSignature("L" + SUPER_BINARY_NAME + "<TR;>;")
+                    .transform(),
+                transformer(Super.class).removeInnerClasses().transform())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isCfRuntime()) {
+      runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
+    } else {
+      runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<R>", "R", "T");
+    }
+  }
+
+  @Test
+  public void testUnboundParametersInMethodRuntime() throws Exception {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters)
+            .addProgramClassFileData(
+                transformer(Main.class)
+                    .removeInnerClasses()
+                    .setGenericSignature(
+                        MethodPredicate.onName("testStatic"), "<R:Ljava/lang/Object;>()TS;")
+                    .setGenericSignature(
+                        MethodPredicate.onName("testVirtual"), "<R:Ljava/lang/Object;>()TQ;")
+                    .transform(),
+                transformer(Super.class).removeInnerClasses().transform())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isCfRuntime()) {
+      runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<T>", "null", "null");
+    } else {
+      runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<T>", "S", "Q");
+    }
+  }
+
+  @Test
+  public void testUnboundParametersInClassR8() throws Exception {
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(
+                transformer(Main.class)
+                    .removeInnerClasses()
+                    .setGenericSignature("L" + SUPER_BINARY_NAME + "<TR;>;")
+                    .transform(),
+                transformer(Super.class).removeInnerClasses().transform())
+            .addKeepAllClassesRule()
+            .addKeepAttributes(
+                ProguardKeepAttributes.SIGNATURE,
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isCfRuntime()) {
+      runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
+    } else {
+      runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<R>", "R", "T");
+    }
+  }
+
+  @Test
+  public void testUnboundParametersInMethodR8() throws Exception {
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(
+                transformer(Main.class)
+                    .removeInnerClasses()
+                    .setGenericSignature(
+                        MethodPredicate.onName("testStatic"), "<R:Ljava/lang/Object;>()TS;")
+                    .setGenericSignature(
+                        MethodPredicate.onName("testVirtual"), "<R:Ljava/lang/Object;>()TQ;")
+                    .transform(),
+                transformer(Super.class).removeInnerClasses().transform())
+            .addKeepAllClassesRule()
+            .addKeepAttributes(
+                ProguardKeepAttributes.SIGNATURE,
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isCfRuntime()) {
+      runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<T>", "null", "null");
+    } else {
+      runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<T>", "S", "Q");
+    }
+  }
+
+  public static class Super<T> {}
+
+  public static class Main<T> extends Super<T> {
+
+    public static <R extends Super<R>> void main(String[] args) throws NoSuchMethodException {
+      System.out.println(Main.class.getGenericSuperclass());
+      testStatic();
+      new Main<>().testVirtual();
+    }
+
+    private static <R> R testStatic() {
+      try {
+        Method testStatic = Main.class.getDeclaredMethod("testStatic");
+        System.out.println(testStatic.getGenericReturnType());
+        return null;
+      } catch (NoSuchMethodException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    private T testVirtual() {
+      try {
+        Method testVirtual = Main.class.getDeclaredMethod("testVirtual");
+        System.out.println(testVirtual.getGenericReturnType());
+        return null;
+      } catch (NoSuchMethodException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 283c5af..86e4cb7 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -545,6 +545,19 @@
         });
   }
 
+  public ClassFileTransformer setGenericSignature(MethodPredicate predicate, String newSignature) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String descriptor, String signature, String[] exceptions) {
+            return predicate.test(access, name, descriptor, signature, exceptions)
+                ? super.visitMethod(access, name, descriptor, newSignature, exceptions)
+                : super.visitMethod(access, name, descriptor, signature, exceptions);
+          }
+        });
+  }
+
   public ClassFileTransformer removeFields(FieldPredicate predicate) {
     return addClassTransformer(
         new ClassTransformer() {