Version 1.4.95
Cherry pick: Remove parameter annotations in unused argument removal
CL: https://r8-review.googlesource.com/c/r8/+/37882
Cherry pick: Remove parameter annotations in uninstantiated type optimization
CL: https://r8-review.googlesource.com/c/r8/+/37883
Bug: 131663970, 131718819
Change-Id: I97debc4a7f923ff55c04e6709c45e1bf1bab3fa5
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index fa693c5..356d63d 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.4.94";
+ public static final String LABEL = "1.4.95";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index 44b09b0..7bafd01 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -12,6 +12,8 @@
public class DexAnnotationSet extends CachedHashValueDexItem {
+ public static final DexAnnotationSet[] EMPTY_ARRAY = {};
+
private static final int UNSORTED = 0;
private static final DexAnnotationSet THE_EMPTY_ANNOTATIONS_SET =
new DexAnnotationSet(new DexAnnotation[0]);
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 8cab73b..79c3b28 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -46,11 +46,13 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
+import java.util.function.IntPredicate;
public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult {
@@ -614,15 +616,25 @@
public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method) {
checkIfObsolete();
+ return toTypeSubstitutedMethod(method, null);
+ }
+
+ public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method, Consumer<Builder> consumer) {
+ checkIfObsolete();
if (this.method == method) {
return this;
}
Builder builder = builder(this);
builder.setMethod(method);
// TODO(b/112847660): Fix type fixers that use this method: ProguardMapApplier
- // TODO(b/112847660): Fix type fixers that use this method: Staticizer
- // TODO(b/112847660): Fix type fixers that use this method: VerticalClassMerger
+ // TODO(b/112847660): Fix type fixers that use this method: Class staticizer
+ // TODO(b/112847660): Fix type fixers that use this method: Uninstantiated type optimization
+ // TODO(b/112847660): Fix type fixers that use this method: Unused argument removal
+ // TODO(b/112847660): Fix type fixers that use this method: Vertical class merger
// setObsolete();
+ if (consumer != null) {
+ consumer.accept(builder);
+ }
return builder.build();
}
@@ -1226,12 +1238,12 @@
return new Builder(from);
}
- private static class Builder {
+ public static class Builder {
private DexMethod method;
private final MethodAccessFlags accessFlags;
private final DexAnnotationSet annotations;
- private final ParameterAnnotationsList parameterAnnotations;
+ private ParameterAnnotationsList parameterAnnotations;
private Code code;
private CompilationState compilationState;
private OptimizationInfo optimizationInfo;
@@ -1242,17 +1254,63 @@
method = from.method;
accessFlags = from.accessFlags.copy();
annotations = from.annotations;
- parameterAnnotations = from.parameterAnnotationsList;
code = from.code;
compilationState = from.compilationState;
optimizationInfo = from.optimizationInfo.mutableCopy();
classFileVersion = from.classFileVersion;
+
+ if (from.parameterAnnotationsList.isEmpty()
+ || from.parameterAnnotationsList.size() == method.proto.parameters.size()) {
+ parameterAnnotations = from.parameterAnnotationsList;
+ } else {
+ assert false
+ : "Parameter annotations does not match proto of method `"
+ + method.toSourceString()
+ + "` (was: "
+ + parameterAnnotations
+ + ")";
+ parameterAnnotations = ParameterAnnotationsList.empty();
+ }
}
public void setMethod(DexMethod method) {
this.method = method;
}
+ public Builder setParameterAnnotations(ParameterAnnotationsList parameterAnnotations) {
+ this.parameterAnnotations = parameterAnnotations;
+ return this;
+ }
+
+ public Builder removeParameterAnnotations(IntPredicate predicate) {
+ if (parameterAnnotations.isEmpty()) {
+ // Nothing to do.
+ return this;
+ }
+
+ List<DexAnnotationSet> newParameterAnnotations = new ArrayList<>();
+ int newNumberOfMissingParameterAnnotations = 0;
+
+ for (int oldIndex = 0; oldIndex < parameterAnnotations.size(); oldIndex++) {
+ if (!predicate.test(oldIndex)) {
+ if (parameterAnnotations.isMissing(oldIndex)) {
+ newNumberOfMissingParameterAnnotations++;
+ } else {
+ newParameterAnnotations.add(parameterAnnotations.get(oldIndex));
+ }
+ }
+ }
+
+ if (newParameterAnnotations.isEmpty()) {
+ return setParameterAnnotations(ParameterAnnotationsList.empty());
+ }
+
+ return setParameterAnnotations(
+ new ParameterAnnotationsList(
+ newParameterAnnotations.toArray(DexAnnotationSet.EMPTY_ARRAY),
+ newNumberOfMissingParameterAnnotations));
+ }
+
public Builder setStatic() {
this.accessFlags.setStatic();
return this;
@@ -1282,6 +1340,8 @@
assert accessFlags != null;
assert annotations != null;
assert parameterAnnotations != null;
+ assert parameterAnnotations.isEmpty()
+ || parameterAnnotations.size() == method.proto.parameters.size();
DexEncodedMethod result =
new DexEncodedMethod(
method, accessFlags, annotations, parameterAnnotations, code, classFileVersion);
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index b591bab..72c20a3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -13,7 +13,7 @@
public class DexString extends IndexedDexItem implements PresortedComparable<DexString> {
- public static final DexString[] EMPTY_ARRAY = new DexString[]{};
+ public static final DexString[] EMPTY_ARRAY = {};
public final int size; // size of this string, in UTF-16
public final byte[] content;
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 402b56d..9e124f2 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.IteratorUtils;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
@@ -29,6 +30,7 @@
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
+import java.util.function.Consumer;
import java.util.function.Function;
/**
@@ -217,6 +219,18 @@
}
return new RemovedArgumentsInfo(newRemovedArguments);
}
+
+ public Consumer<DexEncodedMethod.Builder> createParameterAnnotationsRemover(
+ DexEncodedMethod method) {
+ if (numberOfRemovedArguments() > 0 && !method.parameterAnnotationsList.isEmpty()) {
+ return builder -> {
+ int firstArgumentIndex = BooleanUtils.intValue(!method.isStatic());
+ builder.removeParameterAnnotations(
+ oldIndex -> isArgumentRemoved(oldIndex + firstArgumentIndex));
+ };
+ }
+ return null;
+ }
}
private static final RewrittenPrototypeDescription none = new RewrittenPrototypeDescription();
diff --git a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
index 97ae20c..68c7da1 100644
--- a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
+++ b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
@@ -46,7 +46,7 @@
}
private ParameterAnnotationsList() {
- this.values = new DexAnnotationSet[0];
+ this.values = DexAnnotationSet.EMPTY_ARRAY;
this.missingParameterAnnotations = 0;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index c0d0266..68f5002 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -208,7 +208,11 @@
// TODO(b/110806787): Can be extended to handle collisions by renaming the given
// method.
if (usedSignatures.add(wrapper)) {
- clazz.setDirectMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
+ clazz.setDirectMethod(
+ i,
+ encodedMethod.toTypeSubstitutedMethod(
+ newMethod,
+ removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod)));
methodMapping.put(method, newMethod);
if (removedArgumentsInfo.hasRemovedArguments()) {
removedArgumentsInfoPerMethod.put(newMethod, removedArgumentsInfo);
@@ -229,6 +233,7 @@
DexMethod method = encodedMethod.method;
RewrittenPrototypeDescription prototypeChanges =
getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
+ RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
if (newMethod != method) {
Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -242,7 +247,11 @@
boolean signatureIsAvailable = usedSignatures.add(wrapper);
assert signatureIsAvailable;
- clazz.setVirtualMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
+ clazz.setVirtualMethod(
+ i,
+ encodedMethod.toTypeSubstitutedMethod(
+ newMethod,
+ removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod)));
methodMapping.put(method, newMethod);
}
}
@@ -252,6 +261,7 @@
DexMethod method = encodedMethod.method;
RewrittenPrototypeDescription prototypeChanges =
getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
+ RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
if (newMethod != method) {
Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -262,7 +272,11 @@
if (!methodPool.hasSeen(wrapper) && usedSignatures.add(wrapper)) {
methodPool.seen(wrapper);
- clazz.setVirtualMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
+ clazz.setVirtualMethod(
+ i,
+ encodedMethod.toTypeSubstitutedMethod(
+ newMethod,
+ removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod)));
methodMapping.put(method, newMethod);
boolean added =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index fe2c067..450a549 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -174,7 +174,8 @@
} while (!isMethodSignatureAvailable(newSignature));
markSignatureAsNoLongerUsed(method.method);
markSignatureAsUsed(newSignature);
- return method.toTypeSubstitutedMethod(newSignature);
+ return method.toTypeSubstitutedMethod(
+ newSignature, unused.createParameterAnnotationsRemover(method));
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanUtils.java b/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
index bacea6f..cc94778 100644
--- a/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
@@ -7,6 +7,10 @@
private static final Boolean[] VALUES = new Boolean[] { Boolean.TRUE, Boolean.FALSE };
+ public static int intValue(boolean value) {
+ return value ? 1 : 0;
+ }
+
public static Boolean[] values() {
return VALUES;
}
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index c91c288..2c2bcac 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -281,4 +281,12 @@
return new String(characters);
}
+
+ public static String times(String string, int count) {
+ StringBuilder builder = new StringBuilder();
+ while (--count >= 0) {
+ builder.append(string);
+ }
+ return builder.toString();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 7f06ea2..5900399c 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -183,19 +183,35 @@
}
public R8TestBuilder enableConstantArgumentAnnotations() {
- if (!enableConstantArgumentAnnotations) {
- enableConstantArgumentAnnotations = true;
- addInternalKeepRules(
- "-keepconstantarguments class * { @com.android.tools.r8.KeepConstantArguments *; }");
+ return enableConstantArgumentAnnotations(true);
+ }
+
+ public R8TestBuilder enableConstantArgumentAnnotations(boolean value) {
+ if (value) {
+ if (!enableConstantArgumentAnnotations) {
+ enableConstantArgumentAnnotations = true;
+ addInternalKeepRules(
+ "-keepconstantarguments class * { @com.android.tools.r8.KeepConstantArguments *; }");
+ }
+ } else {
+ assert !enableConstantArgumentAnnotations;
}
return self();
}
public R8TestBuilder enableUnusedArgumentAnnotations() {
- if (!enableUnusedArgumentAnnotations) {
- enableUnusedArgumentAnnotations = true;
- addInternalKeepRules(
- "-keepunusedarguments class * { @com.android.tools.r8.KeepUnusedArguments *; }");
+ return enableUnusedArgumentAnnotations(true);
+ }
+
+ public R8TestBuilder enableUnusedArgumentAnnotations(boolean value) {
+ if (value) {
+ if (!enableUnusedArgumentAnnotations) {
+ enableUnusedArgumentAnnotations = true;
+ addInternalKeepRules(
+ "-keepunusedarguments class * { @com.android.tools.r8.KeepUnusedArguments *; }");
+ }
+ } else {
+ assert !enableUnusedArgumentAnnotations;
}
return self();
}
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 0047eec..d6ac028 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -118,8 +118,12 @@
return self();
}
+ public T addKeepAttributes(String... attributes) {
+ return addKeepRules("-keepattributes " + String.join(",", attributes));
+ }
+
public T addKeepAllAttributes() {
- return addKeepRules("-keepattributes *");
+ return addKeepAttributes("*");
}
private static String getMethodLine(MethodReference method) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
new file mode 100644
index 0000000..a15b418
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
@@ -0,0 +1,232 @@
+// 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 {}
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
new file mode 100644
index 0000000..128a2ec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
@@ -0,0 +1,206 @@
+// 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.unusedarguments;
+
+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.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 UnusedAnnotatedArgumentsTest extends TestBase {
+
+ private final boolean keepUnusedArguments;
+ private final Backend backend;
+
+ @Parameters(name = "{1}, keep unused arguments: {0}")
+ public static List<Object[]> params() {
+ return buildParameters(BooleanUtils.values(), Backend.values());
+ }
+
+ public UnusedAnnotatedArgumentsTest(boolean keepUnusedArguments, Backend backend) {
+ this.keepUnusedArguments = keepUnusedArguments;
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(backend)
+ .addInnerClasses(UnusedAnnotatedArgumentsTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepClassRules(Used.class, Unused.class)
+ .addKeepAttributes("RuntimeVisibleParameterAnnotations")
+ .enableClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .enableUnusedArgumentAnnotations(keepUnusedArguments)
+ // TODO(b/123060011): Mapping not working in presence of unused argument removal.
+ .minification(keepUnusedArguments)
+ .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 usedClassSubject = inspector.clazz(Used.class);
+ assertThat(usedClassSubject, isPresent());
+
+ ClassSubject unusedClassSubject = inspector.clazz(Unused.class);
+ assertThat(unusedClassSubject, 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());
+
+ if (keepUnusedArguments || 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(
+ unusedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
+ } else {
+ assertEquals(
+ usedClassSubject.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(
+ usedClassSubject.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);
+ }
+
+ @KeepUnusedArguments
+ @NeverInline
+ static void testRemoveStaticFromStart(
+ @Unused String unused, @Used String used, @Used String otherUsed) {
+ System.out.println(used + otherUsed);
+ }
+
+ @KeepUnusedArguments
+ @NeverInline
+ static void testRemoveStaticFromMiddle(
+ @Used String used, @Unused String unused, @Used String otherUsed) {
+ System.out.println(used + otherUsed);
+ }
+
+ @KeepUnusedArguments
+ @NeverInline
+ static void testRemoveStaticFromEnd(
+ @Used String used, @Used String otherUsed, @Unused String unused) {
+ System.out.println(used + otherUsed);
+ }
+
+ @KeepUnusedArguments
+ @NeverInline
+ void testRemoveVirtualFromStart(
+ @Unused String unused, @Used String used, @Used String otherUsed) {
+ System.out.println(used + otherUsed);
+ }
+
+ @KeepUnusedArguments
+ @NeverInline
+ void testRemoveVirtualFromMiddle(
+ @Used String used, @Unused String unused, @Used String otherUsed) {
+ System.out.println(used + otherUsed);
+ }
+
+ @KeepUnusedArguments
+ @NeverInline
+ void testRemoveVirtualFromEnd(
+ @Used String used, @Used String otherUsed, @Unused String unused) {
+ System.out.println(used + otherUsed);
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.PARAMETER)
+ @interface Used {}
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.PARAMETER)
+ @interface Unused {}
+}