[KeepAnno] Support patterns on classes annotated by annotations
Bug: b/248408342
Change-Id: Icf922d12e77d22cd9f7a9836438a9a328862cd07
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
index 177990e..408abf2 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
@@ -81,6 +81,9 @@
* <li>instanceOfClassConstantExclusive
* <li>extendsClassName
* <li>extendsClassConstant
+ * <li>classAnnotatedByClassName
+ * <li>classAnnotatedByClassConstant
+ * <li>classAnnotatedByClassNamePattern
* </ul>
*
* <p>If none are specified the default is to match any class.
@@ -277,6 +280,60 @@
Class<?> extendsClassConstant() default Object.class;
/**
+ * Define the class-annotated-by pattern by fully qualified class name.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-annotated-by:
+ *
+ * <ul>
+ * <li>classAnnotatedByClassConstant
+ * <li>classAnnotatedByClassNamePattern
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class regardless of what the class is
+ * annotated by.
+ *
+ * @return The qualified class name that defines the annotation.
+ */
+ String classAnnotatedByClassName() default "";
+
+ /**
+ * Define the class-annotated-by pattern by reference to a Class constant.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-annotated-by:
+ *
+ * <ul>
+ * <li>classAnnotatedByClassName
+ * <li>classAnnotatedByClassNamePattern
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class regardless of what the class is
+ * annotated by.
+ *
+ * @return The class-constant that defines the annotation.
+ */
+ Class<?> classAnnotatedByClassConstant() default Object.class;
+
+ /**
+ * Define the class-annotated-by pattern by reference to a class-name pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-annotated-by:
+ *
+ * <ul>
+ * <li>classAnnotatedByClassName
+ * <li>classAnnotatedByClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class regardless of what the class is
+ * annotated by.
+ *
+ * @return The class-name pattern that defines the annotation.
+ */
+ ClassNamePattern classAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+
+ /**
* Define the member-access pattern by matching on access flags.
*
* <p>Mutually exclusive with all field and method properties as use restricts the match to both
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
index a35d08b..5737edf 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
@@ -43,6 +43,9 @@
* <li>instanceOfClassConstantExclusive
* <li>extendsClassName
* <li>extendsClassConstant
+ * <li>classAnnotatedByClassName
+ * <li>classAnnotatedByClassConstant
+ * <li>classAnnotatedByClassNamePattern
* </ul>
*
* <p>If none are specified the default is to match any class.
@@ -239,6 +242,60 @@
Class<?> extendsClassConstant() default Object.class;
/**
+ * Define the class-annotated-by pattern by fully qualified class name.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-annotated-by:
+ *
+ * <ul>
+ * <li>classAnnotatedByClassConstant
+ * <li>classAnnotatedByClassNamePattern
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class regardless of what the class is
+ * annotated by.
+ *
+ * @return The qualified class name that defines the annotation.
+ */
+ String classAnnotatedByClassName() default "";
+
+ /**
+ * Define the class-annotated-by pattern by reference to a Class constant.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-annotated-by:
+ *
+ * <ul>
+ * <li>classAnnotatedByClassName
+ * <li>classAnnotatedByClassNamePattern
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class regardless of what the class is
+ * annotated by.
+ *
+ * @return The class-constant that defines the annotation.
+ */
+ Class<?> classAnnotatedByClassConstant() default Object.class;
+
+ /**
+ * Define the class-annotated-by pattern by reference to a class-name pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-annotated-by:
+ *
+ * <ul>
+ * <li>classAnnotatedByClassName
+ * <li>classAnnotatedByClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class regardless of what the class is
+ * annotated by.
+ *
+ * @return The class-name pattern that defines the annotation.
+ */
+ ClassNamePattern classAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+
+ /**
* Define the member pattern in full by a reference to a binding.
*
* <p>Mutually exclusive with all other class and member pattern properties. When a member binding
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
index e2ebc3b..ed5e0ce 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
@@ -140,6 +140,9 @@
* <li>instanceOfClassConstantExclusive
* <li>extendsClassName
* <li>extendsClassConstant
+ * <li>classAnnotatedByClassName
+ * <li>classAnnotatedByClassConstant
+ * <li>classAnnotatedByClassNamePattern
* </ul>
*
* <p>If none are specified the default is to match any class.
@@ -336,6 +339,60 @@
Class<?> extendsClassConstant() default Object.class;
/**
+ * Define the class-annotated-by pattern by fully qualified class name.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-annotated-by:
+ *
+ * <ul>
+ * <li>classAnnotatedByClassConstant
+ * <li>classAnnotatedByClassNamePattern
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class regardless of what the class is
+ * annotated by.
+ *
+ * @return The qualified class name that defines the annotation.
+ */
+ String classAnnotatedByClassName() default "";
+
+ /**
+ * Define the class-annotated-by pattern by reference to a Class constant.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-annotated-by:
+ *
+ * <ul>
+ * <li>classAnnotatedByClassName
+ * <li>classAnnotatedByClassNamePattern
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class regardless of what the class is
+ * annotated by.
+ *
+ * @return The class-constant that defines the annotation.
+ */
+ Class<?> classAnnotatedByClassConstant() default Object.class;
+
+ /**
+ * Define the class-annotated-by pattern by reference to a class-name pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-annotated-by:
+ *
+ * <ul>
+ * <li>classAnnotatedByClassName
+ * <li>classAnnotatedByClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class regardless of what the class is
+ * annotated by.
+ *
+ * @return The class-name pattern that defines the annotation.
+ */
+ ClassNamePattern classAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+
+ /**
* Define the member pattern in full by a reference to a binding.
*
* <p>Mutually exclusive with all other class and member pattern properties. When a member binding
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index 91751e6..f25e60d 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -1085,7 +1085,8 @@
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
if (descriptor.equals(Target.DESCRIPTOR)) {
- return KeepTargetVisitor.create(parsingContext, builder::addTarget, bindingsHelper);
+ return KeepTargetVisitor.create(
+ parsingContext.annotation(descriptor), builder::addTarget, bindingsHelper);
}
return super.visitAnnotation(name, descriptor);
}
@@ -1403,6 +1404,7 @@
private KeepClassItemReference boundClassItemReference = null;
private final ClassNameParser classNameParser;
+ private final ClassNameParser annotatedByParser;
private final InstanceOfDeclaration instanceOfDeclaration;
private final List<Declaration<?>> declarations;
private final List<PropertyParser<?, ?>> parsers;
@@ -1415,7 +1417,14 @@
classNameParser.setProperty(Item.className, ClassNameProperty.NAME);
classNameParser.setProperty(Item.classConstant, ClassNameProperty.CONSTANT);
classNameParser.setProperty(Item.classNamePattern, ClassNameProperty.PATTERN);
- parsers = ImmutableList.of(classNameParser);
+
+ annotatedByParser = new ClassNameParser(parsingContext.group(Item.classAnnotatedByGroup));
+ annotatedByParser.setProperty(Item.classAnnotatedByClassName, ClassNameProperty.NAME);
+ annotatedByParser.setProperty(Item.classAnnotatedByClassConstant, ClassNameProperty.CONSTANT);
+ annotatedByParser.setProperty(
+ Item.classAnnotatedByClassNamePattern, ClassNameProperty.PATTERN);
+
+ parsers = ImmutableList.of(classNameParser, annotatedByParser);
instanceOfDeclaration = new InstanceOfDeclaration(parsingContext);
declarations = ImmutableList.of(instanceOfDeclaration);
@@ -1435,12 +1444,14 @@
return boundClassItemReference != null;
}
- private boolean classPatternsAreDefined() {
- return !classNameParser.isDefault() || !instanceOfDeclaration.isDefault();
+ private boolean classPatternsAreDeclared() {
+ return classNameParser.isDeclared()
+ || annotatedByParser.isDeclared()
+ || !instanceOfDeclaration.isDefault();
}
private void checkAllowedDefinitions() {
- if (isBindingReferenceDefined() && classPatternsAreDefined()) {
+ if (isBindingReferenceDefined() && classPatternsAreDeclared()) {
throw parsingContext.error(
"Cannot reference a class binding and class patterns for a single class item");
}
@@ -1456,10 +1467,12 @@
if (isBindingReferenceDefined()) {
return boundClassItemReference;
}
- if (classPatternsAreDefined()) {
+ if (classPatternsAreDeclared()) {
return KeepClassItemPattern.builder()
.setClassNamePattern(
classNameParser.getValueOrDefault(KeepQualifiedClassNamePattern.any()))
+ .setAnnotatedByPattern(
+ annotatedByParser.getValueOrDefault(KeepQualifiedClassNamePattern.any()))
.setInstanceOfPattern(instanceOfDeclaration.getValue())
.build()
.toClassItemReference();
@@ -2016,14 +2029,14 @@
private final KeepTarget.Builder builder = KeepTarget.builder();
static KeepTargetVisitor create(
- ParsingContext parsingContext,
+ AnnotationParsingContext parsingContext,
Parent<KeepTarget> parent,
UserBindingsHelper bindingsHelper) {
return new KeepTargetVisitor(parsingContext, parent, bindingsHelper);
}
private KeepTargetVisitor(
- ParsingContext parsingContext,
+ AnnotationParsingContext parsingContext,
Parent<KeepTarget> parent,
UserBindingsHelper bindingsHelper) {
super(parsingContext);
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
index f0bdccf..40010dc 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
@@ -81,6 +81,11 @@
"instanceOfClassConstantExclusive";
public static final String extendsClassName = "extendsClassName";
public static final String extendsClassConstant = "extendsClassConstant";
+ public static final String classAnnotatedByGroup = "class-annotated-by";
+ public static final String classAnnotatedByClassName = "classAnnotatedByClassName";
+ public static final String classAnnotatedByClassConstant = "classAnnotatedByClassConstant";
+ public static final String classAnnotatedByClassNamePattern =
+ "classAnnotatedByClassNamePattern";
public static final String memberAccess = "memberAccess";
public static final String methodAccess = "methodAccess";
public static final String methodNameGroup = "method-name";
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java
index 5c63faf..4736f07 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java
@@ -22,12 +22,14 @@
private KeepQualifiedClassNamePattern classNamePattern = KeepQualifiedClassNamePattern.any();
private KeepInstanceOfPattern instanceOfPattern = KeepInstanceOfPattern.any();
+ private KeepQualifiedClassNamePattern annotatedByPattern = KeepQualifiedClassNamePattern.any();
private Builder() {}
public Builder copyFrom(KeepClassItemPattern pattern) {
return setClassNamePattern(pattern.getClassNamePattern())
- .setInstanceOfPattern(pattern.getInstanceOfPattern());
+ .setInstanceOfPattern(pattern.getInstanceOfPattern())
+ .setAnnotatedByPattern(pattern.getAnnotatedByPattern());
}
public Builder setClassNamePattern(KeepQualifiedClassNamePattern classNamePattern) {
@@ -40,20 +42,30 @@
return this;
}
+ public Builder setAnnotatedByPattern(KeepQualifiedClassNamePattern annotatedByPattern) {
+ this.annotatedByPattern = annotatedByPattern;
+ return this;
+ }
+
public KeepClassItemPattern build() {
- return new KeepClassItemPattern(classNamePattern, instanceOfPattern);
+ return new KeepClassItemPattern(classNamePattern, instanceOfPattern, annotatedByPattern);
}
}
private final KeepQualifiedClassNamePattern classNamePattern;
private final KeepInstanceOfPattern instanceOfPattern;
+ private final KeepQualifiedClassNamePattern annotatedByPattern;
public KeepClassItemPattern(
- KeepQualifiedClassNamePattern classNamePattern, KeepInstanceOfPattern instanceOfPattern) {
+ KeepQualifiedClassNamePattern classNamePattern,
+ KeepInstanceOfPattern instanceOfPattern,
+ KeepQualifiedClassNamePattern annotatedByPattern) {
assert classNamePattern != null;
assert instanceOfPattern != null;
+ assert annotatedByPattern != null;
this.classNamePattern = classNamePattern;
this.instanceOfPattern = instanceOfPattern;
+ this.annotatedByPattern = annotatedByPattern;
}
@Override
@@ -83,8 +95,12 @@
return instanceOfPattern;
}
+ public KeepQualifiedClassNamePattern getAnnotatedByPattern() {
+ return annotatedByPattern;
+ }
+
public boolean isAny() {
- return classNamePattern.isAny() && instanceOfPattern.isAny();
+ return classNamePattern.isAny() && instanceOfPattern.isAny() && annotatedByPattern.isAny();
}
@Override
@@ -97,12 +113,13 @@
}
KeepClassItemPattern that = (KeepClassItemPattern) obj;
return classNamePattern.equals(that.classNamePattern)
- && instanceOfPattern.equals(that.instanceOfPattern);
+ && instanceOfPattern.equals(that.instanceOfPattern)
+ && annotatedByPattern.equals(that.annotatedByPattern);
}
@Override
public int hashCode() {
- return Objects.hash(classNamePattern, instanceOfPattern);
+ return Objects.hash(classNamePattern, instanceOfPattern, annotatedByPattern);
}
@Override
@@ -110,6 +127,8 @@
return "KeepClassItemPattern"
+ "{ class="
+ classNamePattern
+ + ", annotated-by="
+ + annotatedByPattern
+ ", instance-of="
+ instanceOfPattern
+ '}';
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
index 04485d7..450576c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -41,7 +41,9 @@
* MEMBER_ITEM_REFERENCE ::= MEMBER_BINDING_REFERENCE | MEMBER_ITEM_PATTERN
*
* ITEM_PATTERN ::= CLASS_ITEM_PATTERN | MEMBER_ITEM_PATTERN
- * CLASS_ITEM_PATTERN ::= class QUALIFIED_CLASS_NAME_PATTERN instance-of INSTANCE_OF_PATTERN
+ * CLASS_ITEM_PATTERN ::= class QUALIFIED_CLASS_NAME_PATTERN
+ * annotated-by ANNOTATED_BY_PATTERN
+ * instance-of INSTANCE_OF_PATTERN
* MEMBER_ITEM_PATTERN ::= CLASS_ITEM_REFERENCE { MEMBER_PATTERN }
*
* TYPE_PATTERN
@@ -65,6 +67,8 @@
* INSTANCE_OF_PATTERN_INCLUSIVE ::= QUALIFIED_CLASS_NAME_PATTERN
* INSTANCE_OF_PATTERN_EXCLUSIVE ::= QUALIFIED_CLASS_NAME_PATTERN
*
+ * ANNOTATED_BY_PATTERN ::= QUALIFIED_CLASS_NAME_PATTERN
+ *
* MEMBER_PATTERN ::= none | all | FIELD_PATTERN | METHOD_PATTERN
*
* FIELD_PATTERN
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
index 7737744..4f2ee93 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
@@ -98,6 +98,12 @@
StringBuilder builder,
KeepClassItemPattern classPattern,
BiConsumer<StringBuilder, KeepQualifiedClassNamePattern> printClassName) {
+ KeepQualifiedClassNamePattern annotatedByPattern = classPattern.getAnnotatedByPattern();
+ if (!annotatedByPattern.isAny()) {
+ builder.append("@");
+ printClassName(annotatedByPattern, RulePrinter.withoutBackReferences(builder));
+ builder.append(" ");
+ }
builder.append("class ");
printClassName.accept(builder, classPattern.getClassNamePattern());
KeepInstanceOfPattern extendsPattern = classPattern.getInstanceOfPattern();
diff --git a/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByPatternsTest.java
new file mode 100644
index 0000000..5897492
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByPatternsTest.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2024, 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.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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;
+
+@RunWith(Parameterized.class)
+public class ClassAnnotatedByPatternsTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("C1", "C2");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public ClassAnnotatedByPatternsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getInputClasses())
+ .setMinApi(parameters)
+ // TODO(b/248408342): Make this implicit when annotations are kept by the keep-annotation.
+ .addKeepRuntimeVisibleAnnotations()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(
+ TestClass.class, Reflector.class, A1.class, A2.class, C1.class, C2.class, C3.class);
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ // The class constant use will ensure the annotation remains.
+ assertThat(inspector.clazz(A1.class), isPresentAndRenamed());
+ // Nothing is using or keeping the second annotation.
+ assertThat(inspector.clazz(A2.class), isAbsent());
+ // The first two classes are annotated by A1 so the keep-annotation applies and must retain
+ // their name.
+ assertThat(inspector.clazz(C1.class), isPresentAndNotRenamed());
+ assertThat(inspector.clazz(C2.class), isPresentAndNotRenamed());
+ // The last class will remain due to the class constant, but it is optimized/renamed.
+ assertThat(inspector.clazz(C3.class), isPresentAndRenamed());
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A1 {}
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A2 {}
+
+ static class Reflector {
+
+ @UsesReflection(
+ @KeepTarget(
+ classAnnotatedByClassConstant = A1.class,
+ constraints = {KeepConstraint.ANNOTATIONS, KeepConstraint.NAME}))
+ public void foo(Class<?>... classes) throws Exception {
+ for (Class<?> clazz : classes) {
+ if (clazz.isAnnotationPresent(A1.class)) {
+ String typeName = clazz.getTypeName();
+ System.out.println(typeName.substring(typeName.lastIndexOf('$') + 1));
+ }
+ }
+ }
+ }
+
+ @A1
+ static class C1 {}
+
+ @A1
+ @A2
+ static class C2 {}
+
+ @A2
+ static class C3 {}
+
+ static class TestClass {
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new Reflector().foo(C1.class, C2.class, C3.class);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
index 61265cb..4b7c0c4 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -297,6 +297,7 @@
private static String CLASS_GROUP = "class";
private static String CLASS_NAME_GROUP = "class-name";
private static String INSTANCE_OF_GROUP = "instance-of";
+ private static String CLASS_ANNOTATED_BY_GROUP = "class-annotated-by";
private Group createDescriptionGroup() {
return new Group("description")
@@ -681,6 +682,44 @@
"If none are specified the default is to match any class instance.");
}
+ private GroupMember classAnnotatedByClassName() {
+ return new GroupMember("classAnnotatedByClassName")
+ .setDocTitle(
+ "Define the " + CLASS_ANNOTATED_BY_GROUP + " pattern by fully qualified class name.")
+ .setDocReturn("The qualified class name that defines the annotation.")
+ .defaultEmptyString();
+ }
+
+ private GroupMember classAnnotatedByClassConstant() {
+ return new GroupMember("classAnnotatedByClassConstant")
+ .setDocTitle(
+ "Define the "
+ + CLASS_ANNOTATED_BY_GROUP
+ + " pattern by reference to a Class constant.")
+ .setDocReturn("The class-constant that defines the annotation.")
+ .defaultObjectClass();
+ }
+
+ private GroupMember classAnnotatedByClassNamePattern() {
+ return new GroupMember("classAnnotatedByClassNamePattern")
+ .setDocTitle(
+ "Define the "
+ + CLASS_ANNOTATED_BY_GROUP
+ + " pattern by reference to a class-name pattern.")
+ .setDocReturn("The class-name pattern that defines the annotation.")
+ .defaultValue(ClassNamePattern.class, DEFAULT_INVALID_CLASS_NAME_PATTERN);
+ }
+
+ private Group createClassAnnotatedByPatternGroup() {
+ return new Group(CLASS_ANNOTATED_BY_GROUP)
+ .addMember(classAnnotatedByClassName())
+ .addMember(classAnnotatedByClassConstant())
+ .addMember(classAnnotatedByClassNamePattern())
+ .addDocFooterParagraph(
+ "If none are specified the default is to match any class "
+ + "regardless of what the class is annotated by.");
+ }
+
private Group createMemberBindingGroup() {
return new Group("member")
.allowMutuallyExclusiveWithOtherGroups()
@@ -870,7 +909,9 @@
Group bindingGroup = createClassBindingGroup();
Group classNameGroup = createClassNamePatternGroup();
Group classInstanceOfGroup = createClassInstanceOfPatternGroup();
- bindingGroup.addMutuallyExclusiveGroups(classNameGroup, classInstanceOfGroup);
+ Group classAnnotatedByGroup = createClassAnnotatedByPatternGroup();
+ bindingGroup.addMutuallyExclusiveGroups(
+ classNameGroup, classInstanceOfGroup, classAnnotatedByGroup);
bindingGroup.generate(this);
println();
@@ -878,6 +919,8 @@
println();
classInstanceOfGroup.generate(this);
println();
+ classAnnotatedByGroup.generate(this);
+ println();
}
// Member binding properties.
@@ -1434,6 +1477,7 @@
// Classes.
createClassNamePatternGroup().generateConstants(this);
createClassInstanceOfPatternGroup().generateConstants(this);
+ createClassAnnotatedByPatternGroup().generateConstants(this);
// Members.
createMemberAccessGroup().generateConstants(this);
// Methods.