Merge commit '1c67df7e75812c0da636efa56997b3ef69afb798' into dev-release
Change-Id: I983dec38b0c8225b54ff2f0250a1b883cbab4be8
diff --git a/d8_r8/build.gradle.kts b/d8_r8/build.gradle.kts
index 850a0c5..187f708 100644
--- a/d8_r8/build.gradle.kts
+++ b/d8_r8/build.gradle.kts
@@ -19,4 +19,8 @@
val r8 by registering() {
dependsOn(gradle.includedBuild("main").task(":r8WithRelocatedDeps"))
}
+
+ val r8lib by registering() {
+ dependsOn(gradle.includedBuild("test").task(":assembleR8LibWithRelocatedDeps"))
+ }
}
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index 1ec0e8b..418e0d5 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -251,7 +251,7 @@
osFolder = "windows"
}
if (os.isMacOsX) {
- osFolder = "osx"
+ osFolder = "osx/Contents/Home"
}
return getRoot().resolveAll("third_party", "openjdk", jdk.folder, osFolder)
}
diff --git a/doc/keepanno-guide.md b/doc/keepanno-guide.md
new file mode 100644
index 0000000..9970ae4
--- /dev/null
+++ b/doc/keepanno-guide.md
@@ -0,0 +1,113 @@
+[comment]: <> (DO NOT EDIT - GENERATED FILE)
+[comment]: <> (Changes should be made in doc/keepanno-guide.template.md)
+
+# Guide to Keep Annotations
+
+## Disclaimer
+
+The annotation library described here is in development and considered to be in
+its prototype phase. As such it is not yet feature complete, but we are actively
+working on supporting all of the use cases we know of. Once the design exits the
+prototype phase, it is intended to move to an R8 independent library as part of
+androidx. All feedback: criticism, comments and suggestions are very welcome!
+
+[File new feature requests and
+bugs](https://issuetracker.google.com/issues/new?component=326788) in the
+[R8 component](https://issuetracker.google.com/issues?q=status:open%20componentid:326788).
+
+
+
+## Introduction
+
+When using a Java/Kotlin shrinker such as R8 or Proguard, developers must inform
+the shrinker about parts of the program that are used either externally from the
+program itself or internally via reflection and therefore must be kept.
+
+Traditionally these aspects would be kept by writing keep rules in a
+configuration file and passing that to the shrinker.
+
+The keep annotations described in this document represent an alternative method
+using Java annotations. The motivation for using these annotations is foremost
+to place the description of what to keep closer to the program point using
+reflective behavior. Doing so more directly connects the reflective code with
+the keep specification and makes it easier to maintain as the code develops. In
+addition, the annotations are defined independent from keep rules and have a
+hopefully more clear and direct meaning.
+
+
+## Build configuration
+
+To use the keep annotations your build must include the library of
+annotations. It is currently built as part of each R8 build and if used with R8,
+you should use the matching version. You can find all archived builds at:
+
+```
+https://storage.googleapis.com/r8-releases/raw/<version>/keepanno-annotations.jar
+```
+
+Thus you may obtain version `8.2.34` by running:
+
+```
+wget https://storage.googleapis.com/r8-releases/raw/8.2.34/keepanno-annotations.jar
+```
+
+You will then need to set the system property
+`com.android.tools.r8.enableKeepAnnotations` to instruct R8 to make use of the
+annotations when shrinking:
+
+```
+java -Dcom.android.tools.r8.enableKeepAnnotations=1 \
+ -cp r8.jar com.android.tools.r8.R8 \
+ # ... the rest of your R8 compilation command here ...
+```
+
+### Annotating code using reflection
+
+The keep annotation library defines a family of annotations depending on your
+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.
+
+For example, if your program is reflectively invoking a method, you
+should annotate the method that is doing the reflection. The annotation must describe the
+assumptions the reflective code makes.
+
+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.
+
+The assumptions the code makes are that all methods with the name
+`hiddenMethod` and the empty list of parameters must remain valid for `getDeclaredMethod` if they
+are objects that are instances of the class `BaseClass` or subclasses thereof.
+
+
+```
+static class MyClass {
+
+ @UsesReflection({
+ @KeepTarget(
+ instanceOfClassConstant = BaseClass.class,
+ methodName = "hiddenMethod",
+ methodParameters = {})
+ })
+ public void foo(BaseClass base) throws Exception {
+ base.getClass().getDeclaredMethod("hiddenMethod").invoke(base);
+ }
+}
+```
+
+
+
+
+### Annotating code used by reflection (or via JNI)
+
+
+
+### Annotating APIs
+
+
+### Migrating rules to annotations
+
+
+### My use case is not covered!
+
+
+### Troubleshooting
diff --git a/doc/keepanno-guide.template.md b/doc/keepanno-guide.template.md
new file mode 100644
index 0000000..6e0a8df
--- /dev/null
+++ b/doc/keepanno-guide.template.md
@@ -0,0 +1,85 @@
+# Guide to Keep Annotations
+
+## Disclaimer
+
+The annotation library described here is in development and considered to be in
+its prototype phase. As such it is not yet feature complete, but we are actively
+working on supporting all of the use cases we know of. Once the design exits the
+prototype phase, it is intended to move to an R8 independent library as part of
+androidx. All feedback: criticism, comments and suggestions are very welcome!
+
+[File new feature requests and
+bugs](https://issuetracker.google.com/issues/new?component=326788) in the
+[R8 component](https://issuetracker.google.com/issues?q=status:open%20componentid:326788).
+
+
+
+## Introduction
+
+When using a Java/Kotlin shrinker such as R8 or Proguard, developers must inform
+the shrinker about parts of the program that are used either externally from the
+program itself or internally via reflection and therefore must be kept.
+
+Traditionally these aspects would be kept by writing keep rules in a
+configuration file and passing that to the shrinker.
+
+The keep annotations described in this document represent an alternative method
+using Java annotations. The motivation for using these annotations is foremost
+to place the description of what to keep closer to the program point using
+reflective behavior. Doing so more directly connects the reflective code with
+the keep specification and makes it easier to maintain as the code develops. In
+addition, the annotations are defined independent from keep rules and have a
+hopefully more clear and direct meaning.
+
+
+## Build configuration
+
+To use the keep annotations your build must include the library of
+annotations. It is currently built as part of each R8 build and if used with R8,
+you should use the matching version. You can find all archived builds at:
+
+```
+https://storage.googleapis.com/r8-releases/raw/<version>/keepanno-annotations.jar
+```
+
+Thus you may obtain version `8.2.34` by running:
+
+```
+wget https://storage.googleapis.com/r8-releases/raw/8.2.34/keepanno-annotations.jar
+```
+
+You will then need to set the system property
+`com.android.tools.r8.enableKeepAnnotations` to instruct R8 to make use of the
+annotations when shrinking:
+
+```
+java -Dcom.android.tools.r8.enableKeepAnnotations=1 \
+ -cp r8.jar com.android.tools.r8.R8 \
+ # ... the rest of your R8 compilation command here ...
+```
+
+### Annotating code using reflection
+
+The keep annotation library defines a family of annotations depending on your
+use case. You should generally prefer `@UsesReflection` where applicable.
+
+[[[INCLUDE DOC:UsesReflectionOnVirtualMethod]]]
+
+[[[INCLUDE CODE:UsesReflectionOnVirtualMethod]]]
+
+
+
+### Annotating code used by reflection (or via JNI)
+
+
+
+### Annotating APIs
+
+
+### Migrating rules to annotations
+
+
+### My use case is not covered!
+
+
+### Troubleshooting
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 9657a22..e97d2b5 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
@@ -31,7 +31,11 @@
@Retention(RetentionPolicy.CLASS)
public @interface KeepBinding {
- /** Name with which other bindings, conditions or targets can reference the bound item pattern. */
+ /**
+ * Name with which other bindings, conditions or targets can reference the bound item pattern.
+ *
+ * @return Name of the binding.
+ */
String bindingName();
/**
@@ -47,6 +51,8 @@
*
* <p>If unspecified the default for an item with no member patterns is ONLY_CLASS and if it does
* have member patterns the default is ONLY_MEMBERS
+ *
+ * @return The kind for this pattern.
*/
KeepItemKind kind() default KeepItemKind.DEFAULT;
@@ -67,6 +73,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class.
+ *
+ * @return The name of the binding that defines the class.
*/
String classFromBinding() default "";
@@ -81,6 +89,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class name.
+ *
+ * @return The qualified class name that defines the class.
*/
String className() default "";
@@ -95,6 +105,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class name.
+ *
+ * @return The class-constant that defines the class.
*/
Class<?> classConstant() default Object.class;
@@ -113,6 +125,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The qualified class name that defines what instance-of the class must be.
*/
String instanceOfClassName() default "";
@@ -134,6 +148,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The qualified class name that defines what instance-of the class must be.
*/
String instanceOfClassNameExclusive() default "";
@@ -152,6 +168,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The class constant that defines what instance-of the class must be.
*/
Class<?> instanceOfClassConstant() default Object.class;
@@ -173,6 +191,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The class constant that defines what instance-of the class must be.
*/
Class<?> instanceOfClassConstantExclusive() default Object.class;
@@ -182,8 +202,6 @@
* <p>The pattern is exclusive in that it does not match classes that are instances of the
* pattern, but only those that are instances of classes that are subclasses of the pattern.
*
- * <p>This property is deprecated, use instanceOfClassName instead.
- *
* <p>Mutually exclusive with the following other properties defining instance-of:
*
* <ul>
@@ -196,7 +214,11 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The class name that defines what the class must extend.
+ * @deprecated This property is deprecated, use {@link #instanceOfClassName} instead.
*/
+ @Deprecated
String extendsClassName() default "";
/**
@@ -205,8 +227,6 @@
* <p>The pattern is exclusive in that it does not match classes that are instances of the
* pattern, but only those that are instances of classes that are subclasses of the pattern.
*
- * <p>This property is deprecated, use instanceOfClassConstant instead.
- *
* <p>Mutually exclusive with the following other properties defining instance-of:
*
* <ul>
@@ -219,7 +239,11 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The class constant that defines what the class must extend.
+ * @deprecated This property is deprecated, use {@link #instanceOfClassConstant} instead.
*/
+ @Deprecated
Class<?> extendsClassConstant() default Object.class;
/**
@@ -227,6 +251,8 @@
*
* <p>Mutually exclusive with all field and method properties as use restricts the match to both
* types of members.
+ *
+ * @return The member access-flag constraints that must be met.
*/
MemberAccessFlags[] memberAccess() default {};
@@ -237,6 +263,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any
* method-access flags.
+ *
+ * @return The method access-flag constraints that must be met.
*/
MethodAccessFlags[] methodAccess() default {};
@@ -247,6 +275,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any method
* name.
+ *
+ * @return The exact method name of the method.
*/
String methodName() default "";
@@ -257,6 +287,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any return
* type.
+ *
+ * @return The qualified type name of the method return type.
*/
String methodReturnType() default "";
@@ -267,6 +299,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any
* parameters.
+ *
+ * @return The list of qualified type names of the method parameters.
*/
String[] methodParameters() default {"<default>"};
@@ -277,6 +311,8 @@
*
* <p>If none, and other properties define this item as a field, the default matches any
* field-access flags.
+ *
+ * @return The field access-flag constraints that must be met.
*/
FieldAccessFlags[] fieldAccess() default {};
@@ -287,6 +323,8 @@
*
* <p>If none, and other properties define this item as a field, the default matches any field
* name.
+ *
+ * @return The exact field name of the field.
*/
String fieldName() default "";
@@ -296,6 +334,8 @@
* <p>Mutually exclusive with all method properties.
*
* <p>If none, and other properties define this item as a field, the default matches any type.
+ *
+ * @return The qualified type name of the field type.
*/
String fieldType() default "";
}
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 7777fd8..e539cc4 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
@@ -45,6 +45,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class.
+ *
+ * @return The name of the binding that defines the class.
*/
String classFromBinding() default "";
@@ -59,6 +61,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class name.
+ *
+ * @return The qualified class name that defines the class.
*/
String className() default "";
@@ -73,6 +77,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class name.
+ *
+ * @return The class-constant that defines the class.
*/
Class<?> classConstant() default Object.class;
@@ -91,6 +97,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The qualified class name that defines what instance-of the class must be.
*/
String instanceOfClassName() default "";
@@ -112,6 +120,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The qualified class name that defines what instance-of the class must be.
*/
String instanceOfClassNameExclusive() default "";
@@ -130,6 +140,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The class constant that defines what instance-of the class must be.
*/
Class<?> instanceOfClassConstant() default Object.class;
@@ -151,6 +163,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The class constant that defines what instance-of the class must be.
*/
Class<?> instanceOfClassConstantExclusive() default Object.class;
@@ -160,8 +174,6 @@
* <p>The pattern is exclusive in that it does not match classes that are instances of the
* pattern, but only those that are instances of classes that are subclasses of the pattern.
*
- * <p>This property is deprecated, use instanceOfClassName instead.
- *
* <p>Mutually exclusive with the following other properties defining instance-of:
*
* <ul>
@@ -174,7 +186,11 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The class name that defines what the class must extend.
+ * @deprecated This property is deprecated, use {@link #instanceOfClassName} instead.
*/
+ @Deprecated
String extendsClassName() default "";
/**
@@ -183,8 +199,6 @@
* <p>The pattern is exclusive in that it does not match classes that are instances of the
* pattern, but only those that are instances of classes that are subclasses of the pattern.
*
- * <p>This property is deprecated, use instanceOfClassConstant instead.
- *
* <p>Mutually exclusive with the following other properties defining instance-of:
*
* <ul>
@@ -197,7 +211,11 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The class constant that defines what the class must extend.
+ * @deprecated This property is deprecated, use {@link #instanceOfClassConstant} instead.
*/
+ @Deprecated
Class<?> extendsClassConstant() default Object.class;
/**
@@ -205,6 +223,8 @@
*
* <p>Mutually exclusive with all other class and member pattern properties. When a member binding
* is referenced this item is defined to be that item, including its class and member patterns.
+ *
+ * @return The binding name that defines the member.
*/
String memberFromBinding() default "";
@@ -213,6 +233,8 @@
*
* <p>Mutually exclusive with all field and method properties as use restricts the match to both
* types of members.
+ *
+ * @return The member access-flag constraints that must be met.
*/
MemberAccessFlags[] memberAccess() default {};
@@ -223,6 +245,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any
* method-access flags.
+ *
+ * @return The method access-flag constraints that must be met.
*/
MethodAccessFlags[] methodAccess() default {};
@@ -233,6 +257,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any method
* name.
+ *
+ * @return The exact method name of the method.
*/
String methodName() default "";
@@ -243,6 +269,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any return
* type.
+ *
+ * @return The qualified type name of the method return type.
*/
String methodReturnType() default "";
@@ -253,6 +281,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any
* parameters.
+ *
+ * @return The list of qualified type names of the method parameters.
*/
String[] methodParameters() default {"<default>"};
@@ -263,6 +293,8 @@
*
* <p>If none, and other properties define this item as a field, the default matches any
* field-access flags.
+ *
+ * @return The field access-flag constraints that must be met.
*/
FieldAccessFlags[] fieldAccess() default {};
@@ -273,6 +305,8 @@
*
* <p>If none, and other properties define this item as a field, the default matches any field
* name.
+ *
+ * @return The exact field name of the field.
*/
String fieldName() default "";
@@ -282,6 +316,8 @@
* <p>Mutually exclusive with all method properties.
*
* <p>If none, and other properties define this item as a field, the default matches any type.
+ *
+ * @return The qualified type name of the field type.
*/
String fieldType() default "";
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
index 65454fe..cd3559c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
@@ -27,13 +27,17 @@
@Retention(RetentionPolicy.CLASS)
public @interface KeepForApi {
- /** Optional description to document the reason for this annotation. */
+ /**
+ * Optional description to document the reason for this annotation.
+ *
+ * @return The descriptive message. Defaults to no description.
+ */
String description() default "";
/**
* Additional targets to be kept as part of the API surface.
*
- * <p>Defaults to no additional targets.
+ * @return List of additional target consequences. Defaults to no additional target consequences.
*/
KeepTarget[] additionalTargets() default {};
@@ -46,6 +50,8 @@
*
* <p>It is not possible to use ONLY_CLASS if annotating a member. Also, it is never valid to use
* kind ONLY_MEMBERS as the API surface must keep the class if any member is to be accessible.
+ *
+ * @return The kind for this pattern.
*/
KeepItemKind kind() default KeepItemKind.DEFAULT;
@@ -54,6 +60,8 @@
*
* <p>Mutually exclusive with all field and method properties as use restricts the match to both
* types of members.
+ *
+ * @return The member access-flag constraints that must be met.
*/
MemberAccessFlags[] memberAccess() default {};
@@ -64,6 +72,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any
* method-access flags.
+ *
+ * @return The method access-flag constraints that must be met.
*/
MethodAccessFlags[] methodAccess() default {};
@@ -74,6 +84,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any method
* name.
+ *
+ * @return The exact method name of the method.
*/
String methodName() default "";
@@ -84,6 +96,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any return
* type.
+ *
+ * @return The qualified type name of the method return type.
*/
String methodReturnType() default "";
@@ -94,6 +108,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any
* parameters.
+ *
+ * @return The list of qualified type names of the method parameters.
*/
String[] methodParameters() default {"<default>"};
@@ -104,6 +120,8 @@
*
* <p>If none, and other properties define this item as a field, the default matches any
* field-access flags.
+ *
+ * @return The field access-flag constraints that must be met.
*/
FieldAccessFlags[] fieldAccess() default {};
@@ -114,6 +132,8 @@
*
* <p>If none, and other properties define this item as a field, the default matches any field
* name.
+ *
+ * @return The exact field name of the field.
*/
String fieldName() default "";
@@ -123,6 +143,8 @@
* <p>Mutually exclusive with all method properties.
*
* <p>If none, and other properties define this item as a field, the default matches any type.
+ *
+ * @return The qualified type name of the field type.
*/
String fieldType() default "";
}
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 59e7e7c..8ad5098 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
@@ -41,24 +41,34 @@
*
* <p>If unspecified the default for an item with no member patterns is ONLY_CLASS and if it does
* have member patterns the default is ONLY_MEMBERS
+ *
+ * @return The kind for this pattern.
*/
KeepItemKind kind() default KeepItemKind.DEFAULT;
/**
- * Define the options that do not need to be preserved for the target.
+ * Define the options that are allowed to be modified.
+ *
+ * <p>The specified options do not need to be preserved for the target.
*
* <p>Mutually exclusive with the property `disallow` also defining options.
*
* <p>If nothing is specified for options the default is "allow none" / "disallow all".
+ *
+ * @return Options allowed to be modified for the target.
*/
KeepOption[] allow() default {};
/**
- * Define the options that *must* be preserved for the target.
+ * Define the options that are not allowed to be modified.
+ *
+ * <p>The specified options *must* be preserved for the target.
*
* <p>Mutually exclusive with the property `allow` also defining options.
*
* <p>If nothing is specified for options the default is "allow none" / "disallow all".
+ *
+ * @return Options not allowed to be modified for the target.
*/
KeepOption[] disallow() default {};
@@ -79,6 +89,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class.
+ *
+ * @return The name of the binding that defines the class.
*/
String classFromBinding() default "";
@@ -93,6 +105,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class name.
+ *
+ * @return The qualified class name that defines the class.
*/
String className() default "";
@@ -107,6 +121,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class name.
+ *
+ * @return The class-constant that defines the class.
*/
Class<?> classConstant() default Object.class;
@@ -125,6 +141,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The qualified class name that defines what instance-of the class must be.
*/
String instanceOfClassName() default "";
@@ -146,6 +164,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The qualified class name that defines what instance-of the class must be.
*/
String instanceOfClassNameExclusive() default "";
@@ -164,6 +184,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The class constant that defines what instance-of the class must be.
*/
Class<?> instanceOfClassConstant() default Object.class;
@@ -185,6 +207,8 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The class constant that defines what instance-of the class must be.
*/
Class<?> instanceOfClassConstantExclusive() default Object.class;
@@ -194,8 +218,6 @@
* <p>The pattern is exclusive in that it does not match classes that are instances of the
* pattern, but only those that are instances of classes that are subclasses of the pattern.
*
- * <p>This property is deprecated, use instanceOfClassName instead.
- *
* <p>Mutually exclusive with the following other properties defining instance-of:
*
* <ul>
@@ -208,7 +230,11 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The class name that defines what the class must extend.
+ * @deprecated This property is deprecated, use {@link #instanceOfClassName} instead.
*/
+ @Deprecated
String extendsClassName() default "";
/**
@@ -217,8 +243,6 @@
* <p>The pattern is exclusive in that it does not match classes that are instances of the
* pattern, but only those that are instances of classes that are subclasses of the pattern.
*
- * <p>This property is deprecated, use instanceOfClassConstant instead.
- *
* <p>Mutually exclusive with the following other properties defining instance-of:
*
* <ul>
@@ -231,7 +255,11 @@
* </ul>
*
* <p>If none are specified the default is to match any class instance.
+ *
+ * @return The class constant that defines what the class must extend.
+ * @deprecated This property is deprecated, use {@link #instanceOfClassConstant} instead.
*/
+ @Deprecated
Class<?> extendsClassConstant() default Object.class;
/**
@@ -239,6 +267,8 @@
*
* <p>Mutually exclusive with all other class and member pattern properties. When a member binding
* is referenced this item is defined to be that item, including its class and member patterns.
+ *
+ * @return The binding name that defines the member.
*/
String memberFromBinding() default "";
@@ -247,6 +277,8 @@
*
* <p>Mutually exclusive with all field and method properties as use restricts the match to both
* types of members.
+ *
+ * @return The member access-flag constraints that must be met.
*/
MemberAccessFlags[] memberAccess() default {};
@@ -257,6 +289,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any
* method-access flags.
+ *
+ * @return The method access-flag constraints that must be met.
*/
MethodAccessFlags[] methodAccess() default {};
@@ -267,6 +301,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any method
* name.
+ *
+ * @return The exact method name of the method.
*/
String methodName() default "";
@@ -277,6 +313,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any return
* type.
+ *
+ * @return The qualified type name of the method return type.
*/
String methodReturnType() default "";
@@ -287,6 +325,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any
* parameters.
+ *
+ * @return The list of qualified type names of the method parameters.
*/
String[] methodParameters() default {"<default>"};
@@ -297,6 +337,8 @@
*
* <p>If none, and other properties define this item as a field, the default matches any
* field-access flags.
+ *
+ * @return The field access-flag constraints that must be met.
*/
FieldAccessFlags[] fieldAccess() default {};
@@ -307,6 +349,8 @@
*
* <p>If none, and other properties define this item as a field, the default matches any field
* name.
+ *
+ * @return The exact field name of the field.
*/
String fieldName() default "";
@@ -316,6 +360,8 @@
* <p>Mutually exclusive with all method properties.
*
* <p>If none, and other properties define this item as a field, the default matches any type.
+ *
+ * @return The qualified type name of the field type.
*/
String fieldType() default "";
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
index b1ad804..beba152 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
@@ -32,20 +32,25 @@
@Retention(RetentionPolicy.CLASS)
public @interface UsedByNative {
- /** Optional description to document the reason for this annotation. */
+ /**
+ * Optional description to document the reason for this annotation.
+ *
+ * @return The descriptive message. Defaults to no description.
+ */
String description() default "";
/**
* Conditions that should be satisfied for the annotation to be in effect.
*
- * <p>Defaults to no conditions, thus trivially/unconditionally satisfied.
+ * @return The list of preconditions. Defaults to no conditions, thus trivially/unconditionally
+ * satisfied.
*/
KeepCondition[] preconditions() default {};
/**
* Additional targets to be kept in addition to the annotated class/members.
*
- * <p>Defaults to no additional targets.
+ * @return List of additional target consequences. Defaults to no additional target consequences.
*/
KeepTarget[] additionalTargets() default {};
@@ -61,6 +66,8 @@
* <p>When annotating a member, the default kind is {@link KeepItemKind#ONLY_MEMBERS}.
*
* <p>It is not possible to use ONLY_CLASS if annotating a member.
+ *
+ * @return The kind for this pattern.
*/
KeepItemKind kind() default KeepItemKind.DEFAULT;
@@ -69,6 +76,8 @@
*
* <p>Mutually exclusive with all field and method properties as use restricts the match to both
* types of members.
+ *
+ * @return The member access-flag constraints that must be met.
*/
MemberAccessFlags[] memberAccess() default {};
@@ -79,6 +88,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any
* method-access flags.
+ *
+ * @return The method access-flag constraints that must be met.
*/
MethodAccessFlags[] methodAccess() default {};
@@ -89,6 +100,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any method
* name.
+ *
+ * @return The exact method name of the method.
*/
String methodName() default "";
@@ -99,6 +112,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any return
* type.
+ *
+ * @return The qualified type name of the method return type.
*/
String methodReturnType() default "";
@@ -109,6 +124,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any
* parameters.
+ *
+ * @return The list of qualified type names of the method parameters.
*/
String[] methodParameters() default {"<default>"};
@@ -119,6 +136,8 @@
*
* <p>If none, and other properties define this item as a field, the default matches any
* field-access flags.
+ *
+ * @return The field access-flag constraints that must be met.
*/
FieldAccessFlags[] fieldAccess() default {};
@@ -129,6 +148,8 @@
*
* <p>If none, and other properties define this item as a field, the default matches any field
* name.
+ *
+ * @return The exact field name of the field.
*/
String fieldName() default "";
@@ -138,6 +159,8 @@
* <p>Mutually exclusive with all method properties.
*
* <p>If none, and other properties define this item as a field, the default matches any type.
+ *
+ * @return The qualified type name of the field type.
*/
String fieldType() default "";
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
index 27489cb..8c6e153 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
@@ -32,20 +32,25 @@
@Retention(RetentionPolicy.CLASS)
public @interface UsedByReflection {
- /** Optional description to document the reason for this annotation. */
+ /**
+ * Optional description to document the reason for this annotation.
+ *
+ * @return The descriptive message. Defaults to no description.
+ */
String description() default "";
/**
* Conditions that should be satisfied for the annotation to be in effect.
*
- * <p>Defaults to no conditions, thus trivially/unconditionally satisfied.
+ * @return The list of preconditions. Defaults to no conditions, thus trivially/unconditionally
+ * satisfied.
*/
KeepCondition[] preconditions() default {};
/**
* Additional targets to be kept in addition to the annotated class/members.
*
- * <p>Defaults to no additional targets.
+ * @return List of additional target consequences. Defaults to no additional target consequences.
*/
KeepTarget[] additionalTargets() default {};
@@ -61,6 +66,8 @@
* <p>When annotating a member, the default kind is {@link KeepItemKind#ONLY_MEMBERS}.
*
* <p>It is not possible to use ONLY_CLASS if annotating a member.
+ *
+ * @return The kind for this pattern.
*/
KeepItemKind kind() default KeepItemKind.DEFAULT;
@@ -69,6 +76,8 @@
*
* <p>Mutually exclusive with all field and method properties as use restricts the match to both
* types of members.
+ *
+ * @return The member access-flag constraints that must be met.
*/
MemberAccessFlags[] memberAccess() default {};
@@ -79,6 +88,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any
* method-access flags.
+ *
+ * @return The method access-flag constraints that must be met.
*/
MethodAccessFlags[] methodAccess() default {};
@@ -89,6 +100,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any method
* name.
+ *
+ * @return The exact method name of the method.
*/
String methodName() default "";
@@ -99,6 +112,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any return
* type.
+ *
+ * @return The qualified type name of the method return type.
*/
String methodReturnType() default "";
@@ -109,6 +124,8 @@
*
* <p>If none, and other properties define this item as a method, the default matches any
* parameters.
+ *
+ * @return The list of qualified type names of the method parameters.
*/
String[] methodParameters() default {"<default>"};
@@ -119,6 +136,8 @@
*
* <p>If none, and other properties define this item as a field, the default matches any
* field-access flags.
+ *
+ * @return The field access-flag constraints that must be met.
*/
FieldAccessFlags[] fieldAccess() default {};
@@ -129,6 +148,8 @@
*
* <p>If none, and other properties define this item as a field, the default matches any field
* name.
+ *
+ * @return The exact field name of the field.
*/
String fieldName() default "";
@@ -138,6 +159,8 @@
* <p>Mutually exclusive with all method properties.
*
* <p>If none, and other properties define this item as a field, the default matches any type.
+ *
+ * @return The qualified type name of the field type.
*/
String fieldType() default "";
}
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 56a215d..fd28b1a 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
@@ -63,16 +63,24 @@
@Retention(RetentionPolicy.CLASS)
public @interface UsesReflection {
- /** Optional description to document the reason for this annotation. */
+ /**
+ * Optional description to document the reason for this annotation.
+ *
+ * @return The descriptive message. Defaults to no description.
+ */
String description() default "";
- /** Consequences that must be kept if the annotation is in effect. */
+ /**
+ * Consequences that must be kept if the annotation is in effect.
+ *
+ * @return The list of target consequences.
+ */
KeepTarget[] value();
/**
* Additional preconditions for the annotation to be in effect.
*
- * <p>Defaults to no additional preconditions.
+ * @return The list of additional preconditions. Defaults to no additional preconditions.
*/
KeepCondition[] additionalPreconditions() default {};
}
diff --git a/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java
index 9bb7f82..eed5427 100644
--- a/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java
+++ b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java
@@ -4,29 +4,67 @@
package com.android.tools.r8;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.ArchiveBuilder;
-import com.android.tools.r8.utils.OutputBuilder;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.ZipEntry;
@KeepForApi
public class ArchiveProtoAndroidResourceConsumer implements AndroidResourceConsumer {
- private final OutputBuilder outputBuilder;
+ private final ArchiveBuilder archiveBuilder;
+ private final Path inputPath;
+ private Map<String, Boolean> compressionMap;
public ArchiveProtoAndroidResourceConsumer(Path outputPath) {
- this.outputBuilder = new ArchiveBuilder(outputPath);
- this.outputBuilder.open();
+ this(outputPath, null);
+ }
+
+ public ArchiveProtoAndroidResourceConsumer(Path outputPath, Path inputPath) {
+ this.archiveBuilder = new ArchiveBuilder(outputPath);
+ this.archiveBuilder.open();
+ this.inputPath = inputPath;
+ }
+
+ private synchronized Map<String, Boolean> getCompressionMap(
+ DiagnosticsHandler diagnosticsHandler) {
+ if (compressionMap != null) {
+ return compressionMap;
+ }
+ if (inputPath != null) {
+ compressionMap = new HashMap<>();
+ try {
+ ZipUtils.iter(
+ inputPath,
+ entry -> {
+ compressionMap.put(entry.getName(), entry.getMethod() != ZipEntry.STORED);
+ });
+ } catch (IOException e) {
+ diagnosticsHandler.error(new ExceptionDiagnostic(e, new PathOrigin(inputPath)));
+ }
+ } else {
+ compressionMap = Collections.emptyMap();
+ }
+ return compressionMap;
}
@Override
public void accept(AndroidResourceOutput androidResource, DiagnosticsHandler diagnosticsHandler) {
- outputBuilder.addFile(
+ archiveBuilder.addFile(
androidResource.getPath().location(),
androidResource.getByteDataView(),
- diagnosticsHandler);
+ diagnosticsHandler,
+ getCompressionMap(diagnosticsHandler)
+ .getOrDefault(androidResource.getPath().location(), true));
}
@Override
public void finished(DiagnosticsHandler handler) {
- outputBuilder.close(handler);
+ archiveBuilder.close(handler);
}
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 6e8edf7..6923215 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -856,15 +856,15 @@
assert appView.getDontWarnConfiguration().validate(options);
options.printWarnings();
+
+ if (options.printTimes) {
+ timing.report();
+ }
} catch (ExecutionException e) {
throw unwrapExecutionException(e);
} finally {
inputApp.signalFinishedToProviders(options.reporter);
options.signalFinishedToConsumers();
- // Dump timings.
- if (options.printTimes) {
- timing.report();
- }
}
}
@@ -906,8 +906,7 @@
LegacyResourceShrinker shrinker = resourceShrinkerBuilder.build();
ShrinkerResult shrinkerResult;
if (options.resourceShrinkerConfiguration.isOptimizedShrinking()) {
- shrinkerResult =
- shrinker.shrinkModel(appView.getResourceShrinkerState().getR8ResourceShrinkerModel());
+ shrinkerResult = shrinker.shrinkModel(appView.getResourceAnalysisResult().getModel());
} else {
shrinkerResult = shrinker.run();
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 3e3fe4c..f806710 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.graph;
-import com.android.build.shrinker.r8integration.R8ResourceShrinkerState;
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.contexts.CompilationContext;
@@ -13,6 +12,7 @@
import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
+import com.android.tools.r8.graph.analysis.ResourceAccessAnalysis.ResourceAnalysisResult;
import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.InitClassLens;
@@ -149,7 +149,7 @@
private SeedMapper applyMappingSeedMapper;
- R8ResourceShrinkerState resourceShrinkerState = null;
+ private ResourceAnalysisResult resourceAnalysisResult = null;
// When input has been (partially) desugared these are the classes which has been library
// desugared. This information is populated in the IR converter.
@@ -871,12 +871,12 @@
testing().unboxedEnumsConsumer.accept(dexItemFactory(), unboxedEnums);
}
- public R8ResourceShrinkerState getResourceShrinkerState() {
- return resourceShrinkerState;
+ public void setResourceAnalysisResult(ResourceAnalysisResult resourceAnalysisResult) {
+ this.resourceAnalysisResult = resourceAnalysisResult;
}
- public void setResourceShrinkerState(R8ResourceShrinkerState resourceShrinkerState) {
- this.resourceShrinkerState = resourceShrinkerState;
+ public ResourceAnalysisResult getResourceAnalysisResult() {
+ return resourceAnalysisResult;
}
public boolean validateUnboxedEnumsHaveBeenPruned() {
@@ -971,6 +971,9 @@
setProguardCompatibilityActions(
getProguardCompatibilityActions().withoutPrunedItems(prunedItems, timing));
}
+ if (resourceAnalysisResult != null) {
+ resourceAnalysisResult.withoutPrunedItems(prunedItems, timing);
+ }
if (hasRootSet()) {
rootSet.pruneItems(prunedItems, timing);
}
@@ -1175,6 +1178,17 @@
new ThreadTask() {
@Override
public void run(Timing threadTiming) {
+ appView.resourceAnalysisResult.rewrittenWithLens(lens, threadTiming);
+ }
+
+ @Override
+ public boolean shouldRun() {
+ return appView.resourceAnalysisResult != null;
+ }
+ },
+ new ThreadTask() {
+ @Override
+ public void run(Timing threadTiming) {
appView.setRootSet(appView.rootSet().rewrittenWithLens(lens, threadTiming));
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 92ceda8..39c4168 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -93,6 +93,7 @@
public static final String androidMediaMediaDrmDescriptorString = "Landroid/media/MediaDrm;";
public static final String androidMediaMediaMetadataRetrieverDescriptorString =
"Landroid/media/MediaMetadataRetriever;";
+ public static final String androidResourcesDescriptorString = "Landroid/content/res/Resources;";
/** Set of types that may be synthesized during compilation. */
private final Set<DexType> possibleCompilerSynthesizedTypes = Sets.newIdentityHashSet();
@@ -643,6 +644,13 @@
createStaticallyKnownType(androidMediaMediaDrmDescriptorString);
public final DexType androidMediaMediaMetadataRetrieverType =
createStaticallyKnownType(androidMediaMediaMetadataRetrieverDescriptorString);
+ public final DexType androidResourcesType =
+ createStaticallyKnownType(androidResourcesDescriptorString);
+ public final DexString androidResourcesGetStringName = createString("getString");
+ public final DexProto androidResourcesGetStringProto = createProto(stringType, intType);
+ public final DexMethod androidResourcesGetStringMethod =
+ createMethod(
+ androidResourcesType, androidResourcesGetStringProto, androidResourcesGetStringName);
public final StringBuildingMethods stringBuilderMethods =
new StringBuildingMethods(stringBuilderType);
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
index d4ebe2f..ed61adb 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.graph.analysis;
import com.android.build.shrinker.r8integration.R8ResourceShrinkerState;
+import com.android.build.shrinker.r8integration.R8ResourceShrinkerState.R8ResourceShrinkerModel;
import com.android.tools.r8.AndroidResourceInput;
import com.android.tools.r8.AndroidResourceInput.Kind;
import com.android.tools.r8.ResourceException;
@@ -12,9 +13,12 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.NewArrayEmpty;
@@ -23,8 +27,10 @@
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.shaking.KeepFieldInfo;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.Timing;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.IdentityHashMap;
@@ -34,18 +40,15 @@
public class ResourceAccessAnalysis implements EnqueuerFieldAccessAnalysis {
private final R8ResourceShrinkerState resourceShrinkerState;
- private final Map<DexProgramClass, RClassFieldToValueStore> fieldToValueMapping =
- new IdentityHashMap<>();
+ private final Map<DexType, RClassFieldToValueStore> fieldToValueMapping = new IdentityHashMap<>();
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final Enqueuer enqueuer;
- @SuppressWarnings("UnusedVariable")
private ResourceAccessAnalysis(
- AppView<? extends AppInfoWithClassHierarchy> appView,
- Enqueuer enqueuer,
- R8ResourceShrinkerState resourceShrinkerState) {
+ AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
this.appView = appView;
- this.resourceShrinkerState = resourceShrinkerState;
- appView.setResourceShrinkerState(resourceShrinkerState);
+ this.enqueuer = enqueuer;
+ this.resourceShrinkerState = new R8ResourceShrinkerState();
try {
for (AndroidResourceInput androidResource :
appView.options().androidResourceProvider.getAndroidResources()) {
@@ -62,14 +65,14 @@
public static void register(
AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
if (enabled(appView, enqueuer)) {
- enqueuer.registerFieldAccessAnalysis(
- new ResourceAccessAnalysis(appView, enqueuer, new R8ResourceShrinkerState()));
+ enqueuer.registerFieldAccessAnalysis(new ResourceAccessAnalysis(appView, enqueuer));
}
}
@Override
public void done(Enqueuer enqueuer) {
- appView.setResourceShrinkerState(resourceShrinkerState);
+ appView.setResourceAnalysisResult(
+ new ResourceAnalysisResult(resourceShrinkerState, fieldToValueMapping));
EnqueuerFieldAccessAnalysis.super.done(enqueuer);
}
@@ -93,13 +96,15 @@
return;
}
if (getMaybeCachedIsRClass(resolvedField.getHolder())) {
- DexProgramClass holderType = resolvedField.getHolder();
+ DexType holderType = resolvedField.getHolderType();
if (!fieldToValueMapping.containsKey(holderType)) {
populateRClassValues(resolvedField);
}
assert fieldToValueMapping.containsKey(holderType);
RClassFieldToValueStore rClassFieldToValueStore = fieldToValueMapping.get(holderType);
IntList integers = rClassFieldToValueStore.valueMapping.get(field);
+ enqueuer.applyMinimumKeepInfoWhenLive(
+ resolvedField, KeepFieldInfo.newEmptyJoiner().disallowOptimization());
for (Integer integer : integers) {
resourceShrinkerState.trace(integer);
}
@@ -161,7 +166,7 @@
rClassValueBuilder.addMapping(staticPut.getField(), values);
}
- fieldToValueMapping.put(field.getHolder(), rClassValueBuilder.build());
+ fieldToValueMapping.put(field.getHolderType(), rClassValueBuilder.build());
}
private final Map<DexProgramClass, Boolean> cachedClassLookups = new IdentityHashMap<>();
@@ -189,13 +194,103 @@
return isRClass;
}
+ public static class ResourceAnalysisResult {
+
+ private final R8ResourceShrinkerState resourceShrinkerState;
+ private Map<DexType, RClassFieldToValueStore> rClassFieldToValueStoreMap;
+
+ private ResourceAnalysisResult(
+ R8ResourceShrinkerState resourceShrinkerState,
+ Map<DexType, RClassFieldToValueStore> rClassFieldToValueStoreMap) {
+ this.resourceShrinkerState = resourceShrinkerState;
+ this.rClassFieldToValueStoreMap = rClassFieldToValueStoreMap;
+ }
+
+ public R8ResourceShrinkerModel getModel() {
+ return resourceShrinkerState.getR8ResourceShrinkerModel();
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ public void rewrittenWithLens(GraphLens lens, Timing timing) {
+ Map<DexType, DexType> changed = new IdentityHashMap<>();
+ for (DexType dexType : rClassFieldToValueStoreMap.keySet()) {
+ DexType rewritten = lens.lookupClassType(dexType);
+ if (rewritten != dexType) {
+ changed.put(dexType, rewritten);
+ }
+ }
+ if (changed.size() > 0) {
+ Map<DexType, RClassFieldToValueStore> rewrittenMap = new IdentityHashMap<>();
+ rClassFieldToValueStoreMap.forEach(
+ (type, map) -> {
+ rewrittenMap.put(changed.getOrDefault(type, type), map);
+ map.rewrittenWithLens(lens);
+ });
+ rClassFieldToValueStoreMap = rewrittenMap;
+ }
+ }
+
+ public void withoutPrunedItems(PrunedItems prunedItems, Timing timing) {
+ rClassFieldToValueStoreMap.keySet().removeIf(prunedItems::isRemoved);
+ rClassFieldToValueStoreMap.values().forEach(store -> store.pruneItems(prunedItems));
+ }
+
+ public String getSingleStringValueForField(ProgramField programField) {
+ RClassFieldToValueStore rClassFieldToValueStore =
+ rClassFieldToValueStoreMap.get(programField.getHolderType());
+ if (rClassFieldToValueStore == null) {
+ return null;
+ }
+ if (!rClassFieldToValueStore.hasField(programField.getReference())) {
+ return null;
+ }
+ return getModel()
+ .getStringResourcesWithSingleValue()
+ .get(rClassFieldToValueStore.getResourceId(programField.getReference()));
+ }
+ }
+
private static class RClassFieldToValueStore {
- private final Map<DexField, IntList> valueMapping;
+ private Map<DexField, IntList> valueMapping;
private RClassFieldToValueStore(Map<DexField, IntList> valueMapping) {
this.valueMapping = valueMapping;
}
+ boolean hasField(DexField field) {
+ return valueMapping.containsKey(field);
+ }
+
+ void pruneItems(PrunedItems prunedItems) {
+ valueMapping.keySet().removeIf(prunedItems::isRemoved);
+ }
+
+ int getResourceId(DexField field) {
+ IntList integers = valueMapping.get(field);
+ assert integers.size() == 1;
+ return integers.get(0);
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ public void rewrittenWithLens(GraphLens lens) {
+ Map<DexField, DexField> changed = new IdentityHashMap<>();
+ valueMapping
+ .keySet()
+ .forEach(
+ dexField -> {
+ DexField rewritten = lens.lookupField(dexField);
+ if (rewritten != dexField) {
+ changed.put(dexField, rewritten);
+ }
+ });
+ if (changed.size() > 0) {
+ Map<DexField, IntList> rewrittenMapping = new IdentityHashMap<>();
+ valueMapping.forEach(
+ (key, value) -> rewrittenMapping.put(changed.getOrDefault(key, key), value));
+ valueMapping = rewrittenMapping;
+ }
+ }
+
public static class Builder {
private final Map<DexField, IntList> valueMapping = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
index b34bf05..64f8399 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.ir.code.InvokeType;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxingLens;
import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
import com.android.tools.r8.optimize.MemberRebindingLens;
@@ -384,10 +385,15 @@
return true;
}
- public boolean hasCustomCodeRewritings() {
+ public boolean hasCustomLensCodeRewriter() {
return false;
}
+ public CustomLensCodeRewriter getCustomLensCodeRewriter() {
+ assert hasCustomLensCodeRewriter();
+ return null;
+ }
+
public boolean isAppliedLens() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java
new file mode 100644
index 0000000..84599f0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java
@@ -0,0 +1,51 @@
+// 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.graph.lens;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter;
+import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
+import java.util.Map;
+
+public class NestedGraphLensWithCustomLensCodeRewriter extends NestedGraphLens {
+
+ private CustomLensCodeRewriter customLensCodeRewriter;
+
+ public NestedGraphLensWithCustomLensCodeRewriter(
+ AppView<?> appView,
+ BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
+ BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap,
+ BidirectionalManyToManyRepresentativeMap<DexType, DexType> typeMap) {
+ super(appView, fieldMap, methodMap, typeMap);
+ }
+
+ public NestedGraphLensWithCustomLensCodeRewriter(
+ AppView<?> appView,
+ BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
+ Map<DexMethod, DexMethod> methodMap,
+ BidirectionalManyToManyRepresentativeMap<DexType, DexType> typeMap,
+ BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> newMethodSignatures) {
+ super(appView, fieldMap, methodMap, typeMap, newMethodSignatures);
+ }
+
+ @Override
+ public boolean hasCustomLensCodeRewriter() {
+ return true;
+ }
+
+ public void setCustomLensCodeRewriter(CustomLensCodeRewriter customLensCodeRewriter) {
+ this.customLensCodeRewriter = customLensCodeRewriter;
+ }
+
+ @Override
+ public CustomLensCodeRewriter getCustomLensCodeRewriter() {
+ assert customLensCodeRewriter != null;
+ return customLensCodeRewriter;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoResourceClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoResourceClasses.java
index acf1bce..11a3951 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoResourceClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoResourceClasses.java
@@ -11,7 +11,8 @@
@Override
public boolean canMerge(DexProgramClass program) {
- return !program.getSimpleName().startsWith("R$");
+ String simpleName = program.getSimpleName();
+ return !simpleName.startsWith("R$") && !simpleName.contains("$R$");
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 0e13017..2a10e4c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -259,7 +259,7 @@
: null;
this.enumUnboxer = EnumUnboxer.create(appViewWithLiveness);
this.numberUnboxer = NumberUnboxer.create(appViewWithLiveness);
- this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, enumUnboxer);
+ this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness);
this.inliner = new Inliner(appViewWithLiveness, this, lensCodeRewriter);
this.outliner = Outliner.create(appViewWithLiveness);
this.memberValuePropagation = new R8MemberValuePropagation(appViewWithLiveness);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 9cb88c3..ad14d80 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -107,7 +107,7 @@
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.passes.TrivialPhiSimplifier;
import com.android.tools.r8.ir.optimize.AffectedValues;
-import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
+import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.optimize.argumentpropagation.lenscoderewriter.NullCheckInserter;
import com.android.tools.r8.utils.ArrayUtils;
@@ -159,13 +159,11 @@
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final DexItemFactory factory;
- private final EnumUnboxer enumUnboxer;
private final InternalOptions options;
- LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView, EnumUnboxer enumUnboxer) {
+ LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView) {
this.appView = appView;
this.factory = appView.dexItemFactory();
- this.enumUnboxer = enumUnboxer;
this.options = appView.options();
}
@@ -227,10 +225,11 @@
Set<UnusedArgument> unusedArguments = Sets.newIdentityHashSet();
rewriteArguments(
code, originalMethodReference, prototypeChanges, affectedPhis, unusedArguments);
- if (graphLens.hasCustomCodeRewritings()) {
- assert graphLens.isEnumUnboxerLens();
+ if (graphLens.hasCustomLensCodeRewriter()) {
assert graphLens.getPrevious() == codeLens;
- affectedPhis.addAll(enumUnboxer.rewriteCode(code, methodProcessor, prototypeChanges));
+ CustomLensCodeRewriter customLensCodeRewriter = graphLens.getCustomLensCodeRewriter();
+ affectedPhis.addAll(
+ customLensCodeRewriter.rewriteCode(code, methodProcessor, prototypeChanges, graphLens));
}
if (!unusedArguments.isEmpty()) {
for (UnusedArgument unusedArgument : unusedArguments) {
@@ -1096,10 +1095,10 @@
assert currentLens.isNonIdentityLens();
NonIdentityGraphLens currentNonIdentityLens = currentLens.asNonIdentityLens();
NonIdentityGraphLens fromInclusiveLens = currentNonIdentityLens;
- if (!currentNonIdentityLens.hasCustomCodeRewritings()) {
+ if (!currentNonIdentityLens.hasCustomLensCodeRewriter()) {
GraphLens fromInclusiveLensPredecessor = fromInclusiveLens.getPrevious();
while (fromInclusiveLensPredecessor.isNonIdentityLens()
- && !fromInclusiveLensPredecessor.hasCustomCodeRewritings()
+ && !fromInclusiveLensPredecessor.hasCustomLensCodeRewriter()
&& fromInclusiveLensPredecessor != codeLens) {
fromInclusiveLens = fromInclusiveLensPredecessor.asNonIdentityLens();
fromInclusiveLensPredecessor = fromInclusiveLens.getPrevious();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index 6b6f2ad..8b4cbce 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -212,8 +212,6 @@
appView.clearMethodResolutionOptimizationInfoCollection();
- enumUnboxer.unsetRewriter();
-
// All the code that should be impacted by the lenses inserted between phase 1 and phase 2
// have now been processed and rewritten, we clear code lens rewriting so that the class
// staticizer and phase 3 does not perform again the rewriting.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
index b1a71cf..e270daf 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
@@ -320,6 +320,20 @@
cfInstructions.add(new CfInvoke(invoke.getOpcode(), convertedMethod, invoke.isInterface()));
if (returnConversion != null) {
+ assert returnConversion.getArity() == 1 || returnConversion.getArity() == 2;
+ if (returnConversion.getArity() == 2) {
+ // If there is a second parameter, pass the receiver.
+ if (!invoke.isInvokeSuper(context.getHolderType())) {
+ appView
+ .reporter()
+ .error(
+ "Cannot generate inlined api conversion for return type for "
+ + invoke.getMethod()
+ + " in "
+ + context.getReference());
+ }
+ cfInstructions.add(new CfLoad(ValueType.OBJECT, 0));
+ }
cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false));
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CustomLensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CustomLensCodeRewriter.java
new file mode 100644
index 0000000..1f046d2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CustomLensCodeRewriter.java
@@ -0,0 +1,25 @@
+// 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.ir.optimize;
+
+import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public interface CustomLensCodeRewriter {
+
+ CustomLensCodeRewriter EMPTY =
+ (code, methodProcessor, prototypeChanges, lens) -> Sets.newIdentityHashSet();
+
+ Set<Phi> rewriteCode(
+ IRCode code,
+ MethodProcessor methodProcessor,
+ RewrittenPrototypeDescription prototypeChanges,
+ NonIdentityGraphLens lens);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
index cc90f9b..4e07fba 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
@@ -8,18 +8,14 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Timing;
-import java.util.Collections;
-import java.util.Set;
import java.util.concurrent.ExecutorService;
public class EmptyEnumUnboxer extends EnumUnboxer {
@@ -58,14 +54,6 @@
}
@Override
- public Set<Phi> rewriteCode(
- IRCode code,
- MethodProcessor methodProcessor,
- RewrittenPrototypeDescription prototypeChanges) {
- return Collections.emptySet();
- }
-
- @Override
@SuppressWarnings("BadImport")
public void rewriteWithLens() {
// Intentionally empty.
@@ -84,11 +72,6 @@
}
@Override
- public void unsetRewriter() {
- // Intentionally empty.
- }
-
- @Override
public void updateEnumUnboxingCandidatesInfo() {
// Intentionally empty.
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 48ae3b2..081d608 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -8,17 +8,14 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Timing;
-import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -44,9 +41,6 @@
public abstract void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues);
- public abstract Set<Phi> rewriteCode(
- IRCode code, MethodProcessor methodProcessor, RewrittenPrototypeDescription prototypeChanges);
-
public abstract void rewriteWithLens();
@SuppressWarnings("BadImport")
@@ -59,7 +53,5 @@
Timing timing)
throws ExecutionException;
- public abstract void unsetRewriter();
-
public abstract void updateEnumUnboxingCandidatesInfo();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 5821b3a..5132141 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -166,9 +166,6 @@
checkNotNullMethodsBuilder;
private final DexClassAndField ordinalField;
-
- private EnumUnboxingRewriter enumUnboxerRewriter;
-
private final boolean debugLogEnabled;
private final Map<DexType, List<Reason>> debugLogs;
@@ -764,13 +761,12 @@
updateOptimizationInfos(executorService, feedback, treeFixerResult, previousLens);
- enumUnboxerRewriter =
+ enumUnboxingLens.setCustomLensCodeRewriter(
new EnumUnboxingRewriter(
appView,
treeFixerResult.getCheckNotNullToCheckNotZeroMapping(),
- enumUnboxingLens,
enumDataMap,
- utilityClasses);
+ utilityClasses));
// Ensure determinism of method-to-reprocess set.
appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
@@ -1785,22 +1781,4 @@
enumUnboxingCandidatesInfo.addPrunedMethod(method);
methodsDependingOnLibraryModelisation.remove(method.getReference(), appView.graphLens());
}
-
- @Override
- public Set<Phi> rewriteCode(
- IRCode code,
- MethodProcessor methodProcessor,
- RewrittenPrototypeDescription prototypeChanges) {
- // This has no effect during primary processing since the enumUnboxerRewriter is set
- // in between primary and post processing.
- if (enumUnboxerRewriter != null) {
- return enumUnboxerRewriter.rewriteCode(code, methodProcessor, prototypeChanges);
- }
- return Sets.newIdentityHashSet();
- }
-
- @Override
- public void unsetRewriter() {
- enumUnboxerRewriter = null;
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index 95e4ea6..37b6314 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -14,7 +14,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.MethodLookupResult;
-import com.android.tools.r8.graph.lens.NestedGraphLens;
+import com.android.tools.r8.graph.lens.NestedGraphLensWithCustomLensCodeRewriter;
import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
@@ -44,7 +44,7 @@
import java.util.Map;
import java.util.Set;
-public class EnumUnboxingLens extends NestedGraphLens {
+public class EnumUnboxingLens extends NestedGraphLensWithCustomLensCodeRewriter {
private final AbstractValueFactory abstractValueFactory;
private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod;
@@ -68,11 +68,6 @@
}
@Override
- public boolean hasCustomCodeRewritings() {
- return true;
- }
-
- @Override
public boolean isEnumUnboxerLens() {
return true;
}
@@ -82,6 +77,10 @@
return this;
}
+ public EnumDataMap getUnboxedEnums() {
+ return unboxedEnums;
+ }
+
@Override
public boolean isContextFreeForMethods(GraphLens codeLens) {
if (codeLens == this) {
@@ -383,7 +382,8 @@
originalCheckNotNullMethodSignature, checkNotNullMethod.getReference());
}
- public EnumUnboxingLens build(AppView<?> appView, Set<DexMethod> dispatchMethods) {
+ public EnumUnboxingLens build(
+ AppView<AppInfoWithLiveness> appView, Set<DexMethod> dispatchMethods) {
assert !typeMap.isEmpty();
return new EnumUnboxingLens(
appView,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 46184c4..da575cd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
import com.android.tools.r8.graph.proto.ArgumentInfo;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
@@ -42,6 +43,7 @@
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter;
import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification;
import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
@@ -58,27 +60,24 @@
import java.util.Map;
import java.util.Set;
-public class EnumUnboxingRewriter {
+public class EnumUnboxingRewriter implements CustomLensCodeRewriter {
private final AppView<AppInfoWithLiveness> appView;
private final Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping;
private final DexItemFactory factory;
private final InternalOptions options;
private final EnumDataMap unboxedEnumsData;
- private final EnumUnboxingLens enumUnboxingLens;
private final EnumUnboxingUtilityClasses utilityClasses;
EnumUnboxingRewriter(
AppView<AppInfoWithLiveness> appView,
Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping,
- EnumUnboxingLens enumUnboxingLens,
EnumDataMap unboxedEnumsInstanceFieldData,
EnumUnboxingUtilityClasses utilityClasses) {
this.appView = appView;
- this.checkNotNullToCheckNotZeroMapping = checkNotNullToCheckNotZeroMapping;
this.factory = appView.dexItemFactory();
this.options = appView.options();
- this.enumUnboxingLens = enumUnboxingLens;
+ this.checkNotNullToCheckNotZeroMapping = checkNotNullToCheckNotZeroMapping;
this.unboxedEnumsData = unboxedEnumsInstanceFieldData;
this.utilityClasses = utilityClasses;
}
@@ -146,15 +145,19 @@
return convertedEnums;
}
- Set<Phi> rewriteCode(
+ @Override
+ public Set<Phi> rewriteCode(
IRCode code,
MethodProcessor methodProcessor,
- RewrittenPrototypeDescription prototypeChanges) {
+ RewrittenPrototypeDescription prototypeChanges,
+ NonIdentityGraphLens graphLens) {
// We should not process the enum methods, they will be removed and they may contain invalid
// rewriting rules.
if (unboxedEnumsData.isEmpty()) {
return Sets.newIdentityHashSet();
}
+ assert graphLens.isEnumUnboxerLens();
+ EnumUnboxingLens enumUnboxingLens = graphLens.asEnumUnboxerLens();
assert code.isConsistentSSABeforeTypesAreCorrect(appView);
EnumUnboxerMethodProcessorEventConsumer eventConsumer = methodProcessor.getEventConsumer();
Set<Phi> affectedPhis = Sets.newIdentityHashSet();
@@ -192,7 +195,8 @@
blocks,
block,
iterator,
- instruction.asInvokeMethodWithReceiver());
+ instruction.asInvokeMethodWithReceiver(),
+ enumUnboxingLens);
} else if (instruction.isNewArrayFilled()) {
rewriteNewArrayFilled(instruction.asNewArrayFilled(), code, convertedEnums, iterator);
} else if (instruction.isInvokeStatic()) {
@@ -379,7 +383,8 @@
BasicBlockIterator blocks,
BasicBlock block,
InstructionListIterator iterator,
- InvokeMethodWithReceiver invoke) {
+ InvokeMethodWithReceiver invoke,
+ EnumUnboxingLens enumUnboxingLens) {
ProgramMethod context = code.context();
// If the receiver is null, then the invoke is not rewritten even if the receiver is an
// unboxed enum, but we end up with null.ordinal() or similar which has the correct behavior.
@@ -699,7 +704,7 @@
}
}
- public void rewriteNullCheck(
+ private void rewriteNullCheck(
InstructionListIterator iterator,
InvokeMethod invoke,
ProgramMethod context,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 59be01e..35e4f74 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -54,6 +54,7 @@
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
+import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter;
import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification;
import com.android.tools.r8.ir.optimize.enums.code.CheckNotZeroCode;
@@ -164,6 +165,8 @@
converter.outliner.rewriteWithLens();
// Create mapping from checkNotNull() to checkNotZero() methods.
+ // The customLensCodeRewriter has to be non null for the duplication but is effectively unused.
+ lens.setCustomLensCodeRewriter(CustomLensCodeRewriter.EMPTY);
BiMap<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping =
duplicateCheckNotNullMethods(converter, executorService);
@@ -1120,7 +1123,7 @@
return methodsToProcess;
}
- EnumUnboxingLens getLens() {
+ public EnumUnboxingLens getLens() {
return lens;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index f47e771..1dc3ca2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -50,6 +50,10 @@
register(new ObjectsMethodOptimizer(appView));
register(new StringBuilderMethodOptimizer(appView));
register(new StringMethodOptimizer(appView));
+ if (appView.enableWholeProgramOptimizations()
+ && appView.options().resourceShrinkerConfiguration.isOptimizedShrinking()) {
+ register(new ResourcesMemberOptimizer(appView));
+ }
if (appView.enableWholeProgramOptimizations()) {
// Subtyping is required to prove the enum class is a subtype of java.lang.Enum.
register(new EnumMethodOptimizer(appView));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java
new file mode 100644
index 0000000..1246516
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java
@@ -0,0 +1,147 @@
+// 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.ir.optimize.library;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+public class ResourcesMemberOptimizer extends StatelessLibraryMethodModelCollection {
+
+ private final AppView<?> appView;
+ private final DexItemFactory dexItemFactory;
+ private Optional<Boolean> allowStringInlining = Optional.empty();
+
+ ResourcesMemberOptimizer(AppView<?> appView) {
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private synchronized boolean allowInliningOfGetStringCalls() {
+ if (allowStringInlining.isPresent()) {
+ return allowStringInlining.get();
+ }
+ // TODO(b/312406163): Allow androidx classes that overwrite this, but don't change the value
+ // or have side effects.
+ allowStringInlining = Optional.of(true);
+ Map<DexClass, Boolean> cachedResults = new IdentityHashMap<>();
+ TopDownClassHierarchyTraversal.forProgramClasses(appView.withClassHierarchy())
+ .visit(
+ appView.appInfo().classes(),
+ clazz -> {
+ if (isResourcesSubtype(cachedResults, clazz)) {
+ DexEncodedMethod dexEncodedMethod =
+ clazz.lookupMethod(
+ dexItemFactory.androidResourcesGetStringProto,
+ dexItemFactory.androidResourcesGetStringName);
+ if (dexEncodedMethod != null) {
+ // TODO(b/312695444): Break out of traversal when supported.
+ allowStringInlining = Optional.of(false);
+ }
+ }
+ });
+ return allowStringInlining.get();
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private boolean isResourcesSubtype(Map<DexClass, Boolean> cachedLookups, DexClass dexClass) {
+ Boolean cachedValue = cachedLookups.get(dexClass);
+ if (cachedValue != null) {
+ return cachedValue;
+ }
+ if (dexClass.type == dexItemFactory.androidResourcesType) {
+ return true;
+ }
+ if (dexClass.type == dexItemFactory.objectType) {
+ return false;
+ }
+
+ if (dexClass.superType != null) {
+ DexClass superClass = appView.definitionFor(dexClass.superType);
+ if (superClass != null) {
+ boolean superIsResourcesSubtype = isResourcesSubtype(cachedLookups, superClass);
+ cachedLookups.put(dexClass, superIsResourcesSubtype);
+ return superIsResourcesSubtype;
+ }
+ }
+ cachedLookups.put(dexClass, false);
+ return false;
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.androidResourcesType;
+ }
+
+ @Override
+ public InstructionListIterator optimize(
+ IRCode code,
+ BasicBlockIterator blockIterator,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ AffectedValues affectedValues,
+ Set<BasicBlock> blocksToRemove) {
+ if (allowInliningOfGetStringCalls()
+ && singleTarget
+ .getReference()
+ .isIdenticalTo(dexItemFactory.androidResourcesGetStringMethod)) {
+ maybeInlineGetString(code, instructionIterator, invoke, affectedValues);
+ }
+ return instructionIterator;
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private void maybeInlineGetString(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ AffectedValues affectedValues) {
+ if (invoke.isInvokeVirtual()) {
+ InvokeVirtual invokeVirtual = invoke.asInvokeVirtual();
+ DexMethod invokedMethod = invokeVirtual.getInvokedMethod();
+ assert invokedMethod.isIdenticalTo(dexItemFactory.androidResourcesGetStringMethod);
+ assert invoke.inValues().size() == 2;
+ Instruction valueDefinition = invoke.getLastArgument().definition;
+ if (valueDefinition != null && valueDefinition.isStaticGet()) {
+ DexField field = valueDefinition.asStaticGet().getField();
+ FieldResolutionResult fieldResolutionResult =
+ appView.appInfo().resolveField(field, code.context());
+ ProgramField resolvedField = fieldResolutionResult.getProgramField();
+ if (resolvedField != null) {
+ String singleStringValueForField =
+ appView.getResourceAnalysisResult().getSingleStringValueForField(resolvedField);
+ if (singleStringValueForField != null) {
+ DexString value = dexItemFactory.createString(singleStringValueForField);
+ instructionIterator.replaceCurrentInstructionWithConstString(
+ appView, code, value, affectedValues);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index a43cbe6..15092b9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -34,6 +34,7 @@
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ValueUtils;
import com.android.tools.r8.utils.ValueUtils.ArrayValues;
import com.google.common.collect.ImmutableMap;
@@ -54,7 +55,12 @@
StringMethodOptimizer(AppView<?> appView) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
- this.enableStringFormatOptimizations = appView.options().enableStringFormatOptimization;
+ InternalOptions options = appView.options();
+ this.enableStringFormatOptimizations =
+ appView.enableWholeProgramOptimizations()
+ && !options.debug
+ && options.isOptimizing()
+ && options.isShrinking();
this.valueOfToStringAppend =
ImmutableMap.<DexMethod, DexMethod>builder()
.put(
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index c8d9806..d1a2dda 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3778,7 +3778,7 @@
}
}
- private void applyMinimumKeepInfoWhenLive(
+ public void applyMinimumKeepInfoWhenLive(
ProgramField field, KeepFieldInfo.Joiner minimumKeepInfo) {
applyMinimumKeepInfoWhenLive(field, minimumKeepInfo, EnqueuerEvent.unconditional());
}
diff --git a/src/main/java/com/android/tools/r8/threading/ThreadingModule.java b/src/main/java/com/android/tools/r8/threading/ThreadingModule.java
index 1525e70..7e374c4 100644
--- a/src/main/java/com/android/tools/r8/threading/ThreadingModule.java
+++ b/src/main/java/com/android/tools/r8/threading/ThreadingModule.java
@@ -10,8 +10,6 @@
import com.android.tools.r8.keepanno.annotations.MemberAccessFlags;
import com.android.tools.r8.keepanno.annotations.UsedByReflection;
import com.android.tools.r8.keepanno.annotations.UsesReflection;
-import com.android.tools.r8.threading.providers.blocking.ThreadingModuleBlockingProvider;
-import com.android.tools.r8.threading.providers.singlethreaded.ThreadingModuleSingleThreadedProvider;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
@@ -43,20 +41,20 @@
// Splitting up the names to make reflective identification unlikely.
// We explicitly don't want R8 to optimize out the reflective lookup.
private static final String PACKAGE = "com.android.tools.r8.threading.providers";
- private static final String[] IMPLEMENTATIONS = {
- "blocking.ThreadingModuleBlockingProvider",
- "singlethreaded.ThreadingModuleSingleThreadedProvider"
- };
+ private static final String BLOCKING_PROVIDER = "blocking.ThreadingModuleBlockingProvider";
+ private static final String SINGLE_THREADED_PROVIDER =
+ "singlethreaded.ThreadingModuleSingleThreadedProvider";
+ private static final String[] IMPLEMENTATIONS = {BLOCKING_PROVIDER, SINGLE_THREADED_PROVIDER};
@UsesReflection({
@KeepTarget(
kind = KeepItemKind.CLASS_AND_MEMBERS,
- classConstant = ThreadingModuleBlockingProvider.class,
+ className = PACKAGE + "." + "blocking.ThreadingModuleBlockingProvider",
methodName = "<init>",
methodParameters = {}),
@KeepTarget(
kind = KeepItemKind.CLASS_AND_MEMBERS,
- classConstant = ThreadingModuleSingleThreadedProvider.class,
+ className = PACKAGE + "." + "singlethreaded.ThreadingModuleSingleThreadedProvider",
methodName = "<init>",
methodParameters = {})
})
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index a50dbb9..1ff1d22 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -73,7 +73,7 @@
writeDirectoryNow(data.name, handler);
} else {
assert data.content != null;
- writeFileNow(data.name, data.content, handler);
+ writeFileNow(data.name, data.content, handler, data.storeCompressed);
}
}
}
@@ -135,9 +135,9 @@
ByteDataView view = ByteDataView.of(ByteStreams.toByteArray(in));
synchronized (this) {
if (AndroidApiDataAccess.isApiDatabaseEntry(name)) {
- writeFileNow(name, view, handler);
+ writeFileNow(name, view, handler, true);
} else {
- delayedWrites.add(DelayedData.createFile(name, view));
+ delayedWrites.add(DelayedData.createFile(name, view, true));
}
}
} catch (IOException e) {
@@ -150,16 +150,25 @@
@Override
public synchronized void addFile(String name, ByteDataView content, DiagnosticsHandler handler) {
- delayedWrites.add(DelayedData.createFile(name, ByteDataView.of(content.copyByteData())));
+ addFile(name, content, handler, true);
}
- private void writeFileNow(String name, ByteDataView content, DiagnosticsHandler handler) {
+ public synchronized void addFile(
+ String name, ByteDataView content, DiagnosticsHandler handler, boolean storeCompressed) {
+ delayedWrites.add(
+ DelayedData.createFile(name, ByteDataView.of(content.copyByteData()), storeCompressed));
+ }
+
+ private void writeFileNow(
+ String name, ByteDataView content, DiagnosticsHandler handler, boolean compressed) {
try {
ZipUtils.writeToZipStream(
getStream(),
name,
content,
- AndroidApiDataAccess.isApiDatabaseEntry(name) ? ZipEntry.STORED : ZipEntry.DEFLATED);
+ AndroidApiDataAccess.isApiDatabaseEntry(name) || !compressed
+ ? ZipEntry.STORED
+ : ZipEntry.DEFLATED);
} catch (IOException e) {
handleIOException(e, handler);
}
@@ -168,7 +177,7 @@
private void writeNextIfAvailable(DiagnosticsHandler handler) {
DelayedData data = delayedClassesDexFiles.remove(classesFileIndex);
while (data != null) {
- writeFileNow(data.name, data.content, handler);
+ writeFileNow(data.name, data.content, handler, data.storeCompressed);
classesFileIndex++;
data = delayedClassesDexFiles.remove(classesFileIndex);
}
@@ -179,13 +188,13 @@
int index, String name, ByteDataView content, DiagnosticsHandler handler) {
if (index == classesFileIndex) {
// Fast case, we got the file in order (or we only had one).
- writeFileNow(name, content, handler);
+ writeFileNow(name, content, handler, true);
classesFileIndex++;
writeNextIfAvailable(handler);
} else {
// Data is released in the application writer, take a copy.
- delayedClassesDexFiles.put(index,
- new DelayedData(name, ByteDataView.of(content.copyByteData()), false));
+ delayedClassesDexFiles.put(
+ index, new DelayedData(name, ByteDataView.of(content.copyByteData()), false, true));
}
}
@@ -203,19 +212,23 @@
public final String name;
public final ByteDataView content;
public final boolean isDirectory;
+ public final boolean storeCompressed;
- public static DelayedData createFile(String name, ByteDataView content) {
- return new DelayedData(name, content, false);
+ public static DelayedData createFile(
+ String name, ByteDataView content, boolean storeCompressed) {
+ return new DelayedData(name, content, false, storeCompressed);
}
public static DelayedData createDirectory(String name) {
- return new DelayedData(name, null, true);
+ return new DelayedData(name, null, true, true);
}
- private DelayedData(String name, ByteDataView content, boolean isDirectory) {
+ private DelayedData(
+ String name, ByteDataView content, boolean isDirectory, boolean storeCompressed) {
this.name = name;
this.content = content;
this.isDirectory = isDirectory;
+ this.storeCompressed = storeCompressed;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index de093c4..ae6e72c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -280,9 +280,6 @@
itemFactory = proguardConfiguration.getDexItemFactory();
enableTreeShaking = proguardConfiguration.isShrinking();
enableMinification = proguardConfiguration.isObfuscating();
- // TODO(b/244238384): Enable.
- enableStringFormatOptimization =
- System.getProperty("com.android.tools.r8.optimizeStringFormat") != null;
if (!proguardConfiguration.isOptimizing()) {
// TODO(b/171457102): Avoid the need for this.
@@ -328,7 +325,6 @@
disableGlobalOptimizations();
enableNameReflectionOptimization = false;
enableStringConcatenationOptimization = false;
- enableStringFormatOptimization = false;
}
public void disableGlobalOptimizations() {
@@ -413,7 +409,6 @@
public boolean enableNameReflectionOptimization = true;
public boolean enableStringConcatenationOptimization = true;
// Enabled only for R8 (not D8).
- public boolean enableStringFormatOptimization;
public boolean enableTreeShakingOfLibraryMethodOverrides = false;
public boolean encodeChecksums = false;
public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index e02d3e0..2b11a9f 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -37,6 +37,7 @@
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.BiFunction;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -100,6 +101,15 @@
}
}
+ public static void iter(Path zipFilePath, Consumer<ZipEntry> entryConsumer) throws IOException {
+ try (ZipFile zipFile = new ZipFile(zipFilePath.toFile(), StandardCharsets.UTF_8)) {
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ entryConsumer.accept(entries.nextElement());
+ }
+ }
+ }
+
@SuppressWarnings("UnnecessaryParentheses")
public static boolean containsEntry(Path zipfile, String name) throws IOException {
BooleanBox result = new BooleanBox();
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
index 8273c96..e0cca4f 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -4,7 +4,11 @@
package com.android.build.shrinker.r8integration;
+import com.android.aapt.Resources.ConfigValue;
+import com.android.aapt.Resources.Entry;
+import com.android.aapt.Resources.Item;
import com.android.aapt.Resources.ResourceTable;
+import com.android.aapt.Resources.Value;
import com.android.build.shrinker.NoDebugReporter;
import com.android.build.shrinker.ResourceShrinkerModel;
import com.android.build.shrinker.ResourceTableUtilKt;
@@ -16,7 +20,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class R8ResourceShrinkerState {
@@ -38,12 +44,17 @@
}
public static class R8ResourceShrinkerModel extends ResourceShrinkerModel {
+ private final Map<Integer, String> stringResourcesWithSingleValue = new HashMap<>();
public R8ResourceShrinkerModel(
ShrinkerDebugReporter debugReporter, boolean supportMultipackages) {
super(debugReporter, supportMultipackages);
}
+ public Map<Integer, String> getStringResourcesWithSingleValue() {
+ return stringResourcesWithSingleValue;
+ }
+
// Similar to instantiation in ProtoResourceTableGatherer, but using an inputstream.
void instantiateFromResourceTable(InputStream inputStream) {
try {
@@ -60,14 +71,34 @@
.forEachRemaining(
entryWrapper -> {
ResourceType resourceType = ResourceType.fromClassName(entryWrapper.getType());
+ Entry entry = entryWrapper.getEntry();
+ int entryId = entryWrapper.getId();
+ recordSingleValueResources(resourceType, entry, entryId);
if (resourceType != ResourceType.STYLEABLE) {
this.addResource(
resourceType,
entryWrapper.getPackageName(),
- ResourcesUtil.resourceNameToFieldName(entryWrapper.getEntry().getName()),
- entryWrapper.getId());
+ ResourcesUtil.resourceNameToFieldName(entry.getName()),
+ entryId);
}
});
}
+
+ private void recordSingleValueResources(ResourceType resourceType, Entry entry, int entryId) {
+ if (!entry.hasOverlayableItem() && entry.getConfigValueList().size() == 1) {
+ if (resourceType == ResourceType.STRING) {
+ ConfigValue configValue = entry.getConfigValue(0);
+ if (configValue.hasValue()) {
+ Value value = configValue.getValue();
+ if (value.hasItem()) {
+ Item item = value.getItem();
+ if (item.hasStr()) {
+ stringResourcesWithSingleValue.put(entryId, item.getStr().getValue());
+ }
+ }
+ }
+ }
+ }
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 7ae3463..d1eb192 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -291,6 +291,11 @@
return self();
}
+ public T enableOptimizedShrinking() {
+ builder.setResourceShrinkerConfiguration(b -> b.enableOptimizedShrinkingWithR8().build());
+ return self();
+ }
+
/**
* Allow info, warning, and error diagnostics.
*
@@ -905,8 +910,8 @@
getBuilder()
.setAndroidResourceProvider(
new ArchiveProtoAndroidResourceProvider(resources, new PathOrigin(resources)));
- getBuilder().setAndroidResourceConsumer(new ArchiveProtoAndroidResourceConsumer(output));
- self();
+ getBuilder()
+ .setAndroidResourceConsumer(new ArchiveProtoAndroidResourceConsumer(output, resources));
return addProgramClassFileData(testResource.getRClass().getClassFileData());
}
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
index 0ee66c7..021902d 100644
--- a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
@@ -10,9 +10,12 @@
import com.android.aapt.Resources.ConfigValue;
import com.android.aapt.Resources.Item;
import com.android.aapt.Resources.ResourceTable;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.TestRuntime.CfRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.transformers.ClassTransformer;
import com.android.tools.r8.transformers.MethodTransformer;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -34,12 +37,16 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Set;
import java.util.TreeMap;
+import java.util.TreeSet;
import java.util.stream.Collectors;
import org.junit.Assert;
import org.junit.rules.TemporaryFolder;
public class AndroidResourceTestingUtils {
+ private static final String RESOURCES_DESCRIPTOR =
+ TestBase.descriptor(com.android.tools.r8.androidresources.Resources.class);
enum RClassType {
STRING,
@@ -53,6 +60,32 @@
}
}
+ public static Path resourcesClassAsDex(TemporaryFolder temp) throws Exception {
+ return TestBase.testForD8(temp, Backend.DEX)
+ .addProgramClassFileData(resouresClassAsJavaClass())
+ .compile()
+ .writeToZip();
+ }
+
+ public static byte[] resouresClassAsJavaClass() throws IOException {
+ return transformer(com.android.tools.r8.androidresources.Resources.class)
+ .setClassDescriptor(DexItemFactory.androidResourcesDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ RESOURCES_DESCRIPTOR, DexItemFactory.androidResourcesDescriptorString)
+ .replaceClassDescriptorInMembers(
+ RESOURCES_DESCRIPTOR, DexItemFactory.androidResourcesDescriptorString)
+ .transform();
+ }
+
+ public static byte[] transformResourcesReferences(Class clazz) throws IOException {
+ return transformer(clazz)
+ .replaceClassDescriptorInMethodInstructions(
+ RESOURCES_DESCRIPTOR, DexItemFactory.androidResourcesDescriptorString)
+ .replaceClassDescriptorInMembers(
+ RESOURCES_DESCRIPTOR, DexItemFactory.androidResourcesDescriptorString)
+ .transform();
+ }
+
private static String rClassWithoutNamespaceAndOuter(Class clazz) {
return rClassWithoutNamespaceAndOuter(clazz.getName());
}
@@ -223,6 +256,8 @@
public static class AndroidTestResourceBuilder {
private String manifest;
private final Map<String, String> stringValues = new TreeMap<>();
+ private final Set<String> stringValuesWithExtraLanguage = new TreeSet<>();
+ private final Map<String, String> overlayableValues = new TreeMap<>();
private final Map<String, Integer> styleables = new TreeMap<>();
private final Map<String, byte[]> drawables = new TreeMap<>();
private final Map<String, String> xmlFiles = new TreeMap<>();
@@ -244,7 +279,7 @@
addStringValue(name, name);
}
if (rClassType == RClassType.DRAWABLE) {
- addDrawable(name, TINY_PNG);
+ addDrawable(name + ".png", TINY_PNG);
}
if (rClassType == RClassType.STYLEABLE) {
// Add 4 different values, i.e., the array will be 4 integers.
@@ -282,6 +317,16 @@
return this;
}
+ AndroidTestResourceBuilder addExtraLanguageString(String name) {
+ stringValuesWithExtraLanguage.add(name);
+ return this;
+ }
+
+ AndroidTestResourceBuilder setOverlayableFor(String type, String name) {
+ overlayableValues.put(type, name);
+ return this;
+ }
+
AndroidTestResourceBuilder setPackageId(int packageId) {
this.packageId = packageId;
return this;
@@ -298,7 +343,16 @@
Path resFolder = temp.newFolder("res").toPath();
Path valuesFolder = temp.newFolder("res", "values").toPath();
if (stringValues.size() > 0) {
- FileUtils.writeTextFile(valuesFolder.resolve("strings.xml"), createStringResourceXml());
+ FileUtils.writeTextFile(
+ valuesFolder.resolve("strings.xml"), createStringResourceXml(false));
+ if (stringValuesWithExtraLanguage.size() > 0) {
+ Path languageValues = temp.newFolder("res", "values-da").toPath();
+ FileUtils.writeTextFile(
+ languageValues.resolve("strings.xml"), createStringResourceXml(true));
+ }
+ }
+ if (overlayableValues.size() > 0) {
+ FileUtils.writeTextFile(valuesFolder.resolve("overlayable.xml"), createOverlayableXml());
}
if (styleables.size() > 0) {
FileUtils.writeTextFile(
@@ -401,11 +455,30 @@
new AndroidTestRClass(rClassJavaFile, rewrittenRClassFiles), output);
}
- private String createStringResourceXml() {
+ private String createOverlayableXml() {
StringBuilder stringBuilder = new StringBuilder("<resources>\n");
+
+ stringBuilder.append("<overlayable name=\"OurOverlayables\">\n");
+ stringBuilder.append("<policy type=\"public\">\n");
+ overlayableValues.forEach(
+ (type, name) ->
+ stringBuilder.append("<item type=\"" + type + "\" name=\"" + name + "\" />\n"));
+ stringBuilder.append("</policy>\n");
+ stringBuilder.append("</overlayable>");
+ stringBuilder.append("</resources>");
+ return stringBuilder.toString();
+ }
+
+ private String createStringResourceXml(boolean nonDefaultLanguage) {
+ StringBuilder stringBuilder = new StringBuilder("<resources>\n");
+ String languagePostFix = nonDefaultLanguage ? "_da" : "";
stringValues.forEach(
- (name, value) ->
- stringBuilder.append("<string name=\"" + name + "\">" + value + "</string>\n"));
+ (name, value) -> {
+ if (!nonDefaultLanguage || stringValuesWithExtraLanguage.contains(name)) {
+ stringBuilder.append(
+ "<string name=\"" + name + "\">" + value + languagePostFix + "</string>\n");
+ }
+ });
stringBuilder.append("</resources>");
return stringBuilder.toString();
}
@@ -496,4 +569,5 @@
// The XML document <x/> as a proto packed with AAPT2
public static final byte[] TINY_PROTO_XML =
new byte[] {0xa, 0x3, 0x1a, 0x1, 0x78, 0x1a, 0x2, 0x8, 0x1};
+
}
diff --git a/src/test/java/com/android/tools/r8/androidresources/Resources.java b/src/test/java/com/android/tools/r8/androidresources/Resources.java
new file mode 100644
index 0000000..ef01a99
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/Resources.java
@@ -0,0 +1,16 @@
+// 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.androidresources;
+
+public class Resources {
+ public static String GET_STRING_VALUE = "GET_STRING_VALUE";
+
+ // Returns the GET_STRING_VALUE to be able to distinguish resource inlined values from values
+ // we get from this call (i.e., not inlined). Inlined values are the actual values from the
+ // resource table.
+ public String getString(int id) {
+ return GET_STRING_VALUE;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/androidresources/TestArchiveCompression.java b/src/test/java/com/android/tools/r8/androidresources/TestArchiveCompression.java
new file mode 100644
index 0000000..e7d0e79
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/TestArchiveCompression.java
@@ -0,0 +1,108 @@
+// 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.androidresources;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.zip.ZipEntry;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TestArchiveCompression extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build();
+ }
+
+ public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+ return new AndroidTestResourceBuilder()
+ .withSimpleManifestAndAppNameString()
+ .addRClassInitializeWithDefaultValues(R.string.class, R.drawable.class)
+ .build(temp);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ AndroidTestResource testResources = getTestResources(temp);
+ Path resourceZip = testResources.getResourceZip();
+ assertTrue(ZipUtils.containsEntry(resourceZip, "res/drawable/foobar.png"));
+ assertTrue(ZipUtils.containsEntry(resourceZip, "res/drawable/unused_drawable.png"));
+ assertTrue(ZipUtils.containsEntry(resourceZip, "AndroidManifest.xml"));
+ assertTrue(ZipUtils.containsEntry(resourceZip, "resources.pb"));
+
+ validateCompression(resourceZip);
+ Path resourceOutput = temp.newFile("resources_out.zip").toPath();
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramClasses(FooBar.class)
+ .addAndroidResources(testResources, resourceOutput)
+ .addKeepMainRule(FooBar.class)
+ .compile()
+ .run(parameters.getRuntime(), FooBar.class)
+ .assertSuccess();
+ assertTrue(ZipUtils.containsEntry(resourceOutput, "res/drawable/foobar.png"));
+ assertFalse(ZipUtils.containsEntry(resourceOutput, "res/drawable/unused_drawable.png"));
+ assertTrue(ZipUtils.containsEntry(resourceOutput, "AndroidManifest.xml"));
+ assertTrue(ZipUtils.containsEntry(resourceOutput, "resources.pb"));
+ validateCompression(resourceOutput);
+ }
+
+ private static void validateCompression(Path resourceZip) throws IOException {
+ ZipUtils.iter(
+ resourceZip,
+ entry -> {
+ if (entry.getName().endsWith(".png")) {
+ assertEquals(ZipEntry.STORED, entry.getMethod());
+ } else {
+ assertEquals(ZipEntry.DEFLATED, entry.getMethod());
+ }
+ });
+ }
+
+ public static class FooBar {
+
+ public static void main(String[] args) {
+ if (System.currentTimeMillis() == 0) {
+ System.out.println(R.drawable.foobar);
+ System.out.println(R.string.bar);
+ System.out.println(R.string.foo);
+ }
+ }
+ }
+
+ public static class R {
+
+ public static class string {
+
+ public static int bar;
+ public static int foo;
+ public static int unused_string;
+ }
+
+ public static class drawable {
+
+ public static int foobar;
+ public static int unused_drawable;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/androidresources/TestResourceInlining.java b/src/test/java/com/android/tools/r8/androidresources/TestResourceInlining.java
new file mode 100644
index 0000000..bd97b91
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/TestResourceInlining.java
@@ -0,0 +1,173 @@
+// 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.androidresources;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TestResourceInlining extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean optimize;
+
+ @Parameter(2)
+ public boolean addResourcesSubclass;
+
+ @Parameters(name = "{0}, optimize: {1}, addResourcesSubclass: {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDefaultDexRuntime().withAllApiLevels().build(),
+ BooleanUtils.values(),
+ BooleanUtils.values());
+ }
+
+ public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+ return new AndroidTestResourceBuilder()
+ .withSimpleManifestAndAppNameString()
+ .addRClassInitializeWithDefaultValues(R.string.class)
+ .setOverlayableFor("string", "overlayable")
+ .addExtraLanguageString("bar")
+ .build(temp);
+ }
+
+ public static Path getAndroidResourcesClassInJar(TemporaryFolder temp) throws Exception {
+ byte[] androidResourcesClass =
+ transformer(Resources.class)
+ .setClassDescriptor(DexItemFactory.androidResourcesDescriptorString)
+ .transform();
+ return testForR8(temp, Backend.DEX)
+ .addProgramClassFileData(androidResourcesClass)
+ .compile()
+ .writeToZip();
+ }
+
+ public byte[] getResourcesSubclass() throws IOException {
+ return transformer(ResourcesSubclass.class)
+ .setSuper(DexItemFactory.androidResourcesDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(Resources.class), DexItemFactory.androidResourcesDescriptorString)
+ .transform();
+ }
+
+ @Test
+ public void testR8Optimized() throws Exception {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramClassFileData(
+ AndroidResourceTestingUtils.transformResourcesReferences(FooBar.class))
+ .applyIf(
+ addResourcesSubclass,
+ builder -> {
+ builder.addProgramClassFileData(getResourcesSubclass());
+ builder.addKeepClassRules(ResourcesSubclass.class);
+ })
+ .addAndroidResources(getTestResources(temp))
+ .addKeepMainRule(FooBar.class)
+ .applyIf(optimize, R8TestBuilder::enableOptimizedShrinking)
+ .addRunClasspathFiles(AndroidResourceTestingUtils.resourcesClassAsDex(temp))
+ .compile()
+ .inspectShrunkenResources(
+ resourceTableInspector -> {
+ // We should eventually remove this when optimizing
+ resourceTableInspector.assertContainsResourceWithName("string", "foo");
+ // Has multiple values, don't inline
+ resourceTableInspector.assertContainsResourceWithName("string", "bar");
+ // Has overlayable value, don't inline
+ resourceTableInspector.assertContainsResourceWithName("string", "bar");
+ resourceTableInspector.assertDoesNotContainResourceWithName(
+ "string", "unused_string");
+ })
+ .inspect(
+ inspector -> {
+ // We should have removed one of the calls to getString if we are optimizing.
+ MethodSubject mainMethodSubject = inspector.clazz(FooBar.class).mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertEquals(
+ mainMethodSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isInvokeVirtual)
+ .filter(
+ i ->
+ i.getMethod()
+ .holder
+ .descriptor
+ .toString()
+ .equals(DexItemFactory.androidResourcesDescriptorString))
+ .count(),
+ optimize && !addResourcesSubclass ? 2 : 3);
+ })
+ .run(parameters.getRuntime(), FooBar.class)
+ .applyIf(
+ optimize && !addResourcesSubclass,
+ result -> {
+ result.assertSuccessWithOutputLines(
+ "foo", Resources.GET_STRING_VALUE, Resources.GET_STRING_VALUE);
+ })
+ .applyIf(
+ !optimize || addResourcesSubclass,
+ result -> {
+ result.assertSuccessWithOutputLines(
+ Resources.GET_STRING_VALUE,
+ Resources.GET_STRING_VALUE,
+ Resources.GET_STRING_VALUE);
+ });
+ }
+
+ public static class FooBar {
+
+ public static void main(String[] args) {
+ Resources resources = new Resources();
+ // Ensure that we correctly handle the out value propagation
+ String s = resources.getString(R.string.foo);
+ String t = "X";
+ String u = System.currentTimeMillis() > 0 ? s : t;
+ System.out.println(u);
+ System.out.println(resources.getString(R.string.bar));
+ System.out.println(resources.getString(R.string.overlayable));
+ }
+ }
+
+ public static class ResourcesSubclass extends Resources {
+
+ @Override
+ public String getString(int id) {
+ System.out.println("foobar");
+ return super.getString(id);
+ }
+ }
+
+ public static class R {
+
+ public static class string {
+ public static int foo;
+ public static int bar;
+ public static int overlayable;
+ public static int unused_string;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java b/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java
index da0f3d1..8900bd3 100644
--- a/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java
+++ b/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java
@@ -35,8 +35,7 @@
private static final Collection<Class<?>> CLASSES =
ImmutableList.of(ClassRetained.class, SourceRetained.class, RuntimeRetained.class, A.class);
- private static final String EXPECTED =
- StringUtils.lines("@" + RuntimeRetained.class.getName() + "()");
+ private static final String EXPECTED = StringUtils.lines(RuntimeRetained.class.getName());
@Parameters(name = "{0}, intermediate:{1}")
public static List<Object[]> data() {
@@ -66,7 +65,7 @@
public static void main(String[] args) {
for (Annotation annotation : A.class.getAnnotations()) {
- System.out.println(annotation);
+ System.out.println(annotation.annotationType().getName());
}
}
}
diff --git a/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress.java b/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress.java
index 037cc1d..a3ea4b1 100644
--- a/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress.java
+++ b/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress.java
@@ -38,7 +38,7 @@
System.out.print(index++ + ": ");
}
for (Annotation annotation : annotations[i]) {
- System.out.println(annotation);
+ System.out.println(annotation.annotationType().getName());
}
}
}
diff --git a/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress62300145TestRunner.java b/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress62300145TestRunner.java
index a88c723..d4b0a0e 100644
--- a/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress62300145TestRunner.java
+++ b/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress62300145TestRunner.java
@@ -39,8 +39,8 @@
@Override
public String getExpected() {
return StringUtils.joinLines(
- "0: @com.android.tools.r8.examples.regress_62300145.Regress$A()",
- "1: @com.android.tools.r8.examples.regress_62300145.Regress$A()",
+ "0: com.android.tools.r8.examples.regress_62300145.Regress$A",
+ "1: com.android.tools.r8.examples.regress_62300145.Regress$A",
"2: ");
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringFormatTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringFormatTest.java
index 0f49f15..fa21a9f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringFormatTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringFormatTest.java
@@ -96,7 +96,6 @@
R8TestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(MAIN, TestFormattable.class)
- .addOptionsModification(options -> options.enableStringFormatOptimization = true)
.enableInliningAnnotations()
.addKeepMainRule(MAIN)
.setMinApi(parameters)
@@ -110,7 +109,6 @@
R8TestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(MAIN, TestFormattable.class)
- .addOptionsModification(options -> options.enableStringFormatOptimization = true)
.enableInliningAnnotations()
.addKeepMainRule(MAIN)
.setMinApi(parameters)
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 0914396..1225db2 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
@@ -67,25 +67,21 @@
}
}
- /// 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.
+ /* INCLUDE DOC: UsesReflectionOnVirtualMethod
+ For example, if your program is reflectively invoking a method, you
+ should annotate the method that is doing the reflection. The annotation must describe the
+ assumptions the reflective code makes.
- <p>In the following example, the method `foo` is looking up the method with the name
+ 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.
+ 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.
+ The assumptions the code makes are that all methods with the name
+ `hiddenMethod` and the empty list of parameters must remain valid for `getDeclaredMethod` if they
+ are objects that are instances of the class `BaseClass` or subclasses thereof.
+ INCLUDE END */
- <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 */
+ // INCLUDE CODE: UsesReflectionOnVirtualMethod
static class MyClass {
@UsesReflection({
@@ -99,7 +95,7 @@
}
}
- // DOC END
+ // INCLUDE END
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinterBase.java b/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinterBase.java
index f193da6..e83aed8 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinterBase.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinterBase.java
@@ -13,10 +13,16 @@
public abstract class DocPrinterBase<T> {
private String title = null;
+ private String returnDesc = null;
+ private String deprecatedDesc = null;
private final List<String> additionalLines = new ArrayList<>();
public abstract T self();
+ private boolean isEmptyOrJustTitle() {
+ return returnDesc == null && deprecatedDesc == null && additionalLines.isEmpty();
+ }
+
public T clearDocLines() {
additionalLines.clear();
return self();
@@ -29,6 +35,20 @@
return self();
}
+ public T setDocReturn(String desc) {
+ returnDesc = desc;
+ return self();
+ }
+
+ public boolean isDeprecated() {
+ return deprecatedDesc != null;
+ }
+
+ public T setDeprecated(String desc) {
+ deprecatedDesc = desc;
+ return self();
+ }
+
public T addParagraph(String... lines) {
return addParagraph(Arrays.asList(lines));
}
@@ -67,11 +87,11 @@
}
public void printDoc(Consumer<String> println) {
- assert additionalLines.isEmpty() || title != null;
if (title == null) {
+ assert isEmptyOrJustTitle();
return;
}
- if (additionalLines.isEmpty()) {
+ if (isEmptyOrJustTitle()) {
println.accept("/** " + title + " */");
return;
}
@@ -83,6 +103,12 @@
}
println.accept(" * " + line);
}
+ if (returnDesc != null) {
+ println.accept(" * @return " + returnDesc);
+ }
+ if (deprecatedDesc != null) {
+ println.accept(" * @deprecated " + deprecatedDesc);
+ }
println.accept(" */");
}
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java
new file mode 100644
index 0000000..f4fd1a8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java
@@ -0,0 +1,247 @@
+// 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.utils;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.keepanno.annotations.KeepBinding;
+import com.android.tools.r8.keepanno.annotations.KeepCondition;
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+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.doctests.UsesReflectionDocumentationTest;
+import com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.Generator;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class KeepAnnoMarkdownGenerator {
+
+ public static void generateMarkdownDoc(Generator generator) {
+ try {
+ new KeepAnnoMarkdownGenerator(generator).internalGenerateMarkdownDoc();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static String JAVADOC_URL =
+ "https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/";
+
+ private static final String INCLUDE_MD_START = "[[[INCLUDE";
+ private static final String INCLUDE_MD_DOC_START = "[[[INCLUDE DOC";
+ private static final String INCLUDE_MD_CODE_START = "[[[INCLUDE CODE";
+ private static final String INCLUDE_MD_END = "]]]";
+
+ private static final String INCLUDE_DOC_START = "INCLUDE DOC:";
+ private static final String INCLUDE_DOC_END = "INCLUDE END";
+ private static final String INCLUDE_CODE_START = "INCLUDE CODE:";
+ private static final String INCLUDE_CODE_END = "INCLUDE END";
+
+ private final Generator generator;
+ private final Map<String, String> typeLinkReplacements;
+ private Map<String, String> docReplacements = new HashMap<>();
+ private Map<String, String> codeReplacements = new HashMap<>();
+
+ public KeepAnnoMarkdownGenerator(Generator generator) {
+ this.generator = generator;
+ typeLinkReplacements =
+ getTypeLinkReplacements(
+ KeepEdge.class,
+ KeepBinding.class,
+ KeepTarget.class,
+ KeepCondition.class,
+ UsesReflection.class,
+ UsedByReflection.class,
+ UsedByNative.class,
+ KeepForApi.class);
+ populateCodeAndDocReplacements(UsesReflectionDocumentationTest.class);
+ }
+
+ private Map<String, String> getTypeLinkReplacements(Class<?>... classes) {
+ ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
+ for (Class<?> clazz : classes) {
+ builder.put("`@" + clazz.getSimpleName() + "`", getMdLink(clazz));
+ }
+ return builder.build();
+ }
+
+ private void populateCodeAndDocReplacements(Class<?>... classes) {
+ try {
+ for (Class<?> clazz : classes) {
+ Path sourceFile = ToolHelper.getSourceFileForTestClass(clazz);
+ String text = FileUtils.readTextFile(sourceFile, StandardCharsets.UTF_8);
+ extractMarkers(text, INCLUDE_DOC_START, INCLUDE_DOC_END, docReplacements);
+ extractMarkers(text, INCLUDE_CODE_START, INCLUDE_CODE_END, codeReplacements);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void extractMarkers(
+ String text, String markerKeyStart, String markerKeyEnd, Map<String, String> replacementMap) {
+ int index = text.indexOf(markerKeyStart);
+ while (index >= 0) {
+ int markerTitleEnd = text.indexOf('\n', index);
+ if (markerTitleEnd < 0) {
+ throw new RuntimeException("Failed to find end marker title");
+ }
+ int end = text.indexOf(markerKeyEnd, index);
+ if (end < 0) {
+ throw new RuntimeException("Failed to find end marker");
+ }
+ int endBeforeNewLine = text.lastIndexOf('\n', end);
+ if (endBeforeNewLine < markerTitleEnd) {
+ throw new RuntimeException("No new-line before end marker");
+ }
+ String markerTitle = text.substring(index + markerKeyStart.length(), markerTitleEnd);
+ String includeContent = text.substring(markerTitleEnd + 1, endBeforeNewLine);
+ String old = replacementMap.put(markerTitle.trim(), includeContent);
+ if (old != null) {
+ throw new RuntimeException("Duplicate definition of marker");
+ }
+ index = text.indexOf(markerKeyStart, end);
+ }
+ }
+
+ private String getMdLink(Class<?> clazz) {
+ String url = JAVADOC_URL + clazz.getTypeName().replace('.', '/') + ".html";
+ return "[@" + clazz.getSimpleName() + "](" + url + ")";
+ }
+
+ private void println() {
+ generator.println("");
+ }
+
+ private void println(String line) {
+ generator.println(line);
+ }
+
+ private void internalGenerateMarkdownDoc() throws IOException {
+ Path template = Paths.get("doc/keepanno-guide.template.md");
+ println("[comment]: <> (DO NOT EDIT - GENERATED FILE)");
+ println("[comment]: <> (Changes should be made in " + template + ")");
+ println();
+ List<String> readAllLines = FileUtils.readAllLines(template);
+ for (int i = 0; i < readAllLines.size(); i++) {
+ String line = readAllLines.get(i);
+ try {
+ processLine(line, generator);
+ } catch (Exception e) {
+ System.err.println("Parse error on line " + (i + 1) + ":");
+ System.err.println(line);
+ System.err.println(e.getMessage());
+ }
+ }
+ }
+
+ private String replaceCodeAndDocMarkers(String line) {
+ String originalLine = line;
+ line = line.trim();
+ if (!line.startsWith(INCLUDE_MD_START)) {
+ return originalLine;
+ }
+ int keyStartIndex = line.indexOf(':');
+ if (!line.endsWith(INCLUDE_MD_END) || keyStartIndex < 0) {
+ throw new RuntimeException("Invalid include directive");
+ }
+ String key = line.substring(keyStartIndex + 1, line.length() - INCLUDE_MD_END.length());
+ if (line.startsWith(INCLUDE_MD_DOC_START)) {
+ return replaceDoc(key);
+ }
+ if (line.startsWith(INCLUDE_MD_CODE_START)) {
+ return replaceCode(key);
+ }
+ throw new RuntimeException("Unknown replacement marker");
+ }
+
+ private String replaceDoc(String key) {
+ String replacement = docReplacements.get(key);
+ if (replacement == null) {
+ throw new RuntimeException("No replacement defined for " + key);
+ }
+ return unindentLines(replacement, new StringBuilder()).toString();
+ }
+
+ private String replaceCode(String key) {
+ String replacement = codeReplacements.get(key);
+ if (replacement == null) {
+ throw new RuntimeException("No replacement defined for " + key);
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append("```\n");
+ unindentLines(replacement, builder);
+ builder.append("```\n");
+ return builder.toString();
+ }
+
+ private StringBuilder unindentLines(String replacement, StringBuilder builder) {
+ int shortestSpacePrefix = Integer.MAX_VALUE;
+ List<String> lines = StringUtils.split(replacement, '\n');
+ for (String line : lines) {
+ if (!line.isEmpty()) {
+ shortestSpacePrefix = Math.min(shortestSpacePrefix, findFirstNonSpaceIndex(line));
+ }
+ }
+ if (shortestSpacePrefix > 0) {
+ for (String line : lines) {
+ if (!line.isEmpty()) {
+ builder.append(line.substring(shortestSpacePrefix));
+ }
+ builder.append('\n');
+ }
+ } else {
+ builder.append(replacement);
+ builder.append('\n');
+ }
+ return builder;
+ }
+
+ private int findFirstNonSpaceIndex(String line) {
+ for (int i = 0; i < line.length(); i++) {
+ if (line.charAt(i) != ' ') {
+ return i;
+ }
+ }
+ return line.length();
+ }
+
+ private String tryLinkReplacements(String line) {
+ int index = line.indexOf("`@");
+ if (index < 0) {
+ return null;
+ }
+ int end = line.indexOf('`', index + 1);
+ if (end < 0) {
+ throw new RuntimeException("No end marker on line: " + line);
+ }
+ String typeLink = line.substring(index, end + 1);
+ String replacement = typeLinkReplacements.get(typeLink);
+ if (replacement == null) {
+ throw new RuntimeException("Unknown type link: " + typeLink);
+ }
+ return line.replace(typeLink, replacement);
+ }
+
+ private void processLine(String line, Generator generator) {
+ line = replaceCodeAndDocMarkers(line);
+ String replacement = tryLinkReplacements(line);
+ while (replacement != null) {
+ line = replacement;
+ replacement = tryLinkReplacements(line);
+ }
+ generator.println(line);
+ }
+}
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 67a5fe0..005c344 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
@@ -72,6 +72,9 @@
void generate(Generator generator) {
printDoc(generator::println);
+ if (isDeprecated()) {
+ generator.println("@Deprecated");
+ }
if (valueDefault == null) {
generator.println(valueType + " " + name + "();");
} else {
@@ -203,7 +206,7 @@
}
}
- private static class Generator {
+ public static class Generator {
private static final List<Class<?>> ANNOTATION_IMPORTS =
ImmutableList.of(ElementType.class, Retention.class, RetentionPolicy.class, Target.class);
@@ -222,13 +225,14 @@
}
private void println() {
- // Don't indent empty lines.
- writer.println();
+ println("");
}
- private void println(String line) {
- assert line.length() > 0;
- writer.print(Strings.repeat(" ", indent));
+ public void println(String line) {
+ // Don't indent empty lines.
+ if (line.length() > 0) {
+ writer.print(Strings.repeat(" ", indent));
+ }
writer.println(line);
}
@@ -265,6 +269,7 @@
.addMember(
new GroupMember("description")
.setDocTitle("Optional description to document the reason for this annotation.")
+ .setDocReturn("The descriptive message. Defaults to no description.")
.defaultEmptyString());
}
@@ -279,8 +284,9 @@
new GroupMember("preconditions")
.setDocTitle(
"Conditions that should be satisfied for the annotation to be in effect.")
- .addParagraph(
- "Defaults to no conditions, thus trivially/unconditionally satisfied.")
+ .setDocReturn(
+ "The list of preconditions. "
+ + "Defaults to no conditions, thus trivially/unconditionally satisfied.")
.defaultEmptyArray(KeepCondition.class));
}
@@ -289,6 +295,7 @@
.addMember(
new GroupMember("consequences")
.setDocTitle("Consequences that must be kept if the annotation is in effect.")
+ .setDocReturn("The list of target consequences.")
.requiredValueOfArrayType(KeepTarget.class));
}
@@ -297,6 +304,7 @@
.addMember(
new GroupMember("value")
.setDocTitle("Consequences that must be kept if the annotation is in effect.")
+ .setDocReturn("The list of target consequences.")
.requiredValueOfArrayType(KeepTarget.class));
}
@@ -305,7 +313,9 @@
.addMember(
new GroupMember("additionalPreconditions")
.setDocTitle("Additional preconditions for the annotation to be in effect.")
- .addParagraph("Defaults to no additional preconditions.")
+ .setDocReturn(
+ "The list of additional preconditions. "
+ + "Defaults to no additional preconditions.")
.defaultEmptyArray("KeepCondition"));
}
@@ -314,7 +324,9 @@
.addMember(
new GroupMember("additionalTargets")
.setDocTitle(docTitle)
- .addParagraph("Defaults to no additional targets.")
+ .setDocReturn(
+ "List of additional target consequences. "
+ + "Defaults to no additional target consequences.")
.defaultEmptyArray("KeepTarget"));
}
@@ -327,6 +339,7 @@
.defaultType("KeepItemKind")
.defaultValue("KeepItemKind.DEFAULT")
.setDocTitle("Specify the kind of this item pattern.")
+ .setDocReturn("The kind for this pattern.")
.addParagraph("Possible values are:")
.addUnorderedList(
KeepItemKind.ONLY_CLASS.name(),
@@ -343,15 +356,16 @@
return new Group(OPTIONS_GROUP)
.addMember(
new GroupMember("allow")
- .setDocTitle(
- "Define the "
- + OPTIONS_GROUP
- + " that do not need to be preserved for the target.")
+ .setDocTitle("Define the " + OPTIONS_GROUP + " that are allowed to be modified.")
+ .addParagraph("The specified options do not need to be preserved for the target.")
+ .setDocReturn("Options allowed to be modified for the target.")
.defaultEmptyArray("KeepOption"))
.addMember(
new GroupMember("disallow")
.setDocTitle(
- "Define the " + OPTIONS_GROUP + " that *must* be preserved for the target.")
+ "Define the " + OPTIONS_GROUP + " that are not allowed to be modified.")
+ .addParagraph("The specified options *must* be preserved for the target.")
+ .setDocReturn("Options not allowed to be modified for the target.")
.defaultEmptyArray("KeepOption"))
.addDocFooterParagraph(
"If nothing is specified for "
@@ -368,12 +382,14 @@
.setDocTitle(
"Name with which other bindings, conditions or targets "
+ "can reference the bound item pattern.")
+ .setDocReturn("Name of the binding.")
.requiredStringValue();
}
private GroupMember classFromBinding() {
return new GroupMember("classFromBinding")
.setDocTitle("Define the " + CLASS_GROUP + " pattern by reference to a binding.")
+ .setDocReturn("The name of the binding that defines the class.")
.defaultEmptyString();
}
@@ -386,6 +402,7 @@
private GroupMember className() {
return new GroupMember("className")
.setDocTitle("Define the " + CLASS_NAME_GROUP + " pattern by fully qualified class name.")
+ .setDocReturn("The qualified class name that defines the class.")
.defaultEmptyString();
}
@@ -393,6 +410,7 @@
return new GroupMember("classConstant")
.setDocTitle(
"Define the " + CLASS_NAME_GROUP + " pattern by reference to a Class constant.")
+ .setDocReturn("The class-constant that defines the class.")
.defaultObjectClass();
}
@@ -409,6 +427,7 @@
"Define the "
+ INSTANCE_OF_GROUP
+ " pattern as classes that are instances of the fully qualified class name.")
+ .setDocReturn("The qualified class name that defines what instance-of the class must be.")
.defaultEmptyString();
}
@@ -418,6 +437,7 @@
"Define the "
+ INSTANCE_OF_GROUP
+ " pattern as classes that are instances the referenced Class constant.")
+ .setDocReturn("The class constant that defines what instance-of the class must be.")
.defaultObjectClass();
}
@@ -433,6 +453,7 @@
"Define the "
+ INSTANCE_OF_GROUP
+ " pattern as classes that are instances of the fully qualified class name.")
+ .setDocReturn("The qualified class name that defines what instance-of the class must be.")
.addParagraph(getInstanceOfExclusiveDoc())
.defaultEmptyString();
}
@@ -444,6 +465,7 @@
+ INSTANCE_OF_GROUP
+ " pattern as classes that are instances the referenced Class constant.")
.addParagraph(getInstanceOfExclusiveDoc())
+ .setDocReturn("The class constant that defines what instance-of the class must be.")
.defaultObjectClass();
}
@@ -454,7 +476,9 @@
+ INSTANCE_OF_GROUP
+ " pattern as classes extending the fully qualified class name.")
.addParagraph(getInstanceOfExclusiveDoc())
- .addParagraph("This property is deprecated, use instanceOfClassName instead.")
+ .setDeprecated(
+ "This property is deprecated, use " + docLink(instanceOfClassName()) + " instead.")
+ .setDocReturn("The class name that defines what the class must extend.")
.defaultEmptyString();
}
@@ -465,7 +489,11 @@
+ INSTANCE_OF_GROUP
+ " pattern as classes extending the referenced Class constant.")
.addParagraph(getInstanceOfExclusiveDoc())
- .addParagraph("This property is deprecated, use instanceOfClassConstant instead.")
+ .setDeprecated(
+ "This property is deprecated, use "
+ + docLink(instanceOfClassConstant())
+ + " instead.")
+ .setDocReturn("The class constant that defines what the class must extend.")
.defaultObjectClass();
}
@@ -490,6 +518,7 @@
"Mutually exclusive with all other class and member pattern properties.",
"When a member binding is referenced this item is defined to be that item,",
"including its class and member patterns.")
+ .setDocReturn("The binding name that defines the member.")
.defaultEmptyString());
}
@@ -501,6 +530,7 @@
.addParagraph(
"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"));
}
@@ -531,6 +561,7 @@
.setDocTitle("Define the method-access pattern by matching on access flags.")
.addParagraph(getMutuallyExclusiveForMethodProperties())
.addParagraph(getMethodDefaultDoc("any method-access flags"))
+ .setDocReturn("The method access-flag constraints that must be met.")
.defaultEmptyArray("MethodAccessFlags"));
}
@@ -541,6 +572,7 @@
.setDocTitle("Define the method-name pattern by an exact method name.")
.addParagraph(getMutuallyExclusiveForMethodProperties())
.addParagraph(getMethodDefaultDoc("any method name"))
+ .setDocReturn("The exact method name of the method.")
.defaultEmptyString());
}
@@ -552,6 +584,7 @@
"Define the method return-type pattern by a fully qualified type or 'void'.")
.addParagraph(getMutuallyExclusiveForMethodProperties())
.addParagraph(getMethodDefaultDoc("any return type"))
+ .setDocReturn("The qualified type name of the method return type.")
.defaultEmptyString());
}
@@ -563,6 +596,7 @@
"Define the method parameters pattern by a list of fully qualified types.")
.addParagraph(getMutuallyExclusiveForMethodProperties())
.addParagraph(getMethodDefaultDoc("any parameters"))
+ .setDocReturn("The list of qualified type names of the method parameters.")
.defaultType("String[]")
.defaultValue("{\"<default>\"}"));
}
@@ -574,6 +608,7 @@
.setDocTitle("Define the field-access pattern by matching on access flags.")
.addParagraph(getMutuallyExclusiveForFieldProperties())
.addParagraph(getFieldDefaultDoc("any field-access flags"))
+ .setDocReturn("The field access-flag constraints that must be met.")
.defaultEmptyArray("FieldAccessFlags"));
}
@@ -584,6 +619,7 @@
.setDocTitle("Define the field-name pattern by an exact field name.")
.addParagraph(getMutuallyExclusiveForFieldProperties())
.addParagraph(getFieldDefaultDoc("any field name"))
+ .setDocReturn("The exact field name of the field.")
.defaultEmptyString());
}
@@ -594,6 +630,7 @@
.setDocTitle("Define the field-type pattern by a fully qualified type.")
.addParagraph(getMutuallyExclusiveForFieldProperties())
.addParagraph(getFieldDefaultDoc("any type"))
+ .setDocReturn("The qualified type name of the field type.")
.defaultEmptyString());
}
@@ -674,12 +711,7 @@
println();
withIndent(
() -> {
- new GroupMember("bindingName")
- .setDocTitle(
- "Name with which other bindings, conditions or targets can reference the bound"
- + " item pattern.")
- .requiredValueOfType("String")
- .generate(this);
+ bindingName().generate(this);
println();
getKindGroup().generate(this);
println();
@@ -939,6 +971,10 @@
return "{@link " + simpleName(clazz) + "}";
}
+ private String docLink(GroupMember member) {
+ return "{@link #" + member.name + "}";
+ }
+
private String docLink(KeepItemKind kind) {
return "{@link KeepItemKind#" + kind.name() + "}";
}
@@ -1236,11 +1272,16 @@
PrintStream printStream = new PrintStream(byteStream);
Generator generator = new Generator(printStream);
fn.accept(generator);
- String formatted = CodeGenerationBase.formatRawOutput(byteStream.toString());
+ String formatted = byteStream.toString();
+ if (file.toString().endsWith(".java")) {
+ formatted = CodeGenerationBase.formatRawOutput(formatted);
+ }
Files.write(Paths.get(ToolHelper.getProjectRoot()).resolve(file), formatted.getBytes());
}
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");
@@ -1260,4 +1301,5 @@
g -> g.generateUsedByX("UsedByNative", "accessed from native code via JNI"));
}
}
+
}
diff --git a/tools/archive.py b/tools/archive.py
index fd250dd..a906a82 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -122,6 +122,21 @@
print('INFO: Open files hard limit: %s' % hard)
+def UploadDir(src_dir, version_or_path, dst_dir, is_main, options):
+ destination = GetUploadDestination(version_or_path, dst_dir, is_main)
+ print(f'Uploading {src_dir} to {destination}')
+ if options.dry_run:
+ if options.dry_run_output:
+ dry_run_destination = os.path.join(options.dry_run_output, version_or_path, dst_dir)
+ print(f'Dry run, not actually uploading. Copying to {dry_run_destination}')
+ shutil.copytree(src_dir, dry_run_destination)
+ else:
+ print('Dry run, not actually uploading')
+ else:
+ utils.upload_directory_to_cloud_storage(src_dir, destination)
+ print(f'Directory available at: {GetUrl(version_or_path, dst_dir, is_main)}')
+
+
def Main():
(options, args) = ParseOptions()
Run(options)
@@ -171,7 +186,9 @@
if (not options.skip_gradle_build):
gradle.RunGradle([
utils.GRADLE_TASK_CONSOLIDATED_LICENSE,
- utils.GRADLE_TASK_KEEP_ANNO_JAR, utils.GRADLE_TASK_R8,
+ utils.GRADLE_TASK_KEEP_ANNO_JAR,
+ utils.GRADLE_TASK_KEEP_ANNO_DOC,
+ utils.GRADLE_TASK_R8,
utils.GRADLE_TASK_R8LIB, utils.GRADLE_TASK_R8LIB_NO_DEPS,
utils.GRADLE_TASK_THREADING_MODULE_BLOCKING,
utils.GRADLE_TASK_THREADING_MODULE_SINGLE_THREADED,
@@ -229,6 +246,20 @@
'-PspdxRevision=' + GetGitHash()
])
+ # Upload keep-anno javadoc to a fixed "docs" location.
+ if is_main:
+ version_or_path = 'docs'
+ dst_dir = 'keepanno/javadoc'
+ UploadDir(utils.KEEPANNO_ANNOTATIONS_DOC, version_or_path, dst_dir, is_main, options)
+
+ # Upload directories.
+ dirs_for_archiving = [
+ (utils.KEEPANNO_ANNOTATIONS_DOC, 'keepanno/javadoc'),
+ ]
+ for (src_dir, dst_dir) in dirs_for_archiving:
+ UploadDir(src_dir, version, dst_dir, is_main, options)
+
+ # Upload files.
for_archiving = [
utils.R8_JAR, utils.R8LIB_JAR, utils.R8LIB_JAR + '.map',
utils.R8LIB_JAR + '_map.zip', utils.R8_FULL_EXCLUDE_DEPS_JAR,
@@ -258,8 +289,8 @@
print('Uploading %s to %s' % (tagged_jar, destination))
if options.dry_run:
if options.dry_run_output:
- dry_run_destination = os.path.join(options.dry_run_output,
- file_name)
+ dry_run_destination = os.path.join(
+ options.dry_run_output, version, file_name)
print('Dry run, not actually uploading. Copying to ' +
dry_run_destination)
shutil.copyfile(tagged_jar, dry_run_destination)
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 2d1a3a8..ce09db8 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -11,6 +11,7 @@
import zipfile
import archive
+import gradle
import jdk
import retrace
import utils
@@ -139,6 +140,18 @@
help='Run compilation in specified mode',
choices=['debug', 'release'],
default=None)
+ parser.add_argument(
+ '--ignore-features',
+ help="Don't split into features when features are present."
+ ' Instead include feature code in main app output.'
+ ' This is always the case when compiler is d8.',
+ default=False,
+ action='store_true')
+ parser.add_argument(
+ '--no-build',
+ help="Don't build when using --version main",
+ default=False,
+ action='store_true')
return parser
@@ -348,7 +361,9 @@
def determine_feature_output(feature_jar, temp):
- return os.path.join(temp, os.path.basename(feature_jar)[:-4] + ".out.jar")
+ return os.path.join(
+ args.output if args.output and os.path.isdir(args.output) else temp,
+ os.path.basename(feature_jar)[:-4] + ".out.jar")
def determine_program_jar(args, dump):
@@ -401,6 +416,10 @@
def download_distribution(version, args, temp):
nolib = args.nolib
if version == 'main':
+ if not args.no_build:
+ gradle.RunGradle(
+ [utils.GRADLE_TASK_R8] if nolib else [utils.GRADLE_TASK_R8LIB]
+ )
return utils.R8_JAR if nolib else utils.R8LIB_JAR
if version == 'source':
return '%s:%s' % (utils.BUILD_JAVA_MAIN_DIR, utils.ALL_DEPS_JAR)
@@ -574,10 +593,13 @@
else:
cmd.extend(['--source', program_jar])
for feature_jar in dump.feature_jars():
- cmd.extend([
- '--feature-jar', feature_jar,
- determine_feature_output(feature_jar, temp)
- ])
+ if not args.ignore_features and compiler != 'd8':
+ cmd.extend([
+ '--feature-jar', feature_jar,
+ determine_feature_output(feature_jar, temp)
+ ])
+ else:
+ cmd.append(feature_jar)
if dump.library_jar():
cmd.extend(['--lib', dump.library_jar()])
if dump.classpath_jar() and not is_l8_compiler(compiler):
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 116a52c..ce83d32 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -121,9 +121,7 @@
validate_version_change_diff(version_diff_output, "main",
version)
- cmd = ['git', 'cl', 'upload', '--no-squash']
- if args.bypass_hooks:
- cmd.append('--bypass-hooks')
+ cmd = ['git', 'cl', 'upload', '--no-squash', '--bypass-hooks']
maybe_check_call(args, cmd)
if args.dry_run:
diff --git a/tools/utils.py b/tools/utils.py
index 597f8b1..e859748 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -43,6 +43,7 @@
GRADLE_TASK_CLEAN_TEST = ':test:cleanTest'
GRADLE_TASK_CONSOLIDATED_LICENSE = ':main:consolidatedLicense'
GRADLE_TASK_KEEP_ANNO_JAR = ':keepanno:keepAnnoAnnotationsJar'
+GRADLE_TASK_KEEP_ANNO_DOC = ':keepanno:keepAnnoAnnotationsDoc'
GRADLE_TASK_R8 = ':main:r8WithRelocatedDeps'
GRADLE_TASK_R8LIB = ':test:assembleR8LibWithRelocatedDeps'
GRADLE_TASK_R8LIB_NO_DEPS = ':test:assembleR8LibNoDeps'
@@ -77,6 +78,7 @@
LIBRARY_DESUGAR_CONVERSIONS_ZIP = os.path.join(
CUSTOM_CONVERSION_DIR, 'library_desugar_conversions.jar')
KEEPANNO_ANNOTATIONS_JAR = os.path.join(LIBS, 'keepanno-annotations.jar')
+KEEPANNO_ANNOTATIONS_DOC = os.path.join('d8_r8', 'keepanno', 'build', 'docs', 'javadoc')
DESUGAR_CONFIGURATION = os.path.join('src', 'library_desugar',
'desugar_jdk_libs.json')
@@ -391,6 +393,14 @@
PrintCmd(cmd)
subprocess.check_call(cmd)
+def upload_directory_to_cloud_storage(source, destination, parallel=True):
+ cmd = [get_gsutil()]
+ if parallel:
+ cmd += ['-m']
+ cmd += ['cp', '-R']
+ cmd += [source, destination]
+ PrintCmd(cmd)
+ subprocess.check_call(cmd)
def delete_file_from_cloud_storage(destination):
cmd = [get_gsutil(), 'rm', destination]