Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 1 | [comment]: <> (DO NOT EDIT - GENERATED FILE) |
| 2 | [comment]: <> (Changes should be made in doc/keepanno-guide.template.md) |
| 3 | |
| 4 | # Guide to Keep Annotations |
| 5 | |
| 6 | ## Disclaimer |
| 7 | |
| 8 | The annotation library described here is in development and considered to be in |
| 9 | its prototype phase. As such it is not yet feature complete, but we are actively |
| 10 | working on supporting all of the use cases we know of. Once the design exits the |
| 11 | prototype phase, it is intended to move to an R8 independent library as part of |
| 12 | androidx. All feedback: criticism, comments and suggestions are very welcome! |
| 13 | |
| 14 | [File new feature requests and |
| 15 | bugs](https://issuetracker.google.com/issues/new?component=326788) in the |
| 16 | [R8 component](https://issuetracker.google.com/issues?q=status:open%20componentid:326788). |
| 17 | |
| 18 | |
Ian Zerny | 4e219c1 | 2023-11-27 13:27:55 +0100 | [diff] [blame] | 19 | ## Table of contents |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 20 | |
Ian Zerny | 4e219c1 | 2023-11-27 13:27:55 +0100 | [diff] [blame] | 21 | - [Introduction](#introduction) |
| 22 | - [Build configuration](#build-configuration) |
| 23 | - [Annotating code using reflection](#using-reflection) |
Ian Zerny | 8442796 | 2023-12-06 10:57:30 +0100 | [diff] [blame] | 24 | - [Invoking methods](#using-reflection-methods) |
| 25 | - [Accessing fields](#using-reflection-fields) |
Ian Zerny | e9981bd | 2024-01-18 13:26:54 +0100 | [diff] [blame] | 26 | - [Accessing annotations](#using-reflection-annotations) |
Ian Zerny | 4e219c1 | 2023-11-27 13:27:55 +0100 | [diff] [blame] | 27 | - [Annotating code used by reflection (or via JNI)](#used-by-reflection) |
| 28 | - [Annotating APIs](#apis) |
Ian Zerny | b7199f2 | 2024-07-03 15:07:59 +0200 | [diff] [blame] | 29 | - [Constraints](#constraints) |
| 30 | - [Defaults](#constraints-defaults) |
| 31 | - [Generic signatures](#constraints-signatures) |
Ian Zerny | 4e219c1 | 2023-11-27 13:27:55 +0100 | [diff] [blame] | 32 | - [Migrating rules to annotations](#migrating-rules) |
| 33 | - [My use case is not covered!](#other-uses) |
| 34 | - [Troubleshooting](#troubleshooting) |
| 35 | |
| 36 | |
| 37 | |
Ian Zerny | 6ad10ce | 2024-01-04 14:40:27 +0100 | [diff] [blame] | 38 | ## Introduction<a name="introduction"></a> |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 39 | |
| 40 | When using a Java/Kotlin shrinker such as R8 or Proguard, developers must inform |
| 41 | the shrinker about parts of the program that are used either externally from the |
| 42 | program itself or internally via reflection and therefore must be kept. |
| 43 | |
| 44 | Traditionally these aspects would be kept by writing keep rules in a |
| 45 | configuration file and passing that to the shrinker. |
| 46 | |
| 47 | The keep annotations described in this document represent an alternative method |
| 48 | using Java annotations. The motivation for using these annotations is foremost |
| 49 | to place the description of what to keep closer to the program point using |
| 50 | reflective behavior. Doing so more directly connects the reflective code with |
Ian Zerny | 8442796 | 2023-12-06 10:57:30 +0100 | [diff] [blame] | 51 | the keep specification and makes it easier to maintain as the code develops. |
| 52 | Often the keep annotations are only in effect if the annotated method is used, |
| 53 | allowing more precise shrinking. In addition, the annotations are defined |
| 54 | independent from keep rules and have a hopefully more clear and direct meaning. |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 55 | |
| 56 | |
Ian Zerny | 6ad10ce | 2024-01-04 14:40:27 +0100 | [diff] [blame] | 57 | ## Build configuration<a name="build-configuration"></a> |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 58 | |
| 59 | To use the keep annotations your build must include the library of |
| 60 | annotations. It is currently built as part of each R8 build and if used with R8, |
| 61 | you should use the matching version. You can find all archived builds at: |
| 62 | |
| 63 | ``` |
| 64 | https://storage.googleapis.com/r8-releases/raw/<version>/keepanno-annotations.jar |
| 65 | ``` |
| 66 | |
| 67 | Thus you may obtain version `8.2.34` by running: |
| 68 | |
| 69 | ``` |
| 70 | wget https://storage.googleapis.com/r8-releases/raw/8.2.34/keepanno-annotations.jar |
| 71 | ``` |
| 72 | |
| 73 | You will then need to set the system property |
| 74 | `com.android.tools.r8.enableKeepAnnotations` to instruct R8 to make use of the |
| 75 | annotations when shrinking: |
| 76 | |
| 77 | ``` |
| 78 | java -Dcom.android.tools.r8.enableKeepAnnotations=1 \ |
| 79 | -cp r8.jar com.android.tools.r8.R8 \ |
| 80 | # ... the rest of your R8 compilation command here ... |
| 81 | ``` |
| 82 | |
Ian Zerny | 8442796 | 2023-12-06 10:57:30 +0100 | [diff] [blame] | 83 | |
Ian Zerny | 6ad10ce | 2024-01-04 14:40:27 +0100 | [diff] [blame] | 84 | ## Annotating code using reflection<a name="using-reflection"></a> |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 85 | |
| 86 | The keep annotation library defines a family of annotations depending on your |
| 87 | use case. You should generally prefer [@UsesReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsesReflection.html) where applicable. |
Ian Zerny | 8442796 | 2023-12-06 10:57:30 +0100 | [diff] [blame] | 88 | Common uses of reflection are to lookup fields and methods on classes. Examples |
| 89 | of such use cases are detailed below. |
| 90 | |
| 91 | |
Ian Zerny | 6ad10ce | 2024-01-04 14:40:27 +0100 | [diff] [blame] | 92 | ### Invoking methods<a name="using-reflection-methods"></a> |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 93 | |
| 94 | For example, if your program is reflectively invoking a method, you |
| 95 | should annotate the method that is doing the reflection. The annotation must describe the |
| 96 | assumptions the reflective code makes. |
| 97 | |
Ian Zerny | 8442796 | 2023-12-06 10:57:30 +0100 | [diff] [blame] | 98 | In the following example, the method `callHiddenMethod` is looking up the method with the name |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 99 | `hiddenMethod` on objects that are instances of `BaseClass`. It is then invoking the method with |
| 100 | no other arguments than the receiver. |
| 101 | |
| 102 | The assumptions the code makes are that all methods with the name |
| 103 | `hiddenMethod` and the empty list of parameters must remain valid for `getDeclaredMethod` if they |
| 104 | are objects that are instances of the class `BaseClass` or subclasses thereof. |
| 105 | |
| 106 | |
| 107 | ``` |
Ian Zerny | 8442796 | 2023-12-06 10:57:30 +0100 | [diff] [blame] | 108 | public class MyHiddenMethodCaller { |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 109 | |
Ian Zerny | b7199f2 | 2024-07-03 15:07:59 +0200 | [diff] [blame] | 110 | @UsesReflection( |
| 111 | @KeepTarget( |
| 112 | instanceOfClassConstant = BaseClass.class, |
| 113 | methodName = "hiddenMethod", |
| 114 | methodParameters = {})) |
Ian Zerny | 8442796 | 2023-12-06 10:57:30 +0100 | [diff] [blame] | 115 | public void callHiddenMethod(BaseClass base) throws Exception { |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 116 | base.getClass().getDeclaredMethod("hiddenMethod").invoke(base); |
| 117 | } |
| 118 | } |
| 119 | ``` |
| 120 | |
| 121 | |
| 122 | |
Ian Zerny | 6ad10ce | 2024-01-04 14:40:27 +0100 | [diff] [blame] | 123 | ### Accessing fields<a name="using-reflection-fields"></a> |
Ian Zerny | 8442796 | 2023-12-06 10:57:30 +0100 | [diff] [blame] | 124 | |
| 125 | For example, if your program is reflectively accessing the fields on a class, you should |
| 126 | annotate the method that is doing the reflection. |
| 127 | |
| 128 | In the following example, the `printFieldValues` method takes in an object of |
| 129 | type `PrintableFieldInterface` and then looks for all the fields declared on the class |
| 130 | of the object. |
| 131 | |
| 132 | The [@KeepTarget](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepTarget.html) describes these field targets. Since the printing only cares about preserving |
Ian Zerny | b7199f2 | 2024-07-03 15:07:59 +0200 | [diff] [blame] | 133 | the fields, the [@KeepTarget.kind](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepTarget.html#kind()) is set to [KeepItemKind.ONLY_FIELDS](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepItemKind.html#ONLY_FIELDS). |
Ian Zerny | 8442796 | 2023-12-06 10:57:30 +0100 | [diff] [blame] | 134 | |
| 135 | |
| 136 | ``` |
Ian Zerny | b7199f2 | 2024-07-03 15:07:59 +0200 | [diff] [blame] | 137 | static class MyFieldValuePrinter { |
Ian Zerny | 8442796 | 2023-12-06 10:57:30 +0100 | [diff] [blame] | 138 | |
Ian Zerny | b7199f2 | 2024-07-03 15:07:59 +0200 | [diff] [blame] | 139 | @UsesReflection( |
| 140 | @KeepTarget( |
| 141 | instanceOfClassConstant = PrintableFieldInterface.class, |
| 142 | kind = KeepItemKind.ONLY_FIELDS)) |
Ian Zerny | 8442796 | 2023-12-06 10:57:30 +0100 | [diff] [blame] | 143 | public void printFieldValues(PrintableFieldInterface objectWithFields) throws Exception { |
| 144 | for (Field field : objectWithFields.getClass().getDeclaredFields()) { |
| 145 | System.out.println(field.getName() + " = " + field.get(objectWithFields)); |
| 146 | } |
| 147 | } |
| 148 | } |
| 149 | ``` |
| 150 | |
| 151 | |
Ian Zerny | e9981bd | 2024-01-18 13:26:54 +0100 | [diff] [blame] | 152 | ### Accessing annotations<a name="using-reflection-annotations"></a> |
| 153 | |
| 154 | If your program is reflectively inspecting annotations on classes, methods or fields, you |
| 155 | will need to declare additional "annotation constraints" about what assumptions are made |
| 156 | about the annotations. |
| 157 | |
| 158 | In the following example, we have defined an annotation that will record the printing name we |
| 159 | would like to use for fields instead of printing the concrete field name. That may be useful |
| 160 | so that the field can be renamed to follow coding conventions for example. |
| 161 | |
| 162 | We are only interested in matching objects that contain fields annotated by `MyNameAnnotation`, |
| 163 | that is specified using [@KeepTarget.fieldAnnotatedByClassConstant](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepTarget.html#fieldAnnotatedByClassConstant()). |
| 164 | |
| 165 | At runtime we need to be able to find the annotation too, so we add a constraint on the |
| 166 | annotation using [@KeepTarget.constrainAnnotations](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepTarget.html#constrainAnnotations()). |
| 167 | |
| 168 | Finally, for the sake of example, we don't actually care about the name of the fields |
| 169 | themselves, so we explicitly declare the smaller set of constraints to be |
| 170 | [KeepConstraint.LOOKUP](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepConstraint.html#LOOKUP) since we must find the fields via `Class.getDeclaredFields` as well as |
| 171 | [KeepConstraint.VISIBILITY_RELAX](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepConstraint.html#VISIBILITY_RELAX) and [KeepConstraint.FIELD_GET](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepConstraint.html#FIELD_GET) in order to be able to get |
| 172 | the actual field value without accessibility errors. |
| 173 | |
| 174 | The effect is that the default constraint [KeepConstraint.NAME](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepConstraint.html#NAME) is not specified which allows |
| 175 | the shrinker to rename the fields at will. |
| 176 | |
| 177 | |
| 178 | ``` |
| 179 | public class MyAnnotationPrinter { |
| 180 | |
| 181 | @Target(ElementType.FIELD) |
| 182 | @Retention(RetentionPolicy.RUNTIME) |
| 183 | public @interface MyNameAnnotation { |
| 184 | String value(); |
| 185 | } |
| 186 | |
| 187 | public static class MyClass { |
| 188 | @MyNameAnnotation("fieldOne") |
| 189 | public int mFieldOne = 1; |
| 190 | |
| 191 | @MyNameAnnotation("fieldTwo") |
| 192 | public int mFieldTwo = 2; |
| 193 | |
| 194 | public int mFieldThree = 3; |
| 195 | } |
| 196 | |
| 197 | @UsesReflection( |
| 198 | @KeepTarget( |
| 199 | fieldAnnotatedByClassConstant = MyNameAnnotation.class, |
| 200 | constrainAnnotations = @AnnotationPattern(constant = MyNameAnnotation.class), |
| 201 | constraints = { |
| 202 | KeepConstraint.LOOKUP, |
| 203 | KeepConstraint.VISIBILITY_RELAX, |
| 204 | KeepConstraint.FIELD_GET |
| 205 | })) |
| 206 | public void printMyNameAnnotatedFields(Object obj) throws Exception { |
| 207 | for (Field field : obj.getClass().getDeclaredFields()) { |
| 208 | if (field.isAnnotationPresent(MyNameAnnotation.class)) { |
| 209 | System.out.println( |
| 210 | field.getAnnotation(MyNameAnnotation.class).value() + " = " + field.get(obj)); |
| 211 | } |
| 212 | } |
| 213 | } |
| 214 | } |
| 215 | ``` |
| 216 | |
| 217 | |
| 218 | If the annotations that need to be kept are not runtime |
| 219 | visible annotations, then you must specify that by including the `RetentionPolicy.CLASS` value in the |
| 220 | [@AnnotationPattern.retention](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/AnnotationPattern.html#retention()) property. |
| 221 | An annotation is runtime visible if its definition is explicitly annotated with |
| 222 | `Retention(RetentionPolicy.RUNTIME)`. |
| 223 | |
| 224 | |
Ian Zerny | 8442796 | 2023-12-06 10:57:30 +0100 | [diff] [blame] | 225 | |
Ian Zerny | 6ad10ce | 2024-01-04 14:40:27 +0100 | [diff] [blame] | 226 | ## Annotating code used by reflection (or via JNI)<a name="used-by-reflection"></a> |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 227 | |
Ian Zerny | d8bc17b | 2024-01-04 09:48:47 +0100 | [diff] [blame] | 228 | Sometimes reflecting code cannot be annotated. For example, the reflection can |
| 229 | be done in native code or in a library outside your control. In such cases you |
| 230 | can annotate the code that is being used by reflection with either |
| 231 | [@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. |
| 232 | Use the one that best matches why the annotation is needed. |
| 233 | |
| 234 | Let's consider some code with reflection outside our control. |
| 235 | For example, the same field printing as in the above example might be part of a library. |
| 236 | |
| 237 | In this example, the `MyClassWithFields` is a class you are passing to the |
| 238 | field-printing utility of the library. Since the library is reflectively accessing each field |
| 239 | 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. |
| 240 | |
Ian Zerny | d8bc17b | 2024-01-04 09:48:47 +0100 | [diff] [blame] | 241 | |
| 242 | ``` |
Ian Zerny | 5f8936a | 2024-01-04 11:43:47 +0100 | [diff] [blame] | 243 | public class MyClassWithFields implements PrintableFieldInterface { |
Ian Zerny | d8bc17b | 2024-01-04 09:48:47 +0100 | [diff] [blame] | 244 | @UsedByReflection final int intField = 42; |
| 245 | |
| 246 | @UsedByReflection String stringField = "Hello!"; |
| 247 | } |
| 248 | |
| 249 | public static void run() throws Exception { |
| 250 | new FieldValuePrinterLibrary().printFieldValues(new MyClassWithFields()); |
| 251 | } |
| 252 | ``` |
| 253 | |
| 254 | |
| 255 | Rather than annotate the individual fields we can annotate the holder and add a specification |
| 256 | 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 |
| 257 | used reflectively. In particular, the "field printer" example we are considering here does not |
| 258 | make reflective assumptions about the holder class, so we should not constrain it. |
| 259 | |
Ian Zerny | d8bc17b | 2024-01-04 09:48:47 +0100 | [diff] [blame] | 260 | |
| 261 | ``` |
Ian Zerny | b7199f2 | 2024-07-03 15:07:59 +0200 | [diff] [blame] | 262 | @UsedByReflection(kind = KeepItemKind.ONLY_FIELDS) public class MyClassWithFields |
| 263 | implements PrintableFieldInterface { |
Ian Zerny | d8bc17b | 2024-01-04 09:48:47 +0100 | [diff] [blame] | 264 | final int intField = 42; |
| 265 | String stringField = "Hello!"; |
| 266 | } |
| 267 | ``` |
| 268 | |
| 269 | |
| 270 | 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 |
| 271 | particular, if we change our code to no longer have any call to the library method |
| 272 | `printFieldValues` the shrinker will still keep all of the fields on our annotated class. |
| 273 | |
| 274 | 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 |
| 275 | 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 |
| 276 | "active". |
| 277 | |
| 278 | 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()). |
| 279 | |
| 280 | |
| 281 | ``` |
| 282 | @UsedByReflection( |
| 283 | preconditions = { |
| 284 | @KeepCondition( |
| 285 | classConstant = FieldValuePrinterLibrary.class, |
| 286 | methodName = "printFieldValues") |
| 287 | }, |
Ian Zerny | b7199f2 | 2024-07-03 15:07:59 +0200 | [diff] [blame] | 288 | kind = KeepItemKind.ONLY_FIELDS) public class MyClassWithFields |
| 289 | implements PrintableFieldInterface { |
Ian Zerny | d8bc17b | 2024-01-04 09:48:47 +0100 | [diff] [blame] | 290 | final int intField = 42; |
| 291 | String stringField = "Hello!"; |
| 292 | } |
| 293 | ``` |
| 294 | |
Ian Zerny | e692bce | 2023-11-27 13:51:43 +0100 | [diff] [blame] | 295 | |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 296 | |
Ian Zerny | 6ad10ce | 2024-01-04 14:40:27 +0100 | [diff] [blame] | 297 | ## Annotating APIs<a name="apis"></a> |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 298 | |
Ian Zerny | b7199f2 | 2024-07-03 15:07:59 +0200 | [diff] [blame] | 299 | If your code is being shrunk before release as a library, then you need to keep |
| 300 | the API surface. For that you should use the [@KeepForApi](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepForApi.html) annotation. |
Ian Zerny | 5f8936a | 2024-01-04 11:43:47 +0100 | [diff] [blame] | 301 | |
| 302 | When annotating a class the default for [@KeepForApi](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepForApi.html) is to keep the class as well as all of its |
| 303 | public and protected members: |
| 304 | |
| 305 | |
| 306 | ``` |
| 307 | @KeepForApi |
| 308 | public class MyApi { |
| 309 | public void thisPublicMethodIsKept() { |
| 310 | /* ... */ |
| 311 | } |
| 312 | |
| 313 | protected void thisProtectedMethodIsKept() { |
| 314 | /* ... */ |
| 315 | } |
| 316 | |
| 317 | void thisPackagePrivateMethodIsNotKept() { |
| 318 | /* ... */ |
| 319 | } |
| 320 | |
| 321 | private void thisPrivateMethodIsNotKept() { |
| 322 | /* ... */ |
| 323 | } |
| 324 | } |
| 325 | ``` |
| 326 | |
| 327 | |
| 328 | The default can be changed using the [@KeepForApi.memberAccess](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepForApi.html#memberAccess()) property: |
| 329 | |
| 330 | |
| 331 | ``` |
| 332 | @KeepForApi( |
| 333 | memberAccess = { |
| 334 | MemberAccessFlags.PUBLIC, |
| 335 | MemberAccessFlags.PROTECTED, |
| 336 | MemberAccessFlags.PACKAGE_PRIVATE |
| 337 | }) |
| 338 | ``` |
| 339 | |
| 340 | |
| 341 | The [@KeepForApi](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepForApi.html) annotation can also be placed directly on members and avoid keeping |
| 342 | unannotated members. The holder class is implicitly kept. When annotating the members |
| 343 | directly, the access does not matter as illustrated here by annotating a package private method: |
| 344 | |
| 345 | |
| 346 | ``` |
| 347 | public class MyOtherApi { |
| 348 | |
| 349 | public void notKept() { |
| 350 | /* ... */ |
| 351 | } |
| 352 | |
| 353 | @KeepForApi |
| 354 | void isKept() { |
| 355 | /* ... */ |
| 356 | } |
| 357 | } |
| 358 | ``` |
| 359 | |
Ian Zerny | e692bce | 2023-11-27 13:51:43 +0100 | [diff] [blame] | 360 | |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 361 | |
Ian Zerny | b7199f2 | 2024-07-03 15:07:59 +0200 | [diff] [blame] | 362 | ## Constraints<a name="constraints"></a> |
| 363 | |
| 364 | When an item is kept (e.g., items matched by [@KeepTarget](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepTarget.html) or annotated by |
| 365 | [@UsedByReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsedByReflection.html) or [@KeepForApi](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepForApi.html)) you can additionally specify constraints |
| 366 | about what properties of that item must be kept. Typical constraints are to keep |
| 367 | the items *name* or its ability to be reflectively *looked up*. You may also be |
| 368 | interested in keeping the generic signature of an item or annotations associated |
| 369 | with it. |
| 370 | |
| 371 | ### Defaults<a name="constraints-defaults"></a> |
| 372 | |
| 373 | By default the constraints are to retain the item's name, its ability to be |
| 374 | looked-up as well as its normal usage. Its normal usage is: |
| 375 | |
| 376 | - to be instantiated, for class items; |
| 377 | - to be invoked, for method items; and |
| 378 | - to be get and/or set, for field items. |
| 379 | |
| 380 | Let us revisit the example reflectively accessing the fields on a class. |
| 381 | |
| 382 | Notice that printing the field names and values only requires looking up the field, printing |
| 383 | its name and getting its value. It does not require setting a new value on the field. |
| 384 | We can thus use a more restrictive set of constraints |
| 385 | by setting the [@KeepTarget.constraints](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepTarget.html#constraints()) property to just [KeepConstraint.LOOKUP](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepConstraint.html#LOOKUP), |
| 386 | [KeepConstraint.NAME](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepConstraint.html#NAME) and [KeepConstraint.FIELD_GET](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepConstraint.html#FIELD_GET). |
| 387 | |
| 388 | |
| 389 | ``` |
| 390 | static class MyFieldValuePrinter { |
| 391 | |
| 392 | @UsesReflection( |
| 393 | @KeepTarget( |
| 394 | instanceOfClassConstant = PrintableFieldInterface.class, |
| 395 | kind = KeepItemKind.ONLY_FIELDS, |
| 396 | constraints = {KeepConstraint.LOOKUP, KeepConstraint.NAME, KeepConstraint.FIELD_GET})) |
| 397 | public void printFieldValues(PrintableFieldInterface objectWithFields) throws Exception { |
| 398 | for (Field field : objectWithFields.getClass().getDeclaredFields()) { |
| 399 | System.out.println(field.getName() + " = " + field.get(objectWithFields)); |
| 400 | } |
| 401 | } |
| 402 | } |
| 403 | ``` |
| 404 | |
| 405 | |
| 406 | |
| 407 | ### Generic signatures<a name="constraints-signatures"></a> |
| 408 | |
| 409 | The generic signature information of an item is not kept by default, and |
| 410 | requires adding constraints to the targeted items. |
| 411 | |
| 412 | Imagine we had code that is making use of the template parameters for implementations of a |
| 413 | generic interface. The code below assumes direct implementations of the `WrappedValue` interface |
| 414 | and simply prints the type parameter used. |
| 415 | |
| 416 | Since we are reflecting on the class structure of implementations of `WrappedValue` we need to |
| 417 | keep it and any instance of it. |
| 418 | |
| 419 | We must also preserve the generic signatures of these classes. We add the |
| 420 | [KeepConstraint.GENERIC_SIGNATURE](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepConstraint.html#GENERIC_SIGNATURE) constraint by using the [@KeepTarget.constraintAdditions](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepTarget.html#constraintAdditions()) |
| 421 | property. This ensures that the default constraints are still in place in addition to the |
| 422 | constraint on generic signatures. |
| 423 | |
| 424 | |
| 425 | ``` |
| 426 | public class GenericSignaturePrinter { |
| 427 | |
| 428 | interface WrappedValue<T> { |
| 429 | T getValue(); |
| 430 | } |
| 431 | |
| 432 | @UsesReflection( |
| 433 | @KeepTarget( |
| 434 | instanceOfClassConstant = WrappedValue.class, |
| 435 | constraintAdditions = KeepConstraint.GENERIC_SIGNATURE)) |
| 436 | public static void printSignature(WrappedValue<?> obj) { |
| 437 | Class<? extends WrappedValue> clazz = obj.getClass(); |
| 438 | for (Type iface : clazz.getGenericInterfaces()) { |
| 439 | String typeName = iface.getTypeName(); |
| 440 | String param = typeName.substring(typeName.lastIndexOf('<') + 1, typeName.lastIndexOf('>')); |
| 441 | System.out.println(clazz.getName() + " uses type " + param); |
| 442 | } |
| 443 | } |
| 444 | ``` |
| 445 | |
| 446 | |
| 447 | |
Ian Zerny | 6ad10ce | 2024-01-04 14:40:27 +0100 | [diff] [blame] | 448 | ## Migrating rules to annotations<a name="migrating-rules"></a> |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 449 | |
Ian Zerny | e692bce | 2023-11-27 13:51:43 +0100 | [diff] [blame] | 450 | There is no automatic migration of keep rules. Keep annotations often invert the |
| 451 | direction and rules have no indication of where the reflection is taking |
| 452 | place or why. Thus, migrating existing keep rules requires user involvement. |
| 453 | Keep rules also have a tendency to be very general, matching a large |
| 454 | number of classes and members. Often the rules are much too broad and are |
| 455 | keeping more than needed which will have a negative impact on the shrinkers |
| 456 | ability to reduce size. |
| 457 | |
| 458 | First step in converting a rule is to determine the purpose of the rule. Is it |
| 459 | API surface or is it reflection? Note that a very general rule may be covering |
| 460 | several use cases and even a mix of both reflection and API usage. |
| 461 | |
| 462 | When migrating it is preferable to use [@UsesReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsesReflection.html) instead of |
| 463 | [@UsedByReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsedByReflection.html). For very general rules it might not be easy or worth it to |
| 464 | migrate without completely reevaluating the rule. If one still wants to replace |
| 465 | it by annotations, the general [@KeepEdge](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepEdge.html) can be used to define a context |
| 466 | independent keep annotation. |
| 467 | |
| 468 | For example, to keep all main methods in the program one could use: |
| 469 | |
| 470 | |
| 471 | ``` |
| 472 | @KeepEdge( |
| 473 | consequences = { |
| 474 | @KeepTarget( |
| 475 | kind = KeepItemKind.CLASS_AND_MEMBERS, |
| 476 | methodName = "main", |
| 477 | methodReturnType = "void", |
| 478 | methodParameters = {"java.lang.String[]"}, |
| 479 | methodAccess = {MethodAccessFlags.PUBLIC, MethodAccessFlags.STATIC}) |
| 480 | }) |
| 481 | public class SomeClass { |
| 482 | // ... |
| 483 | } |
| 484 | ``` |
| 485 | |
| 486 | |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 487 | |
Ian Zerny | 6ad10ce | 2024-01-04 14:40:27 +0100 | [diff] [blame] | 488 | ## My use case is not covered!<a name="other-uses"></a> |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 489 | |
Ian Zerny | e692bce | 2023-11-27 13:51:43 +0100 | [diff] [blame] | 490 | The annotation library is in active development and not all use cases are |
| 491 | described here or supported. Reach out to the R8 team by |
| 492 | [filing a new issue in our tracker](https://issuetracker.google.com/issues/new?component=326788). |
| 493 | Describe your use case and we will look at how best to support it. |
| 494 | |
Ian Zerny | 8f0adab | 2023-11-23 15:12:13 +0100 | [diff] [blame] | 495 | |
Ian Zerny | 6ad10ce | 2024-01-04 14:40:27 +0100 | [diff] [blame] | 496 | ## Troubleshooting<a name="troubleshooting"></a> |
Ian Zerny | e692bce | 2023-11-27 13:51:43 +0100 | [diff] [blame] | 497 | |
| 498 | If an annotation is not working as expected it may be helpful to inspect the |
| 499 | rules that have been extracted for the annotation. This can be done by |
| 500 | inspecting the configuration output of the shrinker. For R8 you can use the |
| 501 | command line argument `--pg-conf-output <path>` to emit the full configuration |
| 502 | used by R8. |