[KeepAnno] Task to create javadoc for keep annotations
Bug: b/248408342
Change-Id: I83ab88e9ae6d384051fa59c81c0efaaa590c273c
diff --git a/d8_r8/keepanno/build.gradle.kts b/d8_r8/keepanno/build.gradle.kts
index 376472a..3d981f6 100644
--- a/d8_r8/keepanno/build.gradle.kts
+++ b/d8_r8/keepanno/build.gradle.kts
@@ -34,4 +34,9 @@
dependsOn(gradle.includedBuild("shared").task(":downloadDeps"))
from(sourceSets.main.get().output)
}
+
+ val keepAnnoAnnotationsDoc by registering(Javadoc::class) {
+ source = sourceSets.main.get().allJava
+ include("com/android/tools/r8/keepanno/annotations/*")
+ }
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java
index e9edfaa..c7f0e87 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java
@@ -24,14 +24,15 @@
* <p>The annotation's 'additionalPreconditions' is optional and can specify additional conditions
* that should be satisfied for the annotation to be in effect.
*
- * <p>The translation of the @UsesReflection annotation into a @KeepEdge is as follows:
+ * <p>The translation of the {@link UsesReflection} annotation into a {@link KeepEdge} is as
+ * follows:
*
* <p>Assume the item of the annotation is denoted by 'CTX' and referred to as its context.
*
* <pre>
- * @UsesReflection(value = targets, [additionalPreconditions = preconditions])
- * ==>
- * @KeepEdge(
+ * UsesReflection(value = targets, [additionalPreconditions = preconditions])
+ * ===
+ * KeepEdge(
* consequences = targets,
* preconditions = {createConditionFromContext(CTX)} + preconditions
* )
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
new file mode 100644
index 0000000..0914396
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionDocumentationTest.java
@@ -0,0 +1,111 @@
+// 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.doctests;
+
+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.KeepTarget;
+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.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UsesReflectionDocumentationTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("on Base", "on Sub");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public UsesReflectionDocumentationTest(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 testWithRuleExtraction() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getInputClasses())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, BaseClass.class, SubClass.class, MyClass.class);
+ }
+
+ static class BaseClass {
+ void hiddenMethod() {
+ System.out.println("on Base");
+ }
+ }
+
+ static class SubClass extends BaseClass {
+ void hiddenMethod() {
+ System.out.println("on Sub");
+ }
+ }
+
+ /// DOC START: UsesReflection on virtual method
+ /* DOC TEXT START:
+ <p>If for example, your program is reflectively invoking a virtual method on some base class, you
+ should annotate the method that is performing the reflection with an annotation describing what
+ assumptions the reflective code is making.
+
+ <p>In the following example, the method `foo` is looking up the method with the name
+ `hiddenMethod` on objects that are instances of `BaseClass`. It is then invoking the method with
+ no other arguments than the receiver.
+
+ <p>The minimal requirement for this code to work is therefore that all methods with the name
+ `hiddenMethod` and the empty list of parameters are targeted if they are objects that are
+ instances of the class `BaseClass` or subclasses thereof.
+
+ <p>By placing the `UsesReflection` annotation on the method `foo` the annotation is only in
+ effect if the method `foo` is determined to be used by the shrinker.
+ So, if `foo` turns out to be dead code then the shrinker can remove `foo` and also ignore the
+ keep annotation.
+ DOC TEXT END */
+ static class MyClass {
+
+ @UsesReflection({
+ @KeepTarget(
+ instanceOfClassConstant = BaseClass.class,
+ methodName = "hiddenMethod",
+ methodParameters = {})
+ })
+ public void foo(BaseClass base) throws Exception {
+ base.getClass().getDeclaredMethod("hiddenMethod").invoke(base);
+ }
+ }
+
+ // DOC END
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ new MyClass().foo(new BaseClass());
+ new MyClass().foo(new SubClass());
+ }
+ }
+}
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 506beb3..2c31a25 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
@@ -784,7 +784,7 @@
KeepItemKind.ONLY_CLASS.name(),
"if annotating a member. Also, it is never valid to use kind",
KeepItemKind.ONLY_MEMBERS.name(),
- "as the API surface must keep the class if any member it to be accessible.")
+ "as the API surface must keep the class if any member is to be accessible.")
.generate(this);
println();
generateMemberPropertiesNoBinding();
@@ -810,14 +810,18 @@
"The annotation's 'additionalPreconditions' is optional and can specify additional"
+ " conditions that should be satisfied for the annotation to be in effect.")
.addParagraph(
- "The translation of the @UsesReflection annotation into a @KeepEdge is as follows:")
+ "The translation of the "
+ + docLink(UsesReflection.class)
+ + " annotation into a "
+ + docLink(KeepEdge.class)
+ + " is as follows:")
.addParagraph(
"Assume the item of the annotation is denoted by 'CTX' and referred to as its"
+ " context.")
.addCodeBlock(
- "@UsesReflection(value = targets, [additionalPreconditions = preconditions])",
- "==>",
- "@KeepEdge(",
+ "UsesReflection(value = targets, [additionalPreconditions = preconditions])",
+ "===",
+ "KeepEdge(",
" consequences = targets,",
" preconditions = {createConditionFromContext(CTX)} + preconditions",
")",
@@ -868,10 +872,14 @@
.setDocTitle("Annotation to mark a class, field or method as being " + doc + ".")
.addParagraph(
"Note: Before using this annotation, consider if instead you can annotate the code"
- + " that is doing reflection with {@link UsesReflection}. Annotating the"
+ + " that is doing reflection with "
+ + docLink(UsesReflection.class)
+ + ". Annotating the"
+ " reflecting code is generally more clear and maintainable, and it also"
+ " naturally gives rise to edges that describe just the reflected aspects of the"
- + " program. The {@link UsedByReflection} annotation is suitable for cases where"
+ + " program. The "
+ + docLink(UsedByReflection.class)
+ + " annotation is suitable for cases where"
+ " the reflecting code is not under user control, or in migrating away from"
+ " rules.")
.addParagraph(
@@ -922,6 +930,10 @@
println("}");
}
+ private String docLink(Class<?> clazz) {
+ return "{@link " + simpleName(clazz) + "}";
+ }
+
private String docLink(KeepItemKind kind) {
return "{@link KeepItemKind#" + kind.name() + "}";
}