[KeepAnno] Add class-name patterns to the annotation language
The class-name patterns allow splitting the match on simple names and packages.
Bug: b/248408342
Change-Id: Ia4a736878f0ebf72e0b1443077a3f7240c83b657
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java
new file mode 100644
index 0000000..1d40ae5
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2023, 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 names of classes and interfaces.
+ *
+ * <p>If no properties are set, the default pattern matches any name of a class or interface.
+ */
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface ClassNamePattern {
+
+ /**
+ * Exact simple name of the class or interface.
+ *
+ * <p>For example, the simple name {@code com.example.MyClass} is {@code MyClass}.
+ *
+ * <p>The default matches any simple name.
+ */
+ String simpleName() default "";
+
+ /**
+ * Exact package name of the class or interface.
+ *
+ * <p>For example, the package of {@code com.example.MyClass} is {@code com.example}.
+ *
+ * <p>The default matches any package.
+ */
+ String packageName() default "";
+}
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 579d46f..3f1152e 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
@@ -74,6 +74,7 @@
* <ul>
* <li>className
* <li>classConstant
+ * <li>classNamePattern
* <li>instanceOfClassName
* <li>instanceOfClassNameExclusive
* <li>instanceOfClassConstant
@@ -95,6 +96,7 @@
*
* <ul>
* <li>classConstant
+ * <li>classNamePattern
* <li>classFromBinding
* </ul>
*
@@ -111,6 +113,7 @@
*
* <ul>
* <li>className
+ * <li>classNamePattern
* <li>classFromBinding
* </ul>
*
@@ -121,6 +124,23 @@
Class<?> classConstant() default Object.class;
/**
+ * Define the class-name pattern by reference to a class-name pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-name:
+ *
+ * <ul>
+ * <li>className
+ * <li>classConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class name.
+ *
+ * @return The class-name pattern that defines the class.
+ */
+ ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
+
+ /**
* Define the instance-of pattern as classes that are instances of the fully qualified class name.
*
* <p>Mutually exclusive with the following other properties defining instance-of:
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 470266b..74b2add 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
@@ -36,6 +36,7 @@
* <ul>
* <li>className
* <li>classConstant
+ * <li>classNamePattern
* <li>instanceOfClassName
* <li>instanceOfClassNameExclusive
* <li>instanceOfClassConstant
@@ -57,6 +58,7 @@
*
* <ul>
* <li>classConstant
+ * <li>classNamePattern
* <li>classFromBinding
* </ul>
*
@@ -73,6 +75,7 @@
*
* <ul>
* <li>className
+ * <li>classNamePattern
* <li>classFromBinding
* </ul>
*
@@ -83,6 +86,23 @@
Class<?> classConstant() default Object.class;
/**
+ * Define the class-name pattern by reference to a class-name pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-name:
+ *
+ * <ul>
+ * <li>className
+ * <li>classConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class name.
+ *
+ * @return The class-name pattern that defines the class.
+ */
+ ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
+
+ /**
* Define the instance-of pattern as classes that are instances of the fully qualified class name.
*
* <p>Mutually exclusive with the following other properties defining instance-of:
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 7bbc2f0..d60525d 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
@@ -133,6 +133,7 @@
* <ul>
* <li>className
* <li>classConstant
+ * <li>classNamePattern
* <li>instanceOfClassName
* <li>instanceOfClassNameExclusive
* <li>instanceOfClassConstant
@@ -154,6 +155,7 @@
*
* <ul>
* <li>classConstant
+ * <li>classNamePattern
* <li>classFromBinding
* </ul>
*
@@ -170,6 +172,7 @@
*
* <ul>
* <li>className
+ * <li>classNamePattern
* <li>classFromBinding
* </ul>
*
@@ -180,6 +183,23 @@
Class<?> classConstant() default Object.class;
/**
+ * Define the class-name pattern by reference to a class-name pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-name:
+ *
+ * <ul>
+ * <li>className
+ * <li>classConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class name.
+ *
+ * @return The class-name pattern that defines the class.
+ */
+ ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
+
+ /**
* Define the instance-of pattern as classes that are instances of the fully qualified class name.
*
* <p>Mutually exclusive with the following other properties defining instance-of:
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
index 1d7e6c6..2fa0ffd 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
@@ -29,7 +29,12 @@
*
* <p>For example, {@code "long"} or {@code "java.lang.String"}.
*
- * <p>Mutually exclusive with the property `constant` also defining type-pattern.
+ * <p>Mutually exclusive with the following other properties defining type-pattern:
+ *
+ * <ul>
+ * <li>constant
+ * <li>classNamePattern
+ * </ul>
*/
String name() default "";
@@ -38,7 +43,24 @@
*
* <p>For example, {@code String.class}.
*
- * <p>Mutually exclusive with the property `name` also defining type-pattern.
+ * <p>Mutually exclusive with the following other properties defining type-pattern:
+ *
+ * <ul>
+ * <li>name
+ * <li>classNamePattern
+ * </ul>
*/
Class<?> constant() default Object.class;
+
+ /**
+ * Classes matching the class-name pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining type-pattern:
+ *
+ * <ul>
+ * <li>name
+ * <li>constant
+ * </ul>
+ */
+ ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
}
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 35beddf..cee27a8 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
@@ -6,6 +6,7 @@
import com.android.tools.r8.keepanno.ast.AccessVisibility;
import com.android.tools.r8.keepanno.ast.AnnotationConstants;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Binding;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.ClassNamePattern;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Condition;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Constraints;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Edge;
@@ -50,10 +51,13 @@
import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern;
import com.android.tools.r8.keepanno.ast.KeepOptions;
import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
+import com.android.tools.r8.keepanno.ast.KeepPackagePattern;
import com.android.tools.r8.keepanno.ast.KeepPreconditions;
import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
import com.android.tools.r8.keepanno.ast.KeepTarget;
import com.android.tools.r8.keepanno.ast.KeepTypePattern;
+import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern;
+import com.android.tools.r8.keepanno.utils.Unimplemented;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -1195,7 +1199,6 @@
}
return true;
}
- ;
abstract T getValue();
@@ -1341,6 +1344,15 @@
}
return null;
}
+
+ @Override
+ AnnotationVisitor parseAnnotation(
+ String name, String descriptor, Consumer<KeepQualifiedClassNamePattern> setValue) {
+ if (name.equals(Item.classNamePattern) && descriptor.equals(ClassNamePattern.DESCRIPTOR)) {
+ return new ClassNamePatternVisitor(setValue);
+ }
+ return super.parseAnnotation(name, descriptor, setValue);
+ }
}
private static class InstanceOfDeclaration extends SingleDeclaration<KeepInstanceOfPattern> {
@@ -1402,11 +1414,18 @@
private KeepClassItemReference boundClassItemReference = null;
private final ClassNameDeclaration classNameDeclaration = new ClassNameDeclaration();
private final InstanceOfDeclaration instanceOfDeclaration = new InstanceOfDeclaration();
+ private final List<Declaration<?>> declarations =
+ ImmutableList.of(classNameDeclaration, instanceOfDeclaration);
public ClassDeclaration(Supplier<UserBindingsHelper> getBindingsHelper) {
this.getBindingsHelper = getBindingsHelper;
}
+ @Override
+ List<Declaration<?>> declarations() {
+ return declarations;
+ }
+
private boolean isBindingReferenceDefined() {
return boundClassItemReference != null;
}
@@ -1429,11 +1448,12 @@
@Override
boolean isDefault() {
- return !isBindingReferenceDefined() && !classPatternsAreDefined();
+ return !isBindingReferenceDefined() && super.isDefault();
}
@Override
KeepClassItemReference getValue() {
+ checkAllowedDefinitions();
if (isBindingReferenceDefined()) {
return boundClassItemReference;
}
@@ -1463,15 +1483,7 @@
setBindingReference(KeepBindingReference.forClass(symbol).toClassItemReference());
return true;
}
- if (classNameDeclaration.tryParse(name, value)) {
- checkAllowedDefinitions();
- return true;
- }
- if (instanceOfDeclaration.tryParse(name, value)) {
- checkAllowedDefinitions();
- return true;
- }
- return false;
+ return super.tryParse(name, value);
}
}
@@ -2139,6 +2151,109 @@
}
}
+ private static class ClassSimpleNameDeclaration
+ extends SingleDeclaration<KeepUnqualfiedClassNamePattern> {
+
+ @Override
+ String kind() {
+ return "class-simple-name";
+ }
+
+ @Override
+ KeepUnqualfiedClassNamePattern getDefaultValue() {
+ return KeepUnqualfiedClassNamePattern.any();
+ }
+
+ @Override
+ KeepUnqualfiedClassNamePattern parse(String name, Object value) {
+ if (name.equals(ClassNamePattern.simpleName) && value instanceof String) {
+ return KeepUnqualfiedClassNamePattern.builder().exact((String) value).build();
+ }
+ return null;
+ }
+ }
+
+ private static class PackageDeclaration extends SingleDeclaration<KeepPackagePattern> {
+
+ @Override
+ String kind() {
+ return "package";
+ }
+
+ @Override
+ KeepPackagePattern getDefaultValue() {
+ return KeepPackagePattern.any();
+ }
+
+ @Override
+ KeepPackagePattern parse(String name, Object value) {
+ if (name.equals(ClassNamePattern.packageName) && value instanceof String) {
+ return KeepPackagePattern.builder().exact((String) value).build();
+ }
+ return null;
+ }
+ }
+
+ private static class ClassNamePatternDeclaration
+ extends Declaration<KeepQualifiedClassNamePattern> {
+
+ private final ClassSimpleNameDeclaration nameDeclaration = new ClassSimpleNameDeclaration();
+ private final PackageDeclaration packageDeclaration = new PackageDeclaration();
+ private final List<Declaration<?>> declarations =
+ ImmutableList.of(nameDeclaration, packageDeclaration);
+
+ @Override
+ String kind() {
+ return "class-name";
+ }
+
+ @Override
+ KeepQualifiedClassNamePattern getValue() {
+ if (!packageDeclaration.isDefault() || !nameDeclaration.isDefault()) {
+ return KeepQualifiedClassNamePattern.builder()
+ .setPackagePattern(packageDeclaration.getValue())
+ .setNamePattern(nameDeclaration.getValue())
+ .build();
+ }
+ return null;
+ }
+
+ @Override
+ List<Declaration<?>> declarations() {
+ return declarations;
+ }
+ }
+
+ private static class ClassNamePatternVisitor extends AnnotationVisitorBase {
+
+ private final ClassNamePatternDeclaration declaration = new ClassNamePatternDeclaration();
+ private final Consumer<KeepQualifiedClassNamePattern> setValue;
+
+ public ClassNamePatternVisitor(Consumer<KeepQualifiedClassNamePattern> setValue) {
+ this.setValue = setValue;
+ }
+
+ @Override
+ public String getAnnotationName() {
+ return ClassNamePattern.SIMPLE_NAME;
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ if (!declaration.tryParse(name, value)) {
+ super.visit(name, value);
+ }
+ }
+
+ @Override
+ public void visitEnd() {
+ if (!declaration.isDefault()) {
+ setValue.accept(declaration.getValue());
+ }
+ super.visitEnd();
+ }
+ }
+
private static class TypePatternVisitor extends AnnotationVisitorBase {
private final Supplier<String> annotationName;
private final Consumer<KeepTypePattern> consumer;
@@ -2177,6 +2292,23 @@
}
@Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ if (TypePattern.classNamePattern.equals(name)
+ && descriptor.equals(ClassNamePattern.DESCRIPTOR)) {
+ return new ClassNamePatternVisitor(
+ p -> {
+ if (p.isExact()) {
+ setResult(KeepTypePattern.fromDescriptor(p.getExactDescriptor()));
+ } else {
+ // TODO(b/248408342): Extend the AST type patterns.
+ throw new Unimplemented("Non-exact class patterns are not unimplemented yet");
+ }
+ });
+ }
+ return super.visitAnnotation(name, descriptor);
+ }
+
+ @Override
public void visitEnd() {
consumer.accept(result != null ? result : 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 1ab1561..cae779e 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
@@ -76,6 +76,7 @@
public static final String memberFromBinding = "memberFromBinding";
public static final String className = "className";
public static final String classConstant = "classConstant";
+ public static final String classNamePattern = "classNamePattern";
public static final String instanceOfClassName = "instanceOfClassName";
public static final String instanceOfClassNameExclusive = "instanceOfClassNameExclusive";
public static final String instanceOfClassConstant = "instanceOfClassConstant";
@@ -203,5 +204,14 @@
"Lcom/android/tools/r8/keepanno/annotations/TypePattern;";
public static final String name = "name";
public static final String constant = "constant";
+ public static final String classNamePattern = "classNamePattern";
+ }
+
+ public static final class ClassNamePattern {
+ public static final String SIMPLE_NAME = "ClassNamePattern";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/ClassNamePattern;";
+ public static final String simpleName = "simpleName";
+ public static final String packageName = "packageName";
}
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java
new file mode 100644
index 0000000..3468b4c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java
@@ -0,0 +1,192 @@
+// Copyright (c) 2023, 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.classpatterns;
+
+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.ClassNamePattern;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.TypePattern;
+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.google.common.collect.ImmutableList;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassNamePatternsTest extends TestBase {
+
+ static final Class<?> A1 = com.android.tools.r8.keepanno.classpatterns.pkg1.A.class;
+ static final Class<?> B1 = com.android.tools.r8.keepanno.classpatterns.pkg1.B.class;
+ static final Class<?> A2 = com.android.tools.r8.keepanno.classpatterns.pkg2.A.class;
+ static final Class<?> B2 = com.android.tools.r8.keepanno.classpatterns.pkg2.B.class;
+
+ static final String EXPECTED_ALL = StringUtils.lines("pkg1.A", "pkg1.B", "pkg2.A", "pkg2.B");
+ static final String EXPECTED_PKG = StringUtils.lines("pkg1.A", "pkg1.B");
+ static final String EXPECTED_NAME = StringUtils.lines("pkg1.B", "pkg2.B");
+ static final String EXPECTED_SINGLE = StringUtils.lines("pkg2.A");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public ClassNamePatternsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getBaseInputClasses())
+ .addProgramClasses(TestAll.class)
+ .run(parameters.getRuntime(), TestAll.class)
+ .assertSuccessWithOutput(EXPECTED_ALL);
+ }
+
+ private void runTestR8(Class<?> mainClass, String expected) throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getBaseInputClasses())
+ .addProgramClasses(mainClass)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), mainClass)
+ .assertSuccessWithOutput(expected);
+ }
+
+ @Test
+ public void testAllR8() throws Exception {
+ runTestR8(TestAll.class, EXPECTED_ALL);
+ }
+
+ @Test
+ public void testPkgR8() throws Exception {
+ runTestR8(TestPkg.class, EXPECTED_PKG);
+ }
+
+ @Test
+ public void testNameR8() throws Exception {
+ runTestR8(TestName.class, EXPECTED_NAME);
+ }
+
+ @Test
+ public void testSingleR8() throws Exception {
+ runTestR8(TestSingle.class, EXPECTED_SINGLE);
+ }
+
+ public List<Class<?>> getBaseInputClasses() {
+ return ImmutableList.of(Util.class, A1, B1, A2, B2);
+ }
+
+ static class Util {
+ private static void lookupClassesAndInvokeMethods() {
+ for (String pkg : Arrays.asList("pkg1", "pkg2")) {
+ for (String name : Arrays.asList("A", "B")) {
+ String type = "com.android.tools.r8.keepanno.classpatterns." + pkg + "." + name;
+ try {
+ Class<?> clazz = Class.forName(type);
+ System.out.println(clazz.getDeclaredMethod("foo").invoke(null));
+ } catch (ClassNotFoundException ignored) {
+ } catch (IllegalAccessException ignored) {
+ } catch (InvocationTargetException ignored) {
+ } catch (NoSuchMethodException ignored) {
+ }
+ }
+ }
+ }
+ }
+
+ static class TestAll {
+
+ @UsesReflection({
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_METHODS,
+ // The empty class pattern is equivalent to "any class".
+ classNamePattern = @ClassNamePattern(),
+ methodName = "foo",
+ methodReturnTypeConstant = String.class)
+ })
+ public void foo() throws Exception {
+ Util.lookupClassesAndInvokeMethods();
+ }
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new TestAll().foo();
+ }
+ }
+
+ static class TestPkg {
+
+ @UsesReflection({
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_METHODS,
+ classNamePattern =
+ @ClassNamePattern(packageName = "com.android.tools.r8.keepanno.classpatterns.pkg1"),
+ methodName = "foo",
+ methodReturnTypeConstant = String.class)
+ })
+ public void foo() throws Exception {
+ Util.lookupClassesAndInvokeMethods();
+ }
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new TestPkg().foo();
+ }
+ }
+
+ static class TestName {
+
+ @UsesReflection({
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_METHODS,
+ classNamePattern = @ClassNamePattern(simpleName = "B"),
+ methodName = "foo",
+ methodReturnTypeConstant = String.class)
+ })
+ public void foo() throws Exception {
+ Util.lookupClassesAndInvokeMethods();
+ }
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new TestName().foo();
+ }
+ }
+
+ static class TestSingle {
+
+ @UsesReflection(
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_METHODS,
+ classNamePattern =
+ @ClassNamePattern(
+ simpleName = "A",
+ packageName = "com.android.tools.r8.keepanno.classpatterns.pkg2"),
+ methodName = "foo",
+ methodReturnTypePattern =
+ @TypePattern(
+ classNamePattern =
+ @ClassNamePattern(packageName = "java.lang", simpleName = "String"))))
+ public void foo() throws Exception {
+ Util.lookupClassesAndInvokeMethods();
+ }
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new TestSingle().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/A.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/A.java
new file mode 100644
index 0000000..eab0e5f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/A.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, 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.classpatterns.pkg1;
+
+public class A {
+
+ public static String foo() {
+ return "pkg1.A";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/B.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/B.java
new file mode 100644
index 0000000..edeb6b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/B.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, 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.classpatterns.pkg1;
+
+public class B {
+
+ public static String foo() {
+ return "pkg1.B";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/A.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/A.java
new file mode 100644
index 0000000..ca0667e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/A.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, 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.classpatterns.pkg2;
+
+public class A {
+
+ public static String foo() {
+ return "pkg2.A";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/B.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/B.java
new file mode 100644
index 0000000..c98e746
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/B.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, 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.classpatterns.pkg2;
+
+public class B {
+
+ public static String foo() {
+ return "pkg2.B";
+ }
+}
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 bd3d82f..bee5471 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
@@ -9,6 +9,7 @@
import com.android.tools.r8.cfmethodgeneration.CodeGenerationBase;
import com.android.tools.r8.keepanno.annotations.CheckOptimizedOut;
import com.android.tools.r8.keepanno.annotations.CheckRemoved;
+import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
import com.android.tools.r8.keepanno.annotations.KeepBinding;
import com.android.tools.r8.keepanno.annotations.KeepCondition;
@@ -24,6 +25,7 @@
import com.android.tools.r8.keepanno.annotations.UsedByNative;
import com.android.tools.r8.keepanno.annotations.UsedByReflection;
import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
import com.google.common.base.Strings;
@@ -51,6 +53,11 @@
Generator.run();
}
+ private static final String DEFAULT_INVALID_TYPE_PATTERN =
+ "@" + simpleName(TypePattern.class) + "(name = \"\")";
+ private static final String DEFAULT_INVALID_CLASS_NAME_PATTERN =
+ "@" + simpleName(ClassNamePattern.class) + "(simpleName = \"\")";
+
public static String quote(String str) {
return "\"" + str + "\"";
}
@@ -69,6 +76,16 @@
this.name = name;
}
+ public GroupMember setType(String type) {
+ valueType = type;
+ return this;
+ }
+
+ public GroupMember setValue(String value) {
+ valueDefault = value;
+ return this;
+ }
+
@Override
public GroupMember self() {
return this;
@@ -90,50 +107,40 @@
generator.println("public static final String " + name + " = " + quote(name) + ";");
}
+ public GroupMember requiredValue(Class<?> type) {
+ assert valueDefault == null;
+ return setType(simpleName(type));
+ }
+
+ public GroupMember requiredArrayValue(Class<?> type) {
+ assert valueDefault == null;
+ return setType(simpleName(type) + "[]");
+ }
+
public GroupMember requiredStringValue() {
- assert valueDefault == null;
- return defaultType("String");
+ return requiredValue(String.class);
}
- public GroupMember requiredValueOfType(String type) {
- assert valueDefault == null;
- return defaultType(type);
+ public GroupMember defaultValue(Class<?> type, String value) {
+ setType(simpleName(type));
+ return setValue(value);
}
- public GroupMember requiredValueOfType(Class<?> type) {
- assert valueDefault == null;
- return defaultType(simpleName(type));
- }
-
- public GroupMember requiredValueOfArrayType(Class<?> type) {
- assert valueDefault == null;
- return defaultType(simpleName(type) + "[]");
- }
-
- public GroupMember defaultType(String type) {
- valueType = type;
- return this;
- }
-
- public GroupMember defaultValue(String value) {
- valueDefault = value;
- return this;
+ public GroupMember defaultArrayValue(Class<?> type, String value) {
+ setType(simpleName(type) + "[]");
+ return setValue("{" + value + "}");
}
public GroupMember defaultEmptyString() {
- return defaultType("String").defaultValue(quote(""));
+ return defaultValue(String.class, quote(""));
}
public GroupMember defaultObjectClass() {
- return defaultType("Class<?>").defaultValue("Object.class");
+ return setType("Class<?>").setValue("Object.class");
}
- public GroupMember defaultEmptyArray(String valueType) {
- return defaultType(valueType + "[]").defaultValue("{}");
- }
-
- public GroupMember defaultEmptyArray(Class<?> type) {
- return defaultEmptyArray(simpleName(type));
+ public GroupMember defaultArrayEmpty(Class<?> type) {
+ return defaultArrayValue(type, "");
}
}
@@ -279,7 +286,7 @@
private Group createBindingsGroup() {
return new Group("bindings")
- .addMember(new GroupMember("bindings").defaultEmptyArray(KeepBinding.class));
+ .addMember(new GroupMember("bindings").defaultArrayEmpty(KeepBinding.class));
}
private Group createPreconditionsGroup() {
@@ -291,7 +298,7 @@
.setDocReturn(
"The list of preconditions. "
+ "Defaults to no conditions, thus trivially/unconditionally satisfied.")
- .defaultEmptyArray(KeepCondition.class));
+ .defaultArrayEmpty(KeepCondition.class));
}
private Group createConsequencesGroup() {
@@ -300,7 +307,7 @@
new GroupMember("consequences")
.setDocTitle("Consequences that must be kept if the annotation is in effect.")
.setDocReturn("The list of target consequences.")
- .requiredValueOfArrayType(KeepTarget.class));
+ .requiredArrayValue(KeepTarget.class));
}
private Group createConsequencesAsValueGroup() {
@@ -309,7 +316,7 @@
new GroupMember("value")
.setDocTitle("Consequences that must be kept if the annotation is in effect.")
.setDocReturn("The list of target consequences.")
- .requiredValueOfArrayType(KeepTarget.class));
+ .requiredArrayValue(KeepTarget.class));
}
private Group createAdditionalPreconditionsGroup() {
@@ -320,7 +327,7 @@
.setDocReturn(
"The list of additional preconditions. "
+ "Defaults to no additional preconditions.")
- .defaultEmptyArray("KeepCondition"));
+ .defaultArrayEmpty(KeepCondition.class));
}
private Group createAdditionalTargetsGroup(String docTitle) {
@@ -331,7 +338,7 @@
.setDocReturn(
"List of additional target consequences. "
+ "Defaults to no additional target consequences.")
- .defaultEmptyArray("KeepTarget"));
+ .defaultArrayEmpty(KeepTarget.class));
}
private Group typePatternGroup() {
@@ -345,7 +352,11 @@
new GroupMember("constant")
.setDocTitle("Exact type from a class constant.")
.addParagraph("For example, {@code String.class}.")
- .defaultObjectClass());
+ .defaultObjectClass())
+ .addMember(
+ new GroupMember("classNamePattern")
+ .setDocTitle("Classes matching the class-name pattern.")
+ .defaultValue(ClassNamePattern.class, DEFAULT_INVALID_CLASS_NAME_PATTERN));
// TODO(b/248408342): Add more injections on type pattern variants.
// /** Exact type name as a string to match any array with that type as member. */
// String arrayOf() default "";
@@ -366,14 +377,37 @@
// boolean anyReference() default false;
}
+ private Group classNamePatternSimpleNameGroup() {
+ return new Group("class-simple-name")
+ .addMember(
+ new GroupMember("simpleName")
+ .setDocTitle("Exact simple name of the class or interface.")
+ .addParagraph(
+ "For example, the simple name of {@code com.example.MyClass} is {@code"
+ + " MyClass}.")
+ .addParagraph("The default matches any simple name.")
+ .defaultEmptyString());
+ }
+
+ private Group classNamePatternPackageGroup() {
+ return new Group("class-package-name")
+ .addMember(
+ new GroupMember("packageName")
+ .setDocTitle("Exact package name of the class or interface.")
+ .addParagraph(
+ "For example, the package of {@code com.example.MyClass} is {@code"
+ + " com.example}.")
+ .addParagraph("The default matches any package.")
+ .defaultEmptyString());
+ }
+
private Group getKindGroup() {
return new Group(KIND_GROUP).addMember(getKindMember());
}
private static GroupMember getKindMember() {
return new GroupMember("kind")
- .defaultType("KeepItemKind")
- .defaultValue("KeepItemKind.DEFAULT")
+ .defaultValue(KeepItemKind.class, "KeepItemKind.DEFAULT")
.setDocTitle("Specify the kind of this item pattern.")
.setDocReturn("The kind for this pattern.")
.addParagraph("Possible values are:")
@@ -406,7 +440,7 @@
"The specified option constraints do not need to be preserved for the"
+ " target.")
.setDocReturn("Option constraints allowed to be modified for the target.")
- .defaultEmptyArray("KeepOption"))
+ .defaultArrayEmpty(KeepOption.class))
.addMember(
new GroupMember("disallow")
.setDeprecated("Use " + docLink(constraints()) + " instead.")
@@ -415,7 +449,7 @@
.addParagraph(
"The specified option constraints *must* be preserved for the target.")
.setDocReturn("Option constraints not allowed to be modified for the target.")
- .defaultEmptyArray("KeepOption"))
+ .defaultArrayEmpty(KeepOption.class))
.addDocFooterParagraph(
"If nothing is specified for "
+ CONSTRAINTS_GROUP
@@ -447,7 +481,7 @@
KeepConstraint.FIELD_GET,
KeepConstraint.FIELD_SET))
.setDocReturn("Usage constraints for the target.")
- .defaultEmptyArray(KeepConstraint.class);
+ .defaultArrayEmpty(KeepConstraint.class);
}
private GroupMember bindingName() {
@@ -487,10 +521,19 @@
.defaultObjectClass();
}
+ private GroupMember classNamePattern() {
+ return new GroupMember("classNamePattern")
+ .setDocTitle(
+ "Define the " + CLASS_NAME_GROUP + " pattern by reference to a class-name pattern.")
+ .setDocReturn("The class-name pattern that defines the class.")
+ .defaultValue(ClassNamePattern.class, DEFAULT_INVALID_CLASS_NAME_PATTERN);
+ }
+
private Group createClassNamePatternGroup() {
return new Group(CLASS_NAME_GROUP)
.addMember(className())
.addMember(classConstant())
+ .addMember(classNamePattern())
.addDocFooterParagraph("If none are specified the default is to match any class name.");
}
@@ -604,7 +647,7 @@
"Mutually exclusive with all field and method properties",
"as use restricts the match to both types of members.")
.setDocReturn("The member access-flag constraints that must be met.")
- .defaultEmptyArray("MemberAccessFlags"));
+ .defaultArrayEmpty(MemberAccessFlags.class));
}
private String getMutuallyExclusiveForMethodProperties() {
@@ -635,7 +678,7 @@
.addParagraph(getMutuallyExclusiveForMethodProperties())
.addParagraph(getMethodDefaultDoc("any method-access flags"))
.setDocReturn("The method access-flag constraints that must be met.")
- .defaultEmptyArray("MethodAccessFlags"));
+ .defaultArrayEmpty(MethodAccessFlags.class));
}
private Group createMethodNameGroup() {
@@ -672,8 +715,7 @@
.addParagraph(getMutuallyExclusiveForMethodProperties())
.addParagraph(getMethodDefaultDoc("any return type"))
.setDocReturn("The pattern of the method return type.")
- .defaultType("TypePattern")
- .defaultValue("@TypePattern(name = \"\")"));
+ .defaultValue(TypePattern.class, DEFAULT_INVALID_TYPE_PATTERN));
}
private Group createMethodParametersGroup() {
@@ -685,8 +727,7 @@
.addParagraph(getMutuallyExclusiveForMethodProperties())
.addParagraph(getMethodDefaultDoc("any parameters"))
.setDocReturn("The list of qualified type names of the method parameters.")
- .defaultType("String[]")
- .defaultValue("{\"\"}"))
+ .defaultArrayValue(String.class, quote("")))
.addMember(
new GroupMember("methodParameterTypePatterns")
.setDocTitle(
@@ -694,8 +735,7 @@
.addParagraph(getMutuallyExclusiveForMethodProperties())
.addParagraph(getMethodDefaultDoc("any parameters"))
.setDocReturn("The list of type patterns for the method parameters.")
- .defaultType("TypePattern[]")
- .defaultValue("{@TypePattern(name = \"\")}"));
+ .defaultArrayValue(TypePattern.class, DEFAULT_INVALID_TYPE_PATTERN));
}
private Group createFieldAccessGroup() {
@@ -706,7 +746,7 @@
.addParagraph(getMutuallyExclusiveForFieldProperties())
.addParagraph(getFieldDefaultDoc("any field-access flags"))
.setDocReturn("The field access-flag constraints that must be met.")
- .defaultEmptyArray("FieldAccessFlags"));
+ .defaultArrayEmpty(FieldAccessFlags.class));
}
private Group createFieldNameGroup() {
@@ -742,8 +782,7 @@
.addParagraph(getMutuallyExclusiveForFieldProperties())
.addParagraph(getFieldDefaultDoc("any type"))
.setDocReturn("The type pattern for the field type.")
- .defaultType("TypePattern")
- .defaultValue("@TypePattern(name = \"\")"));
+ .defaultValue(TypePattern.class, DEFAULT_INVALID_TYPE_PATTERN));
}
private void generateClassAndMemberPropertiesWithClassAndMemberBinding() {
@@ -814,13 +853,37 @@
.printDoc(this::println);
println("@Target(ElementType.ANNOTATION_TYPE)");
println("@Retention(RetentionPolicy.CLASS)");
- println("public @interface TypePattern {");
+ println("public @interface " + simpleName(TypePattern.class) + " {");
println();
withIndent(() -> typePatternGroup().generate(this));
println();
println("}");
}
+ private void generateClassNamePattern() {
+ printCopyRight(2023);
+ printPackage("annotations");
+ printImports(ANNOTATION_IMPORTS);
+ DocPrinter.printer()
+ .setDocTitle("A pattern structure for matching names of classes and interfaces.")
+ .addParagraph(
+ "If no properties are set, the default pattern matches any name of a class or"
+ + " interface.")
+ .printDoc(this::println);
+ println("@Target(ElementType.ANNOTATION_TYPE)");
+ println("@Retention(RetentionPolicy.CLASS)");
+ println("public @interface " + simpleName(ClassNamePattern.class) + " {");
+ println();
+ withIndent(
+ () -> {
+ classNamePatternSimpleNameGroup().generate(this);
+ println();
+ classNamePatternPackageGroup().generate(this);
+ });
+ println();
+ println("}");
+ }
+
private void generateKeepBinding() {
printCopyRight(2022);
printPackage("annotations");
@@ -1151,6 +1214,7 @@
generateFieldAccessConstants();
generateTypePatternConstants();
+ generateClassNamePatternConstants();
});
println("}");
}
@@ -1431,6 +1495,18 @@
println();
}
+ private void generateClassNamePatternConstants() {
+ println("public static final class ClassNamePattern {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(ClassNamePattern.class);
+ classNamePatternSimpleNameGroup().generateConstants(this);
+ classNamePatternPackageGroup().generateConstants(this);
+ });
+ println("}");
+ println();
+ }
+
private static void writeFile(Path file, Consumer<Generator> fn) throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(byteStream);
@@ -1443,26 +1519,31 @@
Files.write(Paths.get(ToolHelper.getProjectRoot()).resolve(file), formatted.getBytes());
}
+ public static Path source(Path pkg, Class<?> clazz) {
+ return pkg.resolve(simpleName(clazz) + ".java");
+ }
+
public static void run() throws IOException {
writeFile(Paths.get("doc/keepanno-guide.md"), KeepAnnoMarkdownGenerator::generateMarkdownDoc);
Path keepAnnoRoot = Paths.get("src/keepanno/java/com/android/tools/r8/keepanno");
Path astPkg = keepAnnoRoot.resolve("ast");
- writeFile(astPkg.resolve("AnnotationConstants.java"), Generator::generateConstants);
+ writeFile(source(astPkg, AnnotationConstants.class), Generator::generateConstants);
Path annoPkg = Paths.get("src/keepanno/java/com/android/tools/r8/keepanno/annotations");
- writeFile(annoPkg.resolve("TypePattern.java"), Generator::generateTypePattern);
- writeFile(annoPkg.resolve("KeepBinding.java"), Generator::generateKeepBinding);
- writeFile(annoPkg.resolve("KeepTarget.java"), Generator::generateKeepTarget);
- writeFile(annoPkg.resolve("KeepCondition.java"), Generator::generateKeepCondition);
- writeFile(annoPkg.resolve("KeepForApi.java"), Generator::generateKeepForApi);
- writeFile(annoPkg.resolve("UsesReflection.java"), Generator::generateUsesReflection);
+ writeFile(source(annoPkg, TypePattern.class), Generator::generateTypePattern);
+ writeFile(source(annoPkg, ClassNamePattern.class), Generator::generateClassNamePattern);
+ writeFile(source(annoPkg, KeepBinding.class), Generator::generateKeepBinding);
+ writeFile(source(annoPkg, KeepTarget.class), Generator::generateKeepTarget);
+ writeFile(source(annoPkg, KeepCondition.class), Generator::generateKeepCondition);
+ writeFile(source(annoPkg, KeepForApi.class), Generator::generateKeepForApi);
+ writeFile(source(annoPkg, UsesReflection.class), Generator::generateUsesReflection);
writeFile(
- annoPkg.resolve("UsedByReflection.java"),
+ source(annoPkg, UsedByReflection.class),
g -> g.generateUsedByX("UsedByReflection", "accessed reflectively"));
writeFile(
- annoPkg.resolve("UsedByNative.java"),
+ source(annoPkg, UsedByNative.class),
g -> g.generateUsedByX("UsedByNative", "accessed from native code via JNI"));
}
}