blob: a15b41826ee1b546746e192029297dc0e623d53e [file] [log] [blame]
// 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.ir.optimize.uninstantiatedtypes;
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.KeepConstantArguments;
import com.android.tools.r8.KeepUnusedArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.utils.BooleanUtils;
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.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
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 UninstantiatedAnnotatedArgumentsTest extends TestBase {
private final boolean keepUninstantiatedArguments;
private final Backend backend;
@Parameters(name = "{1}, keep uninstantiated arguments: {0}")
public static List<Object[]> params() {
return buildParameters(BooleanUtils.values(), Backend.values());
}
public UninstantiatedAnnotatedArgumentsTest(
boolean keepUninstantiatedArguments, Backend backend) {
this.keepUninstantiatedArguments = keepUninstantiatedArguments;
this.backend = backend;
}
@Test
public void test() throws Exception {
testForR8(backend)
.addInnerClasses(UninstantiatedAnnotatedArgumentsTest.class)
.addKeepMainRule(TestClass.class)
.addKeepClassRules(Instantiated.class, Uninstantiated.class)
.addKeepAttributes("RuntimeVisibleParameterAnnotations")
.enableClassInliningAnnotations()
.enableConstantArgumentAnnotations(keepUninstantiatedArguments)
.enableInliningAnnotations()
.enableUnusedArgumentAnnotations()
// TODO(b/123060011): Mapping not working in presence of argument removal.
.minification(keepUninstantiatedArguments)
.compile()
.inspect(this::verifyOutput)
.run(TestClass.class)
.assertSuccessWithOutput(StringUtils.times(StringUtils.lines("Hello world!"), 6));
}
private void verifyOutput(CodeInspector inspector) {
ClassSubject testClassSubject = inspector.clazz(TestClass.class);
assertThat(testClassSubject, isPresent());
ClassSubject instantiatedClassSubject = inspector.clazz(Instantiated.class);
assertThat(instantiatedClassSubject, isPresent());
ClassSubject uninstantiatedClassSubject = inspector.clazz(Uninstantiated.class);
assertThat(uninstantiatedClassSubject, isPresent());
List<MethodSubject> methodSubjects =
ImmutableList.of(
testClassSubject.uniqueMethodWithName("testRemoveStaticFromStart"),
testClassSubject.uniqueMethodWithName("testRemoveStaticFromMiddle"),
testClassSubject.uniqueMethodWithName("testRemoveStaticFromEnd"),
testClassSubject.uniqueMethodWithName("testRemoveVirtualFromStart"),
testClassSubject.uniqueMethodWithName("testRemoveVirtualFromMiddle"),
testClassSubject.uniqueMethodWithName("testRemoveVirtualFromEnd"));
for (MethodSubject methodSubject : methodSubjects) {
assertThat(methodSubject, isPresent());
// TODO(b/131735725): Should also remove arguments from the virtual methods.
if (keepUninstantiatedArguments || methodSubject.getOriginalName().contains("Virtual")) {
assertEquals(3, methodSubject.getMethod().method.proto.parameters.size());
assertEquals(3, methodSubject.getMethod().parameterAnnotationsList.size());
for (int i = 0; i < 3; ++i) {
DexAnnotationSet annotationSet =
methodSubject.getMethod().parameterAnnotationsList.get(i);
assertEquals(1, annotationSet.annotations.length);
DexAnnotation annotation = annotationSet.annotations[0];
if (i == getPositionOfUnusedArgument(methodSubject)) {
assertEquals(
uninstantiatedClassSubject.getFinalName(),
annotation.annotation.type.toSourceString());
} else {
assertEquals(
instantiatedClassSubject.getFinalName(),
annotation.annotation.type.toSourceString());
}
}
} else {
assertEquals(2, methodSubject.getMethod().method.proto.parameters.size());
assertEquals(2, methodSubject.getMethod().parameterAnnotationsList.size());
for (int i = 0; i < 2; ++i) {
DexAnnotationSet annotationSet =
methodSubject.getMethod().parameterAnnotationsList.get(i);
assertEquals(1, annotationSet.annotations.length);
DexAnnotation annotation = annotationSet.annotations[0];
assertEquals(
instantiatedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
}
}
}
}
private static int getPositionOfUnusedArgument(MethodSubject methodSubject) {
switch (methodSubject.getOriginalName(false)) {
case "testRemoveStaticFromStart":
case "testRemoveVirtualFromStart":
return 0;
case "testRemoveStaticFromMiddle":
case "testRemoveVirtualFromMiddle":
return 1;
case "testRemoveStaticFromEnd":
case "testRemoveVirtualFromEnd":
return 2;
default:
throw new Unreachable();
}
}
@NeverClassInline
static class TestClass {
public static void main(String[] args) {
testRemoveStaticFromStart(null, "Hello", " world!");
testRemoveStaticFromMiddle("Hello", null, " world!");
testRemoveStaticFromEnd("Hello", " world!", null);
new TestClass().testRemoveVirtualFromStart(null, "Hello", " world!");
new TestClass().testRemoveVirtualFromMiddle("Hello", null, " world!");
new TestClass().testRemoveVirtualFromEnd("Hello", " world!", null);
}
@KeepConstantArguments
@KeepUnusedArguments
@NeverInline
static void testRemoveStaticFromStart(
@Uninstantiated Dead uninstantiated,
@Instantiated String instantiated,
@Instantiated String otherInstantiated) {
System.out.println(instantiated + otherInstantiated);
}
@KeepConstantArguments
@KeepUnusedArguments
@NeverInline
static void testRemoveStaticFromMiddle(
@Instantiated String instantiated,
@Uninstantiated Dead uninstantiated,
@Instantiated String otherInstantiated) {
System.out.println(instantiated + otherInstantiated);
}
@KeepConstantArguments
@KeepUnusedArguments
@NeverInline
static void testRemoveStaticFromEnd(
@Instantiated String instantiated,
@Instantiated String otherInstantiated,
@Uninstantiated Dead uninstantiated) {
System.out.println(instantiated + otherInstantiated);
}
@KeepConstantArguments
@KeepUnusedArguments
@NeverInline
void testRemoveVirtualFromStart(
@Uninstantiated Dead uninstantiated,
@Instantiated String instantiated,
@Instantiated String otherInstantiated) {
System.out.println(instantiated + otherInstantiated);
}
@KeepConstantArguments
@KeepUnusedArguments
@NeverInline
void testRemoveVirtualFromMiddle(
@Instantiated String instantiated,
@Uninstantiated Dead uninstantiated,
@Instantiated String otherInstantiated) {
System.out.println(instantiated + otherInstantiated);
}
@KeepConstantArguments
@KeepUnusedArguments
@NeverInline
void testRemoveVirtualFromEnd(
@Instantiated String instantiated,
@Instantiated String otherInstantiated,
@Uninstantiated Dead uninstantiated) {
System.out.println(instantiated + otherInstantiated);
}
}
static class Dead {}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@interface Instantiated {}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@interface Uninstantiated {}
}