[KeepAnno] Add user guide for @UsedByReflection
Bug: b/248408342
Change-Id: I3b77260218c63f60627cbeb92225e7b98776ef83
diff --git a/doc/keepanno-guide.md b/doc/keepanno-guide.md
index 4f7665a..d91f5b1 100644
--- a/doc/keepanno-guide.md
+++ b/doc/keepanno-guide.md
@@ -154,7 +154,82 @@
## Annotating code used by reflection (or via JNI)<a id="used-by-reflection"></a>
-TODO
+Sometimes reflecting code cannot be annotated. For example, the reflection can
+be done in native code or in a library outside your control. In such cases you
+can annotate the code that is being used by reflection with either
+[@UsedByReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsedByReflection.html) or [@UsedByNative](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsedByNative.html). These two annotations are equivalent.
+Use the one that best matches why the annotation is needed.
+
+Let's consider some code with reflection outside our control.
+For example, the same field printing as in the above example might be part of a library.
+
+In this example, the `MyClassWithFields` is a class you are passing to the
+field-printing utility of the library. Since the library is reflectively accessing each field
+we annotate them with the [@UsedByReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsedByReflection.html) annotation.
+
+We could additionally add the [@UsedByReflection.constraints](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsedByReflection.html#constraints()) property as we did previously.
+We elide it here for brevity.
+
+
+```
+public static class MyClassWithFields implements PrintableFieldInterface {
+ @UsedByReflection final int intField = 42;
+
+ @UsedByReflection String stringField = "Hello!";
+}
+
+public static void run() throws Exception {
+ new FieldValuePrinterLibrary().printFieldValues(new MyClassWithFields());
+}
+```
+
+
+Rather than annotate the individual fields we can annotate the holder and add a specification
+similar to the [@KeepTarget](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepTarget.html). The [@UsedByReflection.kind](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsedByReflection.html#kind()) specifies that only the fields are
+used reflectively. In particular, the "field printer" example we are considering here does not
+make reflective assumptions about the holder class, so we should not constrain it.
+
+To be more precise let's add the [@UsedByReflection.constraints](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsedByReflection.html#constraints()) property now. This specifies
+that the fields are looked up, their names are used/assumed and their values are read.
+
+
+```
+@UsedByReflection(
+ kind = KeepItemKind.ONLY_FIELDS,
+ constraints = {KeepConstraint.LOOKUP, KeepConstraint.NAME, KeepConstraint.FIELD_GET})
+public static class MyClassWithFields implements PrintableFieldInterface {
+ final int intField = 42;
+ String stringField = "Hello!";
+}
+```
+
+
+Our use of [@UsedByReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsedByReflection.html) is still not as flexible as the original [@UsesReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsesReflection.html). In
+particular, if we change our code to no longer have any call to the library method
+`printFieldValues` the shrinker will still keep all of the fields on our annotated class.
+
+This is because the [@UsesReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsesReflection.html) implicitly encodes as a precondition that the annotated
+method is actually used in the program. If not, the [@UsesReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsesReflection.html) annotation is not
+"active".
+
+Luckily we can specify the same precondition using [@UsedByReflection.preconditions](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsedByReflection.html#preconditions()).
+
+
+```
+@UsedByReflection(
+ preconditions = {
+ @KeepCondition(
+ classConstant = FieldValuePrinterLibrary.class,
+ methodName = "printFieldValues")
+ },
+ kind = KeepItemKind.ONLY_FIELDS,
+ constraints = {KeepConstraint.LOOKUP, KeepConstraint.NAME, KeepConstraint.FIELD_GET})
+public static class MyClassWithFields implements PrintableFieldInterface {
+ final int intField = 42;
+ String stringField = "Hello!";
+}
+```
+
## Annotating APIs<a id="apis"></a>
diff --git a/doc/keepanno-guide.template.md b/doc/keepanno-guide.template.md
index 6e3efde..7b16a54 100644
--- a/doc/keepanno-guide.template.md
+++ b/doc/keepanno-guide.template.md
@@ -86,7 +86,24 @@
## [Annotating code used by reflection (or via JNI)](used-by-reflection)
-TODO
+Sometimes reflecting code cannot be annotated. For example, the reflection can
+be done in native code or in a library outside your control. In such cases you
+can annotate the code that is being used by reflection with either
+`@UsedByReflection` or `@UsedByNative`. These two annotations are equivalent.
+Use the one that best matches why the annotation is needed.
+
+Let's consider some code with reflection outside our control.
+[[[INCLUDE DOC:UsedByReflectionFieldPrinterOnFields]]]
+
+[[[INCLUDE CODE:UsedByReflectionFieldPrinterOnFields]]]
+
+[[[INCLUDE DOC:UsedByReflectionFieldPrinterOnClass]]]
+
+[[[INCLUDE CODE:UsedByReflectionFieldPrinterOnClass]]]
+
+[[[INCLUDE DOC:UsedByReflectionFieldPrinterConditional]]]
+
+[[[INCLUDE CODE:UsedByReflectionFieldPrinterConditional]]]
## [Annotating APIs](apis)
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 d4b3f1d..91751e6 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
@@ -829,9 +829,6 @@
if (!descriptor.equals(itemDescriptor)) {
throw parsingContext.error("must reference its class context " + className);
}
- if (itemPattern.isMemberItemPattern() && items.size() == 1) {
- throw parsingContext.error("kind must include its class");
- }
if (!holderPattern.getInstanceOfPattern().isAny()) {
throw parsingContext.error("cannot define an 'extends' pattern.");
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionDocumentationTest.java b/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionDocumentationTest.java
index 0cf7639..034ac0a 100644
--- a/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionDocumentationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionDocumentationTest.java
@@ -6,9 +6,11 @@
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.KeepCondition;
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;
@@ -22,8 +24,17 @@
@RunWith(Parameterized.class)
public class UsesReflectionDocumentationTest extends TestBase {
+ static final String EXPECTED_METHOD_EXAMPLE = StringUtils.joinLines("on Base", "on Sub");
+ static final String EXPECTED_FIELD_EXAMPLE =
+ StringUtils.joinLines("intField = 42", "stringField = Hello!");
+
static final String EXPECTED =
- StringUtils.lines("on Base", "on Sub", "intField = 42", "stringField = Hello!");
+ StringUtils.lines(
+ EXPECTED_METHOD_EXAMPLE,
+ EXPECTED_FIELD_EXAMPLE,
+ EXPECTED_FIELD_EXAMPLE,
+ EXPECTED_FIELD_EXAMPLE,
+ EXPECTED_FIELD_EXAMPLE);
private final TestParameters parameters;
@@ -58,7 +69,8 @@
}
public List<Class<?>> getExampleClasses() {
- return ImmutableList.of(Example1.class, Example2.class);
+ return ImmutableList.of(
+ Example1.class, Example2.class, Example3.class, Example4.class, Example5.class);
}
static class Example1 {
@@ -160,11 +172,137 @@
}
}
+ static class Example3 {
+
+ interface PrintableFieldInterface {}
+
+ /* INCLUDE DOC: UsedByReflectionFieldPrinterOnFields
+ For example, the same field printing as in the above example might be part of a library.
+
+ In this example, the `MyClassWithFields` is a class you are passing to the
+ field-printing utility of the library. Since the library is reflectively accessing each field
+ we annotate them with the `@UsedByReflection` annotation.
+
+ We could additionally add the `@UsedByReflection#constraints` property as we did previously.
+ We elide it here for brevity.
+ INCLUDE END */
+
+ // INCLUDE CODE: UsedByReflectionFieldPrinterOnFields
+ public static class MyClassWithFields implements PrintableFieldInterface {
+ @UsedByReflection final int intField = 42;
+
+ @UsedByReflection String stringField = "Hello!";
+ }
+
+ public static void run() throws Exception {
+ new FieldValuePrinterLibrary().printFieldValues(new MyClassWithFields());
+ }
+
+ // INCLUDE END
+
+ public static class FieldValuePrinterLibrary {
+
+ public void printFieldValues(PrintableFieldInterface objectWithFields) throws Exception {
+ for (Field field : objectWithFields.getClass().getDeclaredFields()) {
+ System.out.println(field.getName() + " = " + field.get(objectWithFields));
+ }
+ }
+ }
+ }
+
+ static class Example4 {
+
+ interface PrintableFieldInterface {}
+
+ /* INCLUDE DOC: UsedByReflectionFieldPrinterOnClass
+ Rather than annotate the individual fields we can annotate the holder and add a specification
+ similar to the `@KeepTarget`. The `@UsedByReflection#kind` specifies that only the fields are
+ used reflectively. In particular, the "field printer" example we are considering here does not
+ make reflective assumptions about the holder class, so we should not constrain it.
+
+ To be more precise let's add the `@UsedByReflection#constraints` property now. This specifies
+ that the fields are looked up, their names are used/assumed and their values are read.
+ INCLUDE END */
+
+ // INCLUDE CODE: UsedByReflectionFieldPrinterOnClass
+ @UsedByReflection(
+ kind = KeepItemKind.ONLY_FIELDS,
+ constraints = {KeepConstraint.LOOKUP, KeepConstraint.NAME, KeepConstraint.FIELD_GET})
+ public static class MyClassWithFields implements PrintableFieldInterface {
+ final int intField = 42;
+ String stringField = "Hello!";
+ }
+
+ // INCLUDE END
+
+ public static void run() throws Exception {
+ new FieldValuePrinterLibrary().printFieldValues(new MyClassWithFields());
+ }
+
+ public static class FieldValuePrinterLibrary {
+
+ public void printFieldValues(PrintableFieldInterface objectWithFields) throws Exception {
+ for (Field field : objectWithFields.getClass().getDeclaredFields()) {
+ System.out.println(field.getName() + " = " + field.get(objectWithFields));
+ }
+ }
+ }
+ }
+
+ static class Example5 {
+
+ interface PrintableFieldInterface {}
+
+ /* INCLUDE DOC: UsedByReflectionFieldPrinterConditional
+ Our use of `@UsedByReflection` is still not as flexible as the original `@UsesReflection`. In
+ particular, if we change our code to no longer have any call to the library method
+ `printFieldValues` the shrinker will still keep all of the fields on our annotated class.
+
+ This is because the `@UsesReflection` implicitly encodes as a precondition that the annotated
+ method is actually used in the program. If not, the `@UsesReflection` annotation is not
+ "active".
+
+ Luckily we can specify the same precondition using `@UsedByReflection#preconditions`.
+ INCLUDE END */
+
+ // INCLUDE CODE: UsedByReflectionFieldPrinterConditional
+ @UsedByReflection(
+ preconditions = {
+ @KeepCondition(
+ classConstant = FieldValuePrinterLibrary.class,
+ methodName = "printFieldValues")
+ },
+ kind = KeepItemKind.ONLY_FIELDS,
+ constraints = {KeepConstraint.LOOKUP, KeepConstraint.NAME, KeepConstraint.FIELD_GET})
+ public static class MyClassWithFields implements PrintableFieldInterface {
+ final int intField = 42;
+ String stringField = "Hello!";
+ }
+
+ // INCLUDE END
+
+ public static void run() throws Exception {
+ new FieldValuePrinterLibrary().printFieldValues(new MyClassWithFields());
+ }
+
+ public static class FieldValuePrinterLibrary {
+
+ public void printFieldValues(PrintableFieldInterface objectWithFields) throws Exception {
+ for (Field field : objectWithFields.getClass().getDeclaredFields()) {
+ System.out.println(field.getName() + " = " + field.get(objectWithFields));
+ }
+ }
+ }
+ }
+
static class TestClass {
public static void main(String[] args) throws Exception {
Example1.run();
Example2.run();
+ Example3.run();
+ Example4.run();
+ Example5.run();
}
}
}