[KeepAnno] Add annotation pattern constraints
With this CL retaining annotations can be done with patterns on names
and on the retention policy of each individual annotation.
For rule extraction these will unfold to fairly general keep rules
matching most annotations. Better shrinking can be done once native
interpretation is supported in the compiler.
Bug: b/319474935
Change-Id: Icb7d26606cb5d986f4d1150a79ed4231c6a9864e
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java
new file mode 100644
index 0000000..cfd4302
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java
@@ -0,0 +1,82 @@
+// 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See KeepItemAnnotationGenerator.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.keepanno.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A pattern structure for matching annotations.
+ *
+ * <p>If no properties are set, the default pattern matches any annotation with a runtime retention
+ * policy.
+ */
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface AnnotationPattern {
+
+ /**
+ * Define the annotation-name pattern by fully qualified class name.
+ *
+ * <p>Mutually exclusive with the following other properties defining annotation-name:
+ *
+ * <ul>
+ * <li>constant
+ * <li>namePattern
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any annotation name.
+ *
+ * @return The qualified class name that defines the annotation.
+ */
+ String name() default "";
+
+ /**
+ * Define the annotation-name pattern by reference to a {@code Class} constant.
+ *
+ * <p>Mutually exclusive with the following other properties defining annotation-name:
+ *
+ * <ul>
+ * <li>name
+ * <li>namePattern
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any annotation name.
+ *
+ * @return The Class constant that defines the annotation.
+ */
+ Class<?> constant() default Object.class;
+
+ /**
+ * Define the annotation-name pattern by reference to a class-name pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining annotation-name:
+ *
+ * <ul>
+ * <li>name
+ * <li>constant
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any annotation name.
+ *
+ * @return The class-name pattern that defines the annotation.
+ */
+ ClassNamePattern namePattern() default @ClassNamePattern(simpleName = "");
+
+ /**
+ * Specify which retention policies must be set for the annotations.
+ *
+ * <p>Matches annotations with matching retention policies
+ *
+ * @return Retention policies. By default {@code RetentionPolicy.RUNTIME}.
+ */
+ RetentionPolicy[] retention() default {RetentionPolicy.RUNTIME};
+}
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 bf9251b..e5be58a 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
@@ -110,6 +110,28 @@
KeepConstraint[] constraintAdditions() default {};
/**
+ * Patterns for annotations that must remain on the item.
+ *
+ * <p>The annotations matching any of the patterns must remain on the item if the annotation types
+ * remain in the program.
+ *
+ * <p>Note that if the annotation types themselves are unused/removed, then their references on
+ * the item will be removed too. If the annotation types themselves are used reflectively then
+ * they too need a keep annotation or rule to ensure they remain in the program.
+ *
+ * <p>Setting this to a non-empty array implicitly includes {@link KeepConstraint#ANNOTATIONS} in
+ * the set of active constraints.
+ *
+ * <p>By default no annotation patterns are defined and no annotations are required to remain,
+ * unless constraints explicitly includes {@link KeepConstraint#ANNOTATIONS} in that case the
+ * default pattern is the default {@link AnnotationPattern} which matches all annotations with
+ * retention policy {@code RetentionPolicy#RUNTIME}
+ *
+ * @return Annotation patterns
+ */
+ AnnotationPattern[] constrainAnnotations() default {};
+
+ /**
* Define the class pattern by reference to a binding.
*
* <p>Mutually exclusive with the following other properties defining class:
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
index 6157cbd..f81d218 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
@@ -132,6 +132,28 @@
KeepConstraint[] constraintAdditions() default {};
/**
+ * Patterns for annotations that must remain on the item.
+ *
+ * <p>The annotations matching any of the patterns must remain on the item if the annotation types
+ * remain in the program.
+ *
+ * <p>Note that if the annotation types themselves are unused/removed, then their references on
+ * the item will be removed too. If the annotation types themselves are used reflectively then
+ * they too need a keep annotation or rule to ensure they remain in the program.
+ *
+ * <p>Setting this to a non-empty array implicitly includes {@link KeepConstraint#ANNOTATIONS} in
+ * the set of active constraints.
+ *
+ * <p>By default no annotation patterns are defined and no annotations are required to remain,
+ * unless constraints explicitly includes {@link KeepConstraint#ANNOTATIONS} in that case the
+ * default pattern is the default {@link AnnotationPattern} which matches all annotations with
+ * retention policy {@code RetentionPolicy#RUNTIME}
+ *
+ * @return Annotation patterns
+ */
+ AnnotationPattern[] constrainAnnotations() default {};
+
+ /**
* Define the member-annotated-by pattern by fully qualified class name.
*
* <p>Mutually exclusive with the following other properties defining member-annotated-by:
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
index 17add2d..a64c7c8 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
@@ -132,6 +132,28 @@
KeepConstraint[] constraintAdditions() default {};
/**
+ * Patterns for annotations that must remain on the item.
+ *
+ * <p>The annotations matching any of the patterns must remain on the item if the annotation types
+ * remain in the program.
+ *
+ * <p>Note that if the annotation types themselves are unused/removed, then their references on
+ * the item will be removed too. If the annotation types themselves are used reflectively then
+ * they too need a keep annotation or rule to ensure they remain in the program.
+ *
+ * <p>Setting this to a non-empty array implicitly includes {@link KeepConstraint#ANNOTATIONS} in
+ * the set of active constraints.
+ *
+ * <p>By default no annotation patterns are defined and no annotations are required to remain,
+ * unless constraints explicitly includes {@link KeepConstraint#ANNOTATIONS} in that case the
+ * default pattern is the default {@link AnnotationPattern} which matches all annotations with
+ * retention policy {@code RetentionPolicy#RUNTIME}
+ *
+ * @return Annotation patterns
+ */
+ AnnotationPattern[] constrainAnnotations() default {};
+
+ /**
* Define the member-annotated-by pattern by fully qualified class name.
*
* <p>Mutually exclusive with the following other properties defining member-annotated-by:
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationPatternParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationPatternParser.java
new file mode 100644
index 0000000..f10f9cd
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationPatternParser.java
@@ -0,0 +1,127 @@
+// 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.asm;
+
+import com.android.tools.r8.keepanno.asm.ClassNameParser.ClassNameProperty;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.AnnotationPattern;
+import com.android.tools.r8.keepanno.ast.KeepAnnotationPattern;
+import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.keepanno.ast.ParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
+import com.google.common.collect.ImmutableList;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.function.Consumer;
+import org.objectweb.asm.AnnotationVisitor;
+
+public class AnnotationPatternParser
+ extends PropertyParserBase<KeepAnnotationPattern, AnnotationPatternParser.AnnotationProperty> {
+
+ public enum AnnotationProperty {
+ PATTERN
+ }
+
+ public AnnotationPatternParser(ParsingContext parsingContext) {
+ super(parsingContext);
+ }
+
+ @Override
+ AnnotationVisitor tryPropertyAnnotation(
+ AnnotationProperty property,
+ String name,
+ String descriptor,
+ Consumer<KeepAnnotationPattern> setValue) {
+ switch (property) {
+ case PATTERN:
+ AnnotationParsingContext parsingContext =
+ getParsingContext().property(name).annotation(descriptor);
+ AnnotationDeclarationParser parser = new AnnotationDeclarationParser(parsingContext);
+ return new ParserVisitor(
+ parsingContext,
+ parser,
+ () ->
+ setValue.accept(
+ parser.isDeclared()
+ ? parser.getValue()
+ : KeepAnnotationPattern.anyWithRuntimeRetention()));
+ default:
+ return super.tryPropertyAnnotation(property, name, descriptor, setValue);
+ }
+ }
+
+ private enum RetentionProperty {
+ RETENTION
+ }
+
+ private static class RetentionParser
+ extends PropertyParserBase<RetentionPolicy, RetentionProperty> {
+
+ private static final String RETENTION_POLICY_DESC = "Ljava/lang/annotation/RetentionPolicy;";
+
+ public RetentionParser(ParsingContext parsingContext) {
+ super(parsingContext);
+ }
+
+ @Override
+ public boolean tryPropertyEnum(
+ RetentionProperty property,
+ String name,
+ String descriptor,
+ String value,
+ Consumer<RetentionPolicy> setValue) {
+ assert property == RetentionProperty.RETENTION;
+ if (RETENTION_POLICY_DESC.equals(descriptor)) {
+ setValue.accept(RetentionPolicy.valueOf(value));
+ return true;
+ }
+ return super.tryPropertyEnum(property, name, descriptor, value, setValue);
+ }
+ }
+
+ private static class AnnotationDeclarationParser
+ extends DeclarationParser<KeepAnnotationPattern> {
+
+ private final ClassNameParser nameParser;
+ private final ArrayPropertyParser<RetentionPolicy, RetentionProperty> retentionParser;
+ private final List<Parser<?>> parsers;
+
+ public AnnotationDeclarationParser(ParsingContext parsingContext) {
+ nameParser = new ClassNameParser(parsingContext);
+ nameParser.setProperty(AnnotationPattern.name, ClassNameProperty.NAME);
+ nameParser.setProperty(AnnotationPattern.constant, ClassNameProperty.CONSTANT);
+ nameParser.setProperty(AnnotationPattern.namePattern, ClassNameProperty.PATTERN);
+ retentionParser = new ArrayPropertyParser<>(parsingContext, RetentionParser::new);
+ retentionParser.setProperty(AnnotationPattern.retention, RetentionProperty.RETENTION);
+ retentionParser.setValueCheck(
+ (value, propertyContext) -> {
+ if (value.isEmpty()) {
+ throw propertyContext.error("Expected non-empty array of retention policies");
+ }
+ });
+ parsers = ImmutableList.of(nameParser, retentionParser);
+ }
+
+ @Override
+ List<Parser<?>> parsers() {
+ return parsers;
+ }
+
+ public KeepAnnotationPattern getValue() {
+ if (isDefault()) {
+ return null;
+ }
+ KeepAnnotationPattern.Builder builder = KeepAnnotationPattern.builder();
+ if (retentionParser.isDeclared()) {
+ List<RetentionPolicy> policies = retentionParser.getValue();
+ policies.forEach(builder::addRetentionPolicy);
+ } else {
+ builder.addRetentionPolicy(RetentionPolicy.RUNTIME);
+ }
+ return builder
+ .setNamePattern(nameParser.getValueOrDefault(KeepQualifiedClassNamePattern.any()))
+ .build();
+ }
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ArrayPropertyParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ArrayPropertyParser.java
index 3c6cd08..3ae1bd2 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ArrayPropertyParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ArrayPropertyParser.java
@@ -26,6 +26,7 @@
AnnotationVisitor tryPropertyArray(P property, String name, Consumer<List<T>> setValue) {
// The property name and type is forwarded to the element parser.
values = new ArrayList<>();
+ // The context is explicitly *not* extended with the property name here as it is forwarded.
ParsingContext parsingContext = getParsingContext();
return new AnnotationVisitorBase(parsingContext) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
index 9137b3e..6bc17aa 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
@@ -89,7 +89,6 @@
nameParser.setProperty(ClassNamePattern.simpleName, ClassSimpleNameProperty.NAME);
return new ParserVisitor(
parsingContext,
- descriptor,
ImmutableList.of(packageParser, nameParser),
() ->
setValue.accept(
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintsParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintPropertiesParser.java
similarity index 81%
rename from src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintsParser.java
rename to src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintPropertiesParser.java
index e89279b..c652e91 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintsParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintPropertiesParser.java
@@ -4,21 +4,22 @@
package com.android.tools.r8.keepanno.asm;
-import com.android.tools.r8.keepanno.asm.ConstraintsParser.ConstraintsProperty;
+import com.android.tools.r8.keepanno.asm.ConstraintPropertiesParser.ConstraintsProperty;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
import com.android.tools.r8.keepanno.ast.KeepConstraints;
import com.android.tools.r8.keepanno.ast.ParsingContext;
import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
-public class ConstraintsParser extends PropertyParserBase<KeepConstraints, ConstraintsProperty> {
+public class ConstraintPropertiesParser
+ extends PropertyParserBase<KeepConstraints, ConstraintsProperty> {
public enum ConstraintsProperty {
CONSTRAINTS,
ADDITIONS
}
- public ConstraintsParser(ParsingContext parsingContext) {
+ public ConstraintPropertiesParser(ParsingContext parsingContext) {
super(parsingContext.group(Target.constraintsGroup));
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepConstraintsVisitor.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepConstraintsVisitor.java
index b0b58f8..d044892 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepConstraintsVisitor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepConstraintsVisitor.java
@@ -68,7 +68,7 @@
builder.add(KeepConstraint.classOpenHierarchy());
break;
case Constraints.ANNOTATIONS:
- builder.add(KeepConstraint.annotationsAll());
+ builder.add(KeepConstraint.annotationsAllWithRuntimeRetention());
break;
default:
super.visitEnum(ignore, descriptor, value);
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 8c85b9a..36c6de6 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
@@ -4,7 +4,7 @@
package com.android.tools.r8.keepanno.asm;
import com.android.tools.r8.keepanno.asm.ClassNameParser.ClassNameProperty;
-import com.android.tools.r8.keepanno.asm.ConstraintsParser.ConstraintsProperty;
+import com.android.tools.r8.keepanno.asm.ConstraintPropertiesParser.ConstraintsProperty;
import com.android.tools.r8.keepanno.asm.InstanceOfParser.InstanceOfProperties;
import com.android.tools.r8.keepanno.asm.StringPatternParser.StringProperty;
import com.android.tools.r8.keepanno.asm.TypeParser.TypeProperty;
@@ -21,6 +21,7 @@
import com.android.tools.r8.keepanno.ast.AnnotationConstants.MethodAccess;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsedByReflection;
+import com.android.tools.r8.keepanno.ast.KeepAnnotationPattern;
import com.android.tools.r8.keepanno.ast.KeepBindingReference;
import com.android.tools.r8.keepanno.ast.KeepBindings;
import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
@@ -30,6 +31,7 @@
import com.android.tools.r8.keepanno.ast.KeepClassItemReference;
import com.android.tools.r8.keepanno.ast.KeepCondition;
import com.android.tools.r8.keepanno.ast.KeepConsequences;
+import com.android.tools.r8.keepanno.ast.KeepConstraint;
import com.android.tools.r8.keepanno.ast.KeepConstraints;
import com.android.tools.r8.keepanno.ast.KeepDeclaration;
import com.android.tools.r8.keepanno.ast.KeepEdge;
@@ -61,6 +63,7 @@
import com.android.tools.r8.keepanno.ast.ParsingContext.ClassParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.FieldParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.MethodParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.PropertyParsingContext;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -521,16 +524,17 @@
@Override
public AnnotationVisitor visitArray(String name) {
+ PropertyParsingContext propertyParsingContext = parsingContext.property(name);
if (name.equals(Edge.bindings)) {
- return new KeepBindingsVisitor(parsingContext, bindingsHelper);
+ return new KeepBindingsVisitor(propertyParsingContext, bindingsHelper);
}
if (name.equals(Edge.preconditions)) {
return new KeepPreconditionsVisitor(
- parsingContext, builder::setPreconditions, bindingsHelper);
+ propertyParsingContext, builder::setPreconditions, bindingsHelper);
}
if (name.equals(Edge.consequences)) {
return new KeepConsequencesVisitor(
- parsingContext, builder::setConsequences, bindingsHelper);
+ propertyParsingContext, builder::setConsequences, bindingsHelper);
}
return super.visitArray(name);
}
@@ -593,7 +597,7 @@
public AnnotationVisitor visitArray(String name) {
if (name.equals(ForApi.additionalTargets)) {
return new KeepConsequencesVisitor(
- parsingContext,
+ parsingContext.property(name),
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
@@ -695,7 +699,7 @@
public AnnotationVisitor visitArray(String name) {
if (name.equals(ForApi.additionalTargets)) {
return new KeepConsequencesVisitor(
- parsingContext,
+ parsingContext.property(name),
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
@@ -715,6 +719,71 @@
}
}
+ public static class ConstraintDeclarationParser extends DeclarationParser<KeepConstraints> {
+ private final ConstraintPropertiesParser constraintsParser;
+ private final ArrayPropertyParser<
+ KeepAnnotationPattern, AnnotationPatternParser.AnnotationProperty>
+ annotationsParser;
+
+ public ConstraintDeclarationParser(ParsingContext parsingContext) {
+ constraintsParser = new ConstraintPropertiesParser(parsingContext);
+ constraintsParser.setProperty(Target.constraints, ConstraintsProperty.CONSTRAINTS);
+ constraintsParser.setProperty(Target.constraintAdditions, ConstraintsProperty.ADDITIONS);
+
+ annotationsParser = new ArrayPropertyParser<>(parsingContext, AnnotationPatternParser::new);
+ annotationsParser.setProperty(
+ Target.constrainAnnotations, AnnotationPatternParser.AnnotationProperty.PATTERN);
+ annotationsParser.setValueCheck(this::verifyAnnotationList);
+ }
+
+ private void verifyAnnotationList(
+ List<KeepAnnotationPattern> annotationList, ParsingContext parsingContext) {
+ if (annotationList.isEmpty()) {
+ throw parsingContext.error("Expected non-empty array of annotation patterns");
+ }
+ }
+
+ @Override
+ List<Parser<?>> parsers() {
+ return ImmutableList.of(constraintsParser, annotationsParser);
+ }
+
+ public KeepConstraints getValueOrDefault(KeepConstraints defaultValue) {
+ return isDeclared() ? getValue() : defaultValue;
+ }
+
+ public KeepConstraints getValue() {
+ if (isDefault()) {
+ return null;
+ }
+ // If only the annotations are set, add them as an extension of the defaults.
+ if (constraintsParser.isDefault()) {
+ assert annotationsParser.isDeclared();
+ KeepConstraints.Builder builder = KeepConstraints.builder();
+ annotationsParser
+ .getValue()
+ .forEach(pattern -> builder.add(KeepConstraint.annotation(pattern)));
+ return KeepConstraints.defaultAdditions(builder.build());
+ }
+ // If only the constraints are set then those are the constraints as is.
+ if (annotationsParser.isDefault()) {
+ assert constraintsParser.isDeclared();
+ return constraintsParser.getValue();
+ }
+ // Finally if both are set, the explicit annotations override the constraint set.
+ // Filter out the default and add all the explicit patterns.
+ assert constraintsParser.isDeclared();
+ assert annotationsParser.isDeclared();
+ KeepConstraints.Builder builder =
+ KeepConstraints.builder().copyFrom(constraintsParser.getValue()).removeAnnotations();
+ List<KeepAnnotationPattern> annotationPatterns = annotationsParser.getValue();
+ for (KeepAnnotationPattern pattern : annotationPatterns) {
+ builder.add(KeepConstraint.annotation(pattern));
+ }
+ return builder.build();
+ }
+ }
+
/**
* Parsing of @UsedByReflection or @UsedByNative on a class context.
*
@@ -731,7 +800,7 @@
private final KeepConsequences.Builder consequences = KeepConsequences.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
- private final ConstraintsParser constraintsParser;
+ private final ConstraintDeclarationParser constraintsParser;
UsedByReflectionClassVisitor(
AnnotationParsingContext parsingContext,
@@ -743,11 +812,9 @@
this.className = className;
this.parent = parent;
addContext.accept(metaInfoBuilder);
+ constraintsParser = new ConstraintDeclarationParser(parsingContext);
// The class context/holder is the annotated class.
visit(Item.className, className);
- constraintsParser = new ConstraintsParser(parsingContext);
- constraintsParser.setProperty(Target.constraints, ConstraintsProperty.CONSTRAINTS);
- constraintsParser.setProperty(Target.constraintAdditions, ConstraintsProperty.ADDITIONS);
}
@Override
@@ -766,13 +833,14 @@
@Override
public AnnotationVisitor visitArray(String name) {
+ PropertyParsingContext propertyParsingContext = parsingContext.property(name);
if (name.equals(Edge.preconditions)) {
return new KeepPreconditionsVisitor(
- parsingContext, builder::setPreconditions, bindingsHelper);
+ propertyParsingContext, builder::setPreconditions, bindingsHelper);
}
if (name.equals(UsedByReflection.additionalTargets)) {
return new KeepConsequencesVisitor(
- parsingContext,
+ propertyParsingContext,
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
@@ -842,7 +910,7 @@
private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
private final KeepConsequences.Builder consequences = KeepConsequences.builder();
private ItemKind kind = KeepEdgeReader.ItemKind.ONLY_MEMBERS;
- private final ConstraintsParser constraintsParser;
+ private final ConstraintDeclarationParser constraintsParser;
UsedByReflectionMemberVisitor(
AnnotationParsingContext parsingContext,
@@ -854,9 +922,7 @@
this.parent = parent;
this.context = context;
addContext.accept(metaInfoBuilder);
- constraintsParser = new ConstraintsParser(parsingContext);
- constraintsParser.setProperty(Target.constraints, ConstraintsProperty.CONSTRAINTS);
- constraintsParser.setProperty(Target.constraintAdditions, ConstraintsProperty.ADDITIONS);
+ constraintsParser = new ConstraintDeclarationParser(parsingContext);
}
@Override
@@ -883,13 +949,14 @@
@Override
public AnnotationVisitor visitArray(String name) {
+ PropertyParsingContext propertyParsingContext = parsingContext.property(name);
if (name.equals(Edge.preconditions)) {
return new KeepPreconditionsVisitor(
- parsingContext, builder::setPreconditions, bindingsHelper);
+ propertyParsingContext, builder::setPreconditions, bindingsHelper);
}
if (name.equals(UsedByReflection.additionalTargets)) {
return new KeepConsequencesVisitor(
- parsingContext,
+ propertyParsingContext,
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
@@ -973,13 +1040,14 @@
@Override
public AnnotationVisitor visitArray(String name) {
+ PropertyParsingContext propertyParsingContext = parsingContext.property(name);
if (name.equals(AnnotationConstants.UsesReflection.value)) {
return new KeepConsequencesVisitor(
- parsingContext, builder::setConsequences, bindingsHelper);
+ propertyParsingContext, builder::setConsequences, bindingsHelper);
}
if (name.equals(AnnotationConstants.UsesReflection.additionalPreconditions)) {
return new KeepPreconditionsVisitor(
- parsingContext,
+ propertyParsingContext,
additionalPreconditions -> {
additionalPreconditions.forEach(preconditions::addCondition);
},
@@ -1003,7 +1071,7 @@
private final ParsingContext parsingContext;
private final UserBindingsHelper helper;
- public KeepBindingsVisitor(ParsingContext parsingContext, UserBindingsHelper helper) {
+ public KeepBindingsVisitor(PropertyParsingContext parsingContext, UserBindingsHelper helper) {
super(parsingContext);
this.parsingContext = parsingContext;
this.helper = helper;
@@ -1011,6 +1079,7 @@
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ assert name == null;
if (descriptor.equals(AnnotationConstants.Binding.DESCRIPTOR)) {
return new KeepBindingVisitor(parsingContext.annotation(descriptor), helper);
}
@@ -1025,7 +1094,7 @@
private final UserBindingsHelper bindingsHelper;
public KeepPreconditionsVisitor(
- ParsingContext parsingContext,
+ PropertyParsingContext parsingContext,
Parent<KeepPreconditions> parent,
UserBindingsHelper bindingsHelper) {
super(parsingContext);
@@ -1036,6 +1105,7 @@
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ assert name == null;
if (descriptor.equals(Condition.DESCRIPTOR)) {
return new KeepConditionVisitor(
parsingContext.annotation(descriptor), builder::addCondition, bindingsHelper);
@@ -1056,7 +1126,7 @@
private final UserBindingsHelper bindingsHelper;
public KeepConsequencesVisitor(
- ParsingContext parsingContext,
+ PropertyParsingContext parsingContext,
Parent<KeepConsequences> parent,
UserBindingsHelper bindingsHelper) {
super(parsingContext);
@@ -1067,6 +1137,7 @@
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ assert name == null;
if (descriptor.equals(Target.DESCRIPTOR)) {
return KeepTargetVisitor.create(
parsingContext.annotation(descriptor), builder::addTarget, bindingsHelper);
@@ -1786,7 +1857,7 @@
private final Parent<KeepTarget> parent;
private final UserBindingsHelper bindingsHelper;
- private final ConstraintsParser optionsParser;
+ private final ConstraintDeclarationParser constraintsParser;
private final KeepTarget.Builder builder = KeepTarget.builder();
static KeepTargetVisitor create(
@@ -1803,9 +1874,7 @@
super(parsingContext);
this.parent = parent;
this.bindingsHelper = bindingsHelper;
- optionsParser = new ConstraintsParser(parsingContext);
- optionsParser.setProperty(Target.constraints, ConstraintsProperty.CONSTRAINTS);
- optionsParser.setProperty(Target.constraintAdditions, ConstraintsProperty.ADDITIONS);
+ constraintsParser = new ConstraintDeclarationParser(parsingContext);
}
@Override
@@ -1815,7 +1884,7 @@
@Override
public AnnotationVisitor visitArray(String name) {
- AnnotationVisitor visitor = optionsParser.tryParseArray(name, unused -> {});
+ AnnotationVisitor visitor = constraintsParser.tryParseArray(name, unused -> {});
if (visitor != null) {
return visitor;
}
@@ -1825,7 +1894,8 @@
@Override
public void visitEnd() {
super.visitEnd();
- builder.setConstraints(optionsParser.getValueOrDefault(KeepConstraints.defaultConstraints()));
+ builder.setConstraints(
+ constraintsParser.getValueOrDefault(KeepConstraints.defaultConstraints()));
for (KeepItemReference item : getItemsWithBinding()) {
parent.accept(builder.setItemReference(item).build());
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java
index 5f1f69b..5acb59f 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java
@@ -12,33 +12,26 @@
/** Convert parser(s) into an annotation visitor. */
public class ParserVisitor extends AnnotationVisitorBase {
- private final List<PropertyParser<?, ?>> parsers;
+ private final List<Parser<?>> parsers;
private final Runnable onVisitEnd;
public ParserVisitor(
- AnnotationParsingContext parsingContext,
- String annotationDescriptor,
- List<PropertyParser<?, ?>> parsers,
- Runnable onVisitEnd) {
+ AnnotationParsingContext parsingContext, List<Parser<?>> parsers, Runnable onVisitEnd) {
super(parsingContext);
this.parsers = parsers;
this.onVisitEnd = onVisitEnd;
- assert annotationDescriptor.equals(parsingContext.getAnnotationDescriptor());
}
public ParserVisitor(
- AnnotationParsingContext parsingContext,
- String annotationDescriptor,
- PropertyParser<?, ?> declaration,
- Runnable onVisitEnd) {
- this(parsingContext, annotationDescriptor, Collections.singletonList(declaration), onVisitEnd);
+ AnnotationParsingContext parsingContext, Parser<?> parser, Runnable onVisitEnd) {
+ this(parsingContext, Collections.singletonList(parser), onVisitEnd);
}
private <T> void ignore(T unused) {}
@Override
public void visit(String name, Object value) {
- for (PropertyParser<?, ?> parser : parsers) {
+ for (Parser<?> parser : parsers) {
if (parser.tryParse(name, value, this::ignore)) {
return;
}
@@ -48,7 +41,7 @@
@Override
public AnnotationVisitor visitArray(String name) {
- for (PropertyParser<?, ?> parser : parsers) {
+ for (Parser<?> parser : parsers) {
AnnotationVisitor visitor = parser.tryParseArray(name, this::ignore);
if (visitor != null) {
return visitor;
@@ -59,7 +52,7 @@
@Override
public void visitEnum(String name, String descriptor, String value) {
- for (PropertyParser<?, ?> parser : parsers) {
+ for (Parser<?> parser : parsers) {
if (parser.tryParseEnum(name, descriptor, value, this::ignore)) {
return;
}
@@ -69,7 +62,7 @@
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
- for (PropertyParser<?, ?> parser : parsers) {
+ for (Parser<?> parser : parsers) {
AnnotationVisitor visitor = parser.tryParseAnnotation(name, descriptor, this::ignore);
if (visitor != null) {
return visitor;
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java
index 0213ef3..f5b40c3 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.keepanno.ast.ParsingContext;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
@@ -18,6 +19,7 @@
private final Map<String, P> mapping = new HashMap<>();
private String resultPropertyName = null;
private T resultValue = null;
+ private BiConsumer<T, ParsingContext> check = null;
protected PropertyParserBase(ParsingContext parsingContext) {
this.parsingContext = parsingContext;
@@ -52,6 +54,9 @@
private Consumer<T> wrap(String propertyName, Consumer<T> setValue) {
return value -> {
assert value != null;
+ if (check != null) {
+ check.accept(value, parsingContext.property(propertyName));
+ }
if (resultPropertyName != null) {
assert resultValue != null;
error(propertyName);
@@ -144,4 +149,8 @@
}
return null;
}
+
+ public void setValueCheck(BiConsumer<T, ParsingContext> check) {
+ this.check = check;
+ }
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/StringPatternParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/StringPatternParser.java
index f866a90..2bb95df 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/StringPatternParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/StringPatternParser.java
@@ -57,7 +57,6 @@
suffixParser.setProperty(StringPattern.endsWith, StringParser.Property.STRING);
return new ParserVisitor(
parsingContext,
- descriptor,
ImmutableList.of(exactParser, prefixParser, suffixParser),
() -> {
if (exactParser.isDeclared()) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
index a40f450..a0c0afc 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
@@ -58,7 +58,6 @@
typeParser.setProperty(TypePattern.classNamePattern, TypeProperty.CLASS_NAME_PATTERN);
return new ParserVisitor(
context,
- descriptor,
typeParser,
() -> setValue.accept(typeParser.getValueOrDefault(KeepTypePattern.any())));
}
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 9828d43..a2249c5 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
@@ -139,6 +139,7 @@
public static final String constraintsGroup = "constraints";
public static final String constraints = "constraints";
public static final String constraintAdditions = "constraintAdditions";
+ public static final String constrainAnnotations = "constrainAnnotations";
}
public static final class Kind {
@@ -236,4 +237,14 @@
public static final String simpleName = "simpleName";
public static final String packageName = "packageName";
}
+
+ public static final class AnnotationPattern {
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/AnnotationPattern;";
+ public static final String annotationNameGroup = "annotation-name";
+ public static final String name = "name";
+ public static final String constant = "constant";
+ public static final String namePattern = "namePattern";
+ public static final String retention = "retention";
+ }
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationPattern.java
new file mode 100644
index 0000000..5c7cf96
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationPattern.java
@@ -0,0 +1,138 @@
+// 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.ast;
+
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+public class KeepAnnotationPattern {
+
+ private static final int RUNTIME_RETENTION_MASK = 0x1;
+ private static final int CLASS_RETENTION_MASK = 0x2;
+ private static final int ANY_RETENTION_MASK = 0x3;
+
+ private static final KeepAnnotationPattern ANY_WITH_ANY_RETENTION =
+ new KeepAnnotationPattern(KeepQualifiedClassNamePattern.any(), ANY_RETENTION_MASK);
+
+ private static final KeepAnnotationPattern ANY_WITH_RUNTIME_RETENTION =
+ new KeepAnnotationPattern(KeepQualifiedClassNamePattern.any(), RUNTIME_RETENTION_MASK);
+
+ private static final KeepAnnotationPattern ANY_WITH_CLASS_RETENTION =
+ new KeepAnnotationPattern(KeepQualifiedClassNamePattern.any(), CLASS_RETENTION_MASK);
+
+ public static KeepAnnotationPattern any() {
+ return KeepAnnotationPattern.ANY_WITH_ANY_RETENTION;
+ }
+
+ public static KeepAnnotationPattern anyWithRuntimeRetention() {
+ return KeepAnnotationPattern.ANY_WITH_RUNTIME_RETENTION;
+ }
+
+ public static KeepAnnotationPattern anyWithClassRetention() {
+ return KeepAnnotationPattern.ANY_WITH_CLASS_RETENTION;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private KeepQualifiedClassNamePattern namePattern = KeepQualifiedClassNamePattern.any();
+ private int retentionPolicies = 0x0;
+
+ private Builder() {}
+
+ public Builder setNamePattern(KeepQualifiedClassNamePattern namePattern) {
+ this.namePattern = namePattern;
+ return this;
+ }
+
+ public Builder addRetentionPolicy(RetentionPolicy policy) {
+ switch (policy) {
+ case RUNTIME:
+ retentionPolicies |= RUNTIME_RETENTION_MASK;
+ break;
+ case CLASS:
+ retentionPolicies |= CLASS_RETENTION_MASK;
+ break;
+ case SOURCE:
+ throw new KeepEdgeException("Retention policy SOURCE cannot be used in patterns");
+ default:
+ throw new KeepEdgeException("Invalid policy: " + policy);
+ }
+ return this;
+ }
+
+ public KeepAnnotationPattern build() {
+ if (retentionPolicies == 0x0) {
+ throw new KeepEdgeException("Invalid empty retention policy");
+ }
+ if (namePattern.isAny()) {
+ switch (retentionPolicies) {
+ case RUNTIME_RETENTION_MASK:
+ return ANY_WITH_RUNTIME_RETENTION;
+ case CLASS_RETENTION_MASK:
+ return ANY_WITH_CLASS_RETENTION;
+ case ANY_RETENTION_MASK:
+ return ANY_WITH_ANY_RETENTION;
+ default:
+ throw new KeepEdgeException("Invalid retention policy value: " + retentionPolicies);
+ }
+ }
+ return new KeepAnnotationPattern(namePattern, retentionPolicies);
+ }
+ }
+
+ private final KeepQualifiedClassNamePattern namePattern;
+ private final int retentionPolicies;
+
+ private KeepAnnotationPattern(KeepQualifiedClassNamePattern namePattern, int retentionPolicies) {
+ assert namePattern != null;
+ this.namePattern = namePattern;
+ this.retentionPolicies = retentionPolicies;
+ }
+
+ public boolean isAny() {
+ return this == ANY_WITH_ANY_RETENTION;
+ }
+
+ public boolean isAnyWithRuntimeRetention() {
+ return this == ANY_WITH_RUNTIME_RETENTION;
+ }
+
+ public boolean isAnyWithClassRetention() {
+ return this == ANY_WITH_CLASS_RETENTION;
+ }
+
+ public KeepQualifiedClassNamePattern getNamePattern() {
+ return namePattern;
+ }
+
+ public boolean includesRuntimeRetention() {
+ return (retentionPolicies & RUNTIME_RETENTION_MASK) > 0;
+ }
+
+ public boolean includesClassRetention() {
+ return (retentionPolicies & CLASS_RETENTION_MASK) > 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof KeepAnnotationPattern)) {
+ return false;
+ }
+ KeepAnnotationPattern that = (KeepAnnotationPattern) o;
+ return retentionPolicies == that.retentionPolicies && namePattern.equals(that.namePattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(namePattern, retentionPolicies);
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java
index 0f81deb..454f725 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java
@@ -286,23 +286,42 @@
return Annotation.ALL_INSTANCE;
}
- public static Annotation annotation(KeepQualifiedClassNamePattern pattern) {
+ public static Annotation annotationsAllWithRuntimeRetention() {
+ return Annotation.ALL_WITH_RUNTIME_RETENTION_INSTANCE;
+ }
+
+ public static Annotation annotationsAllWithClassRetention() {
+ return Annotation.ALL_WITH_CLASS_RETENTION_INSTANCE;
+ }
+
+ public static Annotation annotation(KeepAnnotationPattern pattern) {
if (pattern.isAny()) {
return annotationsAll();
}
+ if (pattern.isAnyWithRuntimeRetention()) {
+ return annotationsAllWithRuntimeRetention();
+ }
+ if (pattern.isAnyWithClassRetention()) {
+ return annotationsAllWithClassRetention();
+ }
return new Annotation(pattern);
}
public static final class Annotation extends KeepConstraint {
- private static final Annotation ALL_INSTANCE =
- new Annotation(KeepQualifiedClassNamePattern.any());
+ private static final Annotation ALL_INSTANCE = new Annotation(KeepAnnotationPattern.any());
- private final KeepQualifiedClassNamePattern classNamePattern;
+ private static final Annotation ALL_WITH_RUNTIME_RETENTION_INSTANCE =
+ new Annotation(KeepAnnotationPattern.anyWithRuntimeRetention());
- private Annotation(KeepQualifiedClassNamePattern classNamePattern) {
- assert classNamePattern != null;
- this.classNamePattern = classNamePattern;
+ private static final Annotation ALL_WITH_CLASS_RETENTION_INSTANCE =
+ new Annotation(KeepAnnotationPattern.anyWithClassRetention());
+
+ private final KeepAnnotationPattern annotationPattern;
+
+ private Annotation(KeepAnnotationPattern annotationPattern) {
+ assert annotationPattern != null;
+ this.annotationPattern = annotationPattern;
}
@Override
@@ -315,12 +334,16 @@
@Override
public void addRequiredKeepAttributes(Set<KeepAttribute> attributes) {
- attributes.add(KeepAttribute.RUNTIME_VISIBLE_ANNOTATIONS);
- attributes.add(KeepAttribute.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS);
- attributes.add(KeepAttribute.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
- attributes.add(KeepAttribute.RUNTIME_INVISIBLE_ANNOTATIONS);
- attributes.add(KeepAttribute.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS);
- attributes.add(KeepAttribute.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ if (annotationPattern.includesRuntimeRetention()) {
+ attributes.add(KeepAttribute.RUNTIME_VISIBLE_ANNOTATIONS);
+ attributes.add(KeepAttribute.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS);
+ attributes.add(KeepAttribute.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+ }
+ if (annotationPattern.includesClassRetention()) {
+ attributes.add(KeepAttribute.RUNTIME_INVISIBLE_ANNOTATIONS);
+ attributes.add(KeepAttribute.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS);
+ attributes.add(KeepAttribute.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ }
}
@Override
@@ -332,12 +355,12 @@
return false;
}
Annotation that = (Annotation) o;
- return classNamePattern.equals(that.classNamePattern);
+ return annotationPattern.equals(that.annotationPattern);
}
@Override
public int hashCode() {
- return classNamePattern.hashCode();
+ return annotationPattern.hashCode();
}
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java
index c319a3c..fd543ed 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.keepanno.ast;
+import com.android.tools.r8.keepanno.ast.KeepConstraint.Annotation;
import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
import com.google.common.collect.ImmutableSet;
import java.util.Collections;
@@ -19,7 +20,12 @@
}
public static KeepConstraints defaultAdditions(KeepConstraints additionalConstraints) {
- return new Additions(additionalConstraints);
+ if (additionalConstraints instanceof Constraints) {
+ return new Additions((Constraints) additionalConstraints);
+ }
+ // If no explicit constraints are set, this is just identity on the defaults/additions.
+ assert additionalConstraints instanceof Defaults || additionalConstraints instanceof Additions;
+ return additionalConstraints;
}
public static Builder builder() {
@@ -28,17 +34,39 @@
public static class Builder {
+ private boolean defaultAdditions = false;
private final Set<KeepConstraint> constraints = new HashSet<>();
private Builder() {}
+ public Builder copyFrom(KeepConstraints fromConstraints) {
+ if (fromConstraints instanceof Defaults) {
+ // This builder is based on defaults so set the builder as an addition.
+ defaultAdditions = true;
+ } else if (fromConstraints instanceof Additions) {
+ // This builder is an addition, populate the additions into the constraint set.
+ defaultAdditions = true;
+ constraints.addAll(((Additions) fromConstraints).additions.constraints);
+ } else {
+ assert fromConstraints instanceof Constraints;
+ constraints.addAll(((Constraints) fromConstraints).constraints);
+ }
+ return this;
+ }
+
+ public Builder removeAnnotations() {
+ constraints.removeIf(constraint -> constraint instanceof Annotation);
+ return this;
+ }
+
public Builder add(KeepConstraint constraint) {
constraints.add(constraint);
return this;
}
public KeepConstraints build() {
- return new Constraints(constraints);
+ Constraints constraintCollection = new Constraints(constraints);
+ return defaultAdditions ? new Additions(constraintCollection) : constraintCollection;
}
}
@@ -65,9 +93,9 @@
private static class Additions extends KeepConstraints {
- private final KeepConstraints additions;
+ private final Constraints additions;
- public Additions(KeepConstraints additions) {
+ public Additions(Constraints additions) {
this.additions = additions;
}
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 b894eae..05ee81c 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
@@ -122,6 +122,12 @@
*
* MEMBER_ACCESS_FLAG
* ::= public | protected | package-private | private | static | final | synthetic
+ *
+ * ANNOTATION_PATTERN
+ * ::= @QUALIFIED_CLASS_NAME_PATTERN retention(RETENTION_POLICY+)
+ *
+ * RETENTION_POLICY
+ * ::= RUNTIME | CLASS
* </pre>
*/
public final class KeepEdge extends KeepDeclaration {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
index 0d83a25..5f6bb55 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
@@ -55,6 +55,7 @@
}
public PropertyParsingContext property(String propertyName) {
+ assert propertyName != null;
return new PropertyParsingContext(this, propertyName);
}
@@ -273,5 +274,11 @@
public String getContextFrameAsString() {
return getPropertyName();
}
+
+ @Override
+ public boolean isSynthetic() {
+ // The value property is the default and usually unnamed property, so we avoid printing it.
+ return "value".equals(propertyName);
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternAnyRetentionTest.java b/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternAnyRetentionTest.java
new file mode 100644
index 0000000..4b11ba6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternAnyRetentionTest.java
@@ -0,0 +1,127 @@
+// 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.isPresent;
+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.AnnotationPattern;
+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.ClassSubject;
+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 AnnotationPatternAnyRetentionTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("C1: A1");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public AnnotationPatternAnyRetentionTest(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)
+ .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);
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ // The class retention annotation is used and kept. It can be renamed as nothing prohibits that.
+ assertThat(inspector.clazz(A1.class), isPresentAndRenamed());
+ assertThat(inspector.clazz(A2.class), isPresentAndRenamed());
+ // The class is retained by the keep-annotation.
+ ClassSubject clazz = inspector.clazz(C1.class);
+ assertThat(clazz, isPresentAndNotRenamed());
+ assertThat(clazz.annotation(A1.class), isPresent());
+ assertThat(clazz.annotation(A2.class), isPresent());
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A1 {}
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.CLASS)
+ @interface A2 {}
+
+ static class Reflector {
+
+ @UsesReflection({
+ @KeepTarget(
+ classAnnotatedByClassConstant = A2.class,
+ constrainAnnotations =
+ @AnnotationPattern(retention = {RetentionPolicy.CLASS, RetentionPolicy.RUNTIME})),
+ })
+ public void foo(Class<?>... classes) throws Exception {
+ for (Class<?> clazz : classes) {
+ String typeName = clazz.getTypeName();
+ System.out.print(typeName.substring(typeName.lastIndexOf('$') + 1) + ":");
+ if (clazz.isAnnotationPresent(A1.class)) {
+ System.out.print(" A1");
+ }
+ // The below code will not trigger as A2 is not visible at runtime, but it will ensure the
+ // annotation is used.
+ if (clazz.isAnnotationPresent(A2.class)) {
+ System.out.print(" A2");
+ }
+ System.out.println();
+ }
+ }
+ }
+
+ @A1
+ @A2
+ static class C1 {}
+
+ static class TestClass {
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new Reflector().foo(C1.class);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternClassRetentionTest.java b/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternClassRetentionTest.java
new file mode 100644
index 0000000..efc77cf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternClassRetentionTest.java
@@ -0,0 +1,127 @@
+// 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.isPresent;
+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.AnnotationPattern;
+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.ClassSubject;
+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 AnnotationPatternClassRetentionTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("C1:");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public AnnotationPatternClassRetentionTest(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)
+ .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);
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ // Nothing is keeping A1 and no references exist to it.
+ // Having the constraint on annotations with class retention should not prohibit removal.
+ assertThat(inspector.clazz(A1.class), isAbsent());
+ // The class retention annotation is used and kept. It can be renamed as nothing prohibits that.
+ assertThat(inspector.clazz(A2.class), isPresentAndRenamed());
+ // The class is retained by the keep-annotation.
+ ClassSubject clazz = inspector.clazz(C1.class);
+ assertThat(clazz, isPresentAndNotRenamed());
+ assertThat(clazz.annotation(A1.class), isAbsent());
+ assertThat(clazz.annotation(A2.class), isPresent());
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A1 {}
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.CLASS)
+ @interface A2 {}
+
+ static class Reflector {
+
+ @UsesReflection({
+ @KeepTarget(
+ classAnnotatedByClassConstant = A2.class,
+ constrainAnnotations = @AnnotationPattern(retention = RetentionPolicy.CLASS)),
+ })
+ public void foo(Class<?>... classes) throws Exception {
+ for (Class<?> clazz : classes) {
+ String typeName = clazz.getTypeName();
+ System.out.print(typeName.substring(typeName.lastIndexOf('$') + 1) + ":");
+ // Ignoring A1 as we explicitly have no keep-annotation to preserve it.
+ // The below code will not trigger as A2 is not visible at runtime, but it will ensure the
+ // annotation is used.
+ if (clazz.isAnnotationPresent(A2.class)) {
+ System.out.print(" A2");
+ }
+ System.out.println();
+ }
+ }
+ }
+
+ @A1
+ @A2
+ static class C1 {}
+
+ static class TestClass {
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new Reflector().foo(C1.class);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternMultipleTest.java b/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternMultipleTest.java
new file mode 100644
index 0000000..33a4f6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternMultipleTest.java
@@ -0,0 +1,146 @@
+// 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.isPresent;
+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.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
+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.ClassSubject;
+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 AnnotationPatternMultipleTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("C1: A1", "C2:");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public AnnotationPatternMultipleTest(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)
+ .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, A3.class, C1.class, C2.class);
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ assertThat(inspector.clazz(A1.class), isPresentAndRenamed());
+ assertThat(inspector.clazz(A2.class), isPresentAndRenamed());
+ // No use of A3 so R8 will remove it.
+ assertThat(inspector.clazz(A3.class), isAbsent());
+ // The class is retained by the keep-annotation.
+ ClassSubject c1 = inspector.clazz(C1.class);
+ assertThat(c1, isPresentAndNotRenamed());
+ assertThat(c1.annotation(A1.class), isPresent());
+ assertThat(c1.annotation(A3.class), isAbsent());
+
+ ClassSubject c2 = inspector.clazz(C2.class);
+ assertThat(c2, isPresentAndNotRenamed());
+ assertThat(c2.annotation(A2.class), isPresent());
+ assertThat(c2.annotation(A3.class), isAbsent());
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A1 {}
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.CLASS)
+ @interface A2 {}
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A3 {}
+
+ static class Reflector {
+
+ @UsesReflection(
+ @KeepTarget(
+ classAnnotatedByClassNamePattern =
+ @ClassNamePattern(packageName = "com.android.tools.r8.keepanno"),
+ constrainAnnotations = {
+ @AnnotationPattern(constant = A1.class, retention = RetentionPolicy.RUNTIME),
+ @AnnotationPattern(constant = A2.class, retention = RetentionPolicy.CLASS)
+ }))
+ public void foo(Class<?>... classes) throws Exception {
+ for (Class<?> clazz : classes) {
+ String typeName = clazz.getTypeName();
+ System.out.print(typeName.substring(typeName.lastIndexOf('$') + 1) + ":");
+ if (clazz.isAnnotationPresent(A1.class)) {
+ System.out.print(" A1");
+ }
+ // The below code will not trigger as A2 is not visible at runtime, but it will ensure the
+ // annotation is used.
+ if (clazz.isAnnotationPresent(A2.class)) {
+ System.out.print(" A2");
+ }
+ System.out.println();
+ }
+ }
+ }
+
+ @A1
+ @A3
+ static class C1 {}
+
+ @A2
+ @A3
+ static class C2 {}
+
+ static class TestClass {
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new Reflector().foo(C1.class, C2.class);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByAnyAnnoConstraintPatternTest.java b/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByAnyAnnoConstraintPatternTest.java
new file mode 100644
index 0000000..0a33131
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByAnyAnnoConstraintPatternTest.java
@@ -0,0 +1,153 @@
+// 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.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
+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 ClassAnnotatedByAnyAnnoConstraintPatternTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("C1: A1", "C2: A2");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public ClassAnnotatedByAnyAnnoConstraintPatternTest(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)
+ .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,
+ A3.class,
+ C1.class,
+ C2.class,
+ C3.class);
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ // The class constant use will ensure the annotations remains.
+ // They are not renamed as the keep-annotation will match them (they are themselves annotated).
+ assertThat(inspector.clazz(A1.class), isPresentAndNotRenamed());
+ assertThat(inspector.clazz(A2.class), isPresentAndNotRenamed());
+ assertThat(inspector.clazz(A3.class), isPresentAndNotRenamed());
+ // The first two classes are annotated so the keep-annotation applies and retains 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());
+ // The class-retention annotation should not be present on the class as the pattern defaults
+ // to only match runtime retention classes.
+ assertThat(inspector.clazz(C2.class).annotation(A3.class), isAbsent());
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A1 {}
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A2 {}
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.CLASS)
+ @interface A3 {}
+
+ static class Reflector {
+
+ @UsesReflection({
+ @KeepTarget(
+ classAnnotatedByClassNamePattern = @ClassNamePattern,
+ constraints = {KeepConstraint.NAME},
+ constrainAnnotations = @AnnotationPattern),
+ })
+ public void foo(Class<?>... classes) throws Exception {
+ for (Class<?> clazz : classes) {
+ if (clazz.getAnnotations().length > 0) {
+ String typeName = clazz.getTypeName();
+ System.out.print(typeName.substring(typeName.lastIndexOf('$') + 1) + ":");
+ if (clazz.isAnnotationPresent(A1.class)) {
+ System.out.print(" A1");
+ }
+ if (clazz.isAnnotationPresent(A2.class)) {
+ System.out.print(" A2");
+ }
+ if (clazz.isAnnotationPresent(A3.class)) {
+ System.out.print(" A3");
+ }
+ System.out.println();
+ }
+ }
+ }
+ }
+
+ @A1
+ static class C1 {}
+
+ @A2
+ @A3
+ static class C2 {}
+
+ 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/KeepInvalidTargetTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
index 0eace48..d8d5941 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
import com.android.tools.r8.keepanno.annotations.KeepConstraint;
import com.android.tools.r8.keepanno.annotations.KeepTarget;
import com.android.tools.r8.keepanno.annotations.StringPattern;
@@ -186,6 +187,48 @@
}
}
+ @Test
+ public void testEmptyAnnotationConstraints() {
+ assertThrowsWith(
+ () -> extractRuleForClass(EmptyAnnotationConstraints.class),
+ allOf(
+ containsString("Expected non-empty array"),
+ containsString("at property: constrainAnnotations")));
+ }
+
+ static class EmptyAnnotationConstraints {
+
+ @UsesReflection(
+ @KeepTarget(
+ classConstant = A.class,
+ constrainAnnotations = {}))
+ public static void main(String[] args) {
+ System.out.println("Hello, world");
+ }
+ }
+
+ @Test
+ public void testEmptyAnnotationPatternRetention() {
+ assertThrowsWith(
+ () -> extractRuleForClass(EmptyAnnotationPatternRetention.class),
+ allOf(
+ containsString("Expected non-empty array"),
+ containsString("at property: retention"),
+ containsString("at annotation: @AnnotationPattern"),
+ containsString("at property: constrainAnnotations")));
+ }
+
+ static class EmptyAnnotationPatternRetention {
+
+ @UsesReflection(
+ @KeepTarget(
+ classConstant = A.class,
+ constrainAnnotations = @AnnotationPattern(retention = {})))
+ public static void main(String[] args) {
+ System.out.println("Hello, world");
+ }
+ }
+
static class A {
// just a target.
}
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 0852bde..4eb00fc 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
@@ -7,6 +7,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.cfmethodgeneration.CodeGenerationBase;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
import com.android.tools.r8.keepanno.annotations.CheckOptimizedOut;
import com.android.tools.r8.keepanno.annotations.CheckRemoved;
import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
@@ -301,6 +302,7 @@
private static String MEMBER_ANNOTATED_BY_GROUP = "member-annotated-by";
private static String METHOD_ANNOTATED_BY_GROUP = "method-annotated-by";
private static String FIELD_ANNOTATED_BY_GROUP = "field-annotated-by";
+ private static String ANNOTATION_NAME_GROUP = "annotation-name";
private Group createDescriptionGroup() {
return new Group("description")
@@ -487,12 +489,13 @@
docLink(KeepItemKind.ONLY_MEMBERS) + " otherwise.");
}
- private Group getKeepConstraintsGroup() {
- return new Group(CONSTRAINTS_GROUP).addMember(constraints()).addMember(constraintAdditions());
+ private void forEachKeepConstraintGroups(Consumer<Group> fn) {
+ fn.accept(getKeepConstraintsGroup());
+ fn.accept(new Group("constrain-annotations").addMember(constrainAnnotations()));
}
- private static String docLinkList(Enum<?>... values) {
- return StringUtils.join(", ", values, v -> docLink(v), BraceType.TUBORG);
+ private Group getKeepConstraintsGroup() {
+ return new Group(CONSTRAINTS_GROUP).addMember(constraints()).addMember(constraintAdditions());
}
private static GroupMember constraints() {
@@ -527,6 +530,78 @@
.defaultArrayEmpty(KeepConstraint.class);
}
+ private static GroupMember constrainAnnotations() {
+ return new GroupMember("constrainAnnotations")
+ .setDocTitle("Patterns for annotations that must remain on the item.")
+ .addParagraph(
+ "The annotations matching any of the patterns must remain on the item",
+ "if the annotation types remain in the program.")
+ .addParagraph(
+ "Note that if the annotation types themselves are unused/removed,",
+ "then their references on the item will be removed too.",
+ "If the annotation types themselves are used reflectively then they too need a",
+ "keep annotation or rule to ensure they remain in the program.")
+ .addParagraph(
+ "Setting this to a non-empty array implicitly includes ",
+ docLink(KeepConstraint.ANNOTATIONS),
+ "in the set of active constraints.")
+ .addParagraph(
+ "By default no annotation patterns are defined and no annotations are required to",
+ "remain, unless",
+ CONSTRAINTS_GROUP,
+ "explicitly includes",
+ docLink(KeepConstraint.ANNOTATIONS),
+ "in that case the default pattern is the default " + docLink(AnnotationPattern.class),
+ "which matches all annotations with retention policy {@code RetentionPolicy#RUNTIME}")
+ .setDocReturn("Annotation patterns")
+ .defaultArrayEmpty(AnnotationPattern.class);
+ }
+
+ private Group annotationNameGroup() {
+ return new Group(ANNOTATION_NAME_GROUP)
+ .addMember(annotationName())
+ .addMember(annotationConstant())
+ .addMember(annotationNamePattern())
+ .addDocFooterParagraph(
+ "If none are specified the default is to match any annotation name.");
+ }
+
+ private GroupMember annotationName() {
+ return new GroupMember("name")
+ .setDocTitle(
+ "Define the " + ANNOTATION_NAME_GROUP + " pattern by fully qualified class name.")
+ .setDocReturn("The qualified class name that defines the annotation.")
+ .defaultEmptyString();
+ }
+
+ private GroupMember annotationConstant() {
+ return new GroupMember("constant")
+ .setDocTitle(
+ "Define the "
+ + ANNOTATION_NAME_GROUP
+ + " pattern by reference to a {@code Class} constant.")
+ .setDocReturn("The Class constant that defines the annotation.")
+ .defaultObjectClass();
+ }
+
+ private GroupMember annotationNamePattern() {
+ return new GroupMember("namePattern")
+ .setDocTitle(
+ "Define the "
+ + ANNOTATION_NAME_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 static GroupMember annotationRetention() {
+ return new GroupMember("retention")
+ .setDocTitle("Specify which retention policies must be set for the annotations.")
+ .addParagraph("Matches annotations with matching retention policies")
+ .setDocReturn("Retention policies. By default {@code RetentionPolicy.RUNTIME}.")
+ .defaultArrayValue(RetentionPolicy.class, "RetentionPolicy.RUNTIME");
+ }
+
private GroupMember bindingName() {
return new GroupMember("bindingName")
.setDocTitle(
@@ -1024,6 +1099,30 @@
println("}");
}
+ private void generateAnnotationPattern() {
+ printCopyRight(2024);
+ printPackage("annotations");
+ printImports(ANNOTATION_IMPORTS);
+ DocPrinter.printer()
+ .setDocTitle("A pattern structure for matching annotations.")
+ .addParagraph(
+ "If no properties are set, the default pattern matches any annotation",
+ "with a runtime retention policy.")
+ .printDoc(this::println);
+ println("@Target(ElementType.ANNOTATION_TYPE)");
+ println("@Retention(RetentionPolicy.CLASS)");
+ println("public @interface " + simpleName(AnnotationPattern.class) + " {");
+ println();
+ withIndent(
+ () -> {
+ annotationNameGroup().generate(this);
+ println();
+ annotationRetention().generate(this);
+ });
+ println();
+ println("}");
+ }
+
private void generateKeepBinding() {
printCopyRight(2022);
printPackage("annotations");
@@ -1073,8 +1172,11 @@
() -> {
getKindGroup().generate(this);
println();
- getKeepConstraintsGroup().generate(this);
- println();
+ forEachKeepConstraintGroups(
+ g -> {
+ g.generate(this);
+ println();
+ });
generateClassAndMemberPropertiesWithClassAndMemberBinding();
});
println();
@@ -1294,8 +1396,11 @@
+ " if annotating a member.")
.generate(this);
println();
- getKeepConstraintsGroup().generate(this);
- println();
+ forEachKeepConstraintGroups(
+ g -> {
+ g.generate(this);
+ println();
+ });
generateMemberPropertiesNoBinding();
});
println();
@@ -1318,6 +1423,10 @@
return "{@link " + simpleName(kind.getClass()) + "#" + kind.name() + "}";
}
+ private static String docLinkList(Enum<?>... values) {
+ return StringUtils.join(", ", values, v -> docLink(v), BraceType.TUBORG);
+ }
+
private void generateConstants() {
printCopyRight(2023);
printPackage("ast");
@@ -1356,6 +1465,7 @@
generateStringPatternConstants();
generateTypePatternConstants();
generateClassNamePatternConstants();
+ generateAnnotationPatternConstants();
});
println("}");
}
@@ -1509,7 +1619,7 @@
() -> {
generateAnnotationConstants(KeepTarget.class);
getKindGroup().generateConstants(this);
- getKeepConstraintsGroup().generateConstants(this);
+ forEachKeepConstraintGroups(g -> g.generateConstants(this));
});
println("}");
println();
@@ -1663,6 +1773,18 @@
println();
}
+ private void generateAnnotationPatternConstants() {
+ println("public static final class AnnotationPattern {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(AnnotationPattern.class);
+ annotationNameGroup().generateConstants(this);
+ annotationRetention().generateConstants(this);
+ });
+ println("}");
+ println();
+ }
+
private static void writeFile(Path file, Consumer<Generator> fn) throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(byteStream);
@@ -1691,6 +1813,7 @@
writeFile(source(annoPkg, StringPattern.class), Generator::generateStringPattern);
writeFile(source(annoPkg, TypePattern.class), Generator::generateTypePattern);
writeFile(source(annoPkg, ClassNamePattern.class), Generator::generateClassNamePattern);
+ writeFile(source(annoPkg, AnnotationPattern.class), Generator::generateAnnotationPattern);
writeFile(source(annoPkg, KeepBinding.class), Generator::generateKeepBinding);
writeFile(source(annoPkg, KeepTarget.class), Generator::generateKeepTarget);
writeFile(source(annoPkg, KeepCondition.class), Generator::generateKeepCondition);