blob: 339bbbc22005fa30c2b6f5450db826df868a69f7 [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.TestParameters;
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 enableProguardCompatibilityMode;
private final boolean keepUninstantiatedArguments;
private final TestParameters parameters;
@Parameters(name = "{2}, compat: {0}, keep uninstantiated arguments: {1}")
public static List<Object[]> params() {
return buildParameters(
BooleanUtils.values(),
BooleanUtils.values(),
getTestParameters().withAllRuntimesAndApiLevels().build());
}
public UninstantiatedAnnotatedArgumentsTest(
boolean enableProguardCompatibilityMode,
boolean keepUninstantiatedArguments,
TestParameters parameters) {
this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
this.keepUninstantiatedArguments = keepUninstantiatedArguments;
this.parameters = parameters;
}
@Test
public void test() throws Exception {
testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
.addInnerClasses(UninstantiatedAnnotatedArgumentsTest.class)
.addConstantArgumentAnnotations()
.addKeepMainRule(TestClass.class)
.addKeepClassRules(Instantiated.class, Uninstantiated.class)
.addKeepRuntimeVisibleParameterAnnotations()
.enableNeverClassInliningAnnotations()
.enableConstantArgumentAnnotations(keepUninstantiatedArguments)
.enableInliningAnnotations()
.enableUnusedArgumentAnnotations()
// TODO(b/123060011): Mapping not working in presence of argument removal.
.minification(keepUninstantiatedArguments)
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::verifyOutput)
.run(parameters.getRuntime(), 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.
boolean shouldHaveArgumentRemoval =
keepUninstantiatedArguments || methodSubject.getOriginalName().contains("Virtual");
if (shouldHaveArgumentRemoval) {
assertEquals(3, methodSubject.getMethod().getParameters().size());
// In non-compat mode, R8 removes annotations from non-pinned items.
assertEquals(
enableProguardCompatibilityMode ? 3 : 0,
methodSubject.getMethod().getParameterAnnotations().size());
} else {
assertEquals(2, methodSubject.getMethod().getReference().proto.parameters.size());
assertEquals(
enableProguardCompatibilityMode ? 2 : 0,
methodSubject.getMethod().getParameterAnnotations().size());
}
for (int i = 0; i < methodSubject.getMethod().getParameterAnnotations().size(); ++i) {
DexAnnotationSet annotationSet = methodSubject.getMethod().getParameterAnnotation(i);
assertEquals(1, annotationSet.size());
DexAnnotation annotation = annotationSet.getFirst();
if (shouldHaveArgumentRemoval && i == getPositionOfUnusedArgument(methodSubject)) {
assertEquals(
uninstantiatedClassSubject.getFinalName(),
annotation.getAnnotationType().getTypeName());
} else {
assertEquals(
instantiatedClassSubject.getFinalName(),
annotation.getAnnotationType().getTypeName());
}
}
}
}
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 {}
}