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() {