blob: 48877b90147de5fd757524a9dc26d1dfa06bd19b [file] [log] [blame]
// Copyright (c) 2021, 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 com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.graph.genericsignature.GenericSignatureKeepAttributesTest.Outer.Middle;
import com.android.tools.r8.graph.genericsignature.GenericSignatureKeepAttributesTest.Outer.Middle.Inner;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.List;
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 GenericSignatureKeepAttributesTest extends TestBase {
private final TestParameters parameters;
private final boolean isCompat;
private final String[] EXPECTED_JVM =
new String[] {
"Outer.Middle.Inner::test",
"public class com.android.tools.r8.graph.genericsignature"
+ ".GenericSignatureKeepAttributesTest$Outer$Middle$Inner<I>"
};
private final String[] EXPECTED_DEX =
new String[] {
"Outer.Middle.Inner::test",
"class com.android.tools.r8.graph.genericsignature"
+ ".GenericSignatureKeepAttributesTest$Outer$Middle$Inner"
};
@Parameters(name = "{0}, isCompat: {1}")
public static List<Object[]> data() {
return buildParameters(
getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
}
public GenericSignatureKeepAttributesTest(TestParameters parameters, boolean isCompat) {
this.parameters = parameters;
this.isCompat = isCompat;
}
@Test
public void testRuntime() throws Exception {
testForRuntime(parameters)
.addProgramClasses(Supplier.class, Predicate.class, Outer.class, Middle.class, Main.class)
.addProgramClassFileData(getClassFileData())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(parameters.isCfRuntime() ? EXPECTED_JVM : EXPECTED_DEX);
}
@Test
public void testR8() throws Exception {
(isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
.addProgramClasses(Supplier.class, Predicate.class, Outer.class, Middle.class, Main.class)
.addProgramClassFileData(getClassFileData())
.setMinApi(parameters.getApiLevel())
.addKeepAttributeSignature()
.addKeepAttributeInnerClassesAndEnclosingMethod()
.addKeepMainRule(Main.class)
.addKeepClassAndMembersRules(Outer.Middle.Inner.class, Supplier.class, Predicate.class)
.enableInliningAnnotations()
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(parameters.isCfRuntime() ? EXPECTED_JVM : EXPECTED_DEX)
.inspect(this::inspectSignatures);
}
private byte[] getClassFileData() throws Exception {
return transformer(Inner.class)
.transformMethodInsnInMethod(
"test",
((opcode, owner, name, descriptor, isInterface, continuation) -> {
if (parameters.isCfRuntime() && name.equals("toString")) {
name = "toGenericString";
}
continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}))
.transform();
}
private void inspectSignatures(CodeInspector inspector) {
ClassSubject outerClass = inspector.clazz(Outer.class);
assertThat(outerClass, isPresent());
assertEquals(
isCompat ? "<O::L" + binaryName(Supplier.class) + "<*>;>Ljava/lang/Object;" : null,
outerClass.getFinalSignatureAttribute());
ClassSubject middleClass = inspector.clazz(Middle.class);
assertThat(middleClass, isPresent());
assertEquals(
isCompat ? "<M::L" + binaryName(Predicate.class) + "<TO;>;>Ljava/lang/Object;" : null,
middleClass.getFinalSignatureAttribute());
ClassSubject innerClass = inspector.clazz(Inner.class);
assertThat(innerClass, isPresent());
MethodSubject testMethod = innerClass.uniqueMethodWithName("test");
assertThat(testMethod, isPresent());
if (isCompat) {
assertEquals("(TO;TM;)TI;", testMethod.getFinalSignatureAttribute());
} else {
assertEquals(
"(L"
+ binaryName(Supplier.class)
+ "<*>;L"
+ binaryName(Predicate.class)
+ "<Ljava/lang/Object;>;)TI;",
testMethod.getFinalSignatureAttribute());
}
}
public interface Supplier<T> {}
public interface Predicate<T> {}
public static class Outer<O extends Supplier<?>> {
public class Middle<M extends Predicate<O>> {
public class Inner<I> {
public I test(O o, M m) {
System.out.println("Outer.Middle.Inner::test");
System.out.println(this.getClass().toString()); // .toGenericString() for JVMs
return null;
}
}
private Outer<O>.Middle<M>.Inner<Object> createInner() {
return new Inner<>();
}
}
private Outer<O>.Middle<?> createMiddle() {
return new Outer<O>.Middle<>();
}
@NeverInline
public static Outer<?>.Middle<?>.Inner<Object> create() {
return new Outer<>().createMiddle().createInner();
}
}
public static class Main {
public static void main(String[] args) {
Outer.create().test(null, null);
}
}
}