[keepanno] Improve handling of types provided by name
Map Kotlin primitive types to corresponding Java primitive types.
No support for Kotlin unsigned types for now. See b/444622187
Bug: b/392865072
Bug: b/444622187
Change-Id: I2557a7787444fe5bfe0223f1f76947dcd86e3a04
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index 7d4245c..3352549 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -69,6 +69,7 @@
import com.android.tools.r8.keepanno.ast.ParsingContext.FieldParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.MethodParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.PropertyParsingContext;
+import com.android.tools.r8.keepanno.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -127,6 +128,29 @@
return false;
}
+ private static String kotlinTypeToJavaType(String kotlinType) {
+ switch (kotlinType) {
+ case "Boolean":
+ return "boolean";
+ case "Byte":
+ return "byte";
+ case "Short":
+ return "short";
+ case "Char":
+ return "char";
+ case "Int":
+ return "int";
+ case "Long":
+ return "long";
+ case "Float":
+ return "float";
+ case "Double":
+ return "double";
+ default:
+ return kotlinType;
+ }
+ }
+
public static List<KeepDeclaration> readKeepEdges(byte[] classFileBytes) {
return internalReadKeepEdges(classFileBytes, true);
}
@@ -1489,7 +1513,9 @@
public void visit(String name, Object value) {
assert name == null;
if (value instanceof String) {
- builder.addParameterTypePattern(KeepTypePattern.fromDescriptor("L" + value + ";"));
+ builder.addParameterTypePattern(
+ KeepTypePattern.fromDescriptor(
+ DescriptorUtils.javaTypeToDescriptor(kotlinTypeToJavaType((String) value))));
} else {
super.visit(name, value);
}
@@ -1868,11 +1894,17 @@
return;
}
if (name.equals(UsesReflectionToAccessMethod.returnTypeName) && value instanceof String) {
- returnType =
- KeepMethodReturnTypePattern.fromType(
- KeepTypePattern.fromClass(
- KeepClassPattern.fromName(
- KeepQualifiedClassNamePattern.exact((String) value))));
+ if (value.equals("void") || value.equals("Unit")) {
+ returnType = KeepMethodReturnTypePattern.voidType();
+ } else {
+ returnType =
+ KeepMethodReturnTypePattern.fromType(
+ KeepTypePattern.fromClass(
+ KeepClassPattern.fromName(
+ KeepQualifiedClassNamePattern.exactFromDescriptor(
+ DescriptorUtils.javaTypeToDescriptor(
+ kotlinTypeToJavaType((String) value))))));
+ }
return;
}
super.visit(name, value);
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
index 9289894..51a508c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
@@ -390,6 +390,8 @@
return "byte";
case 'S':
return "short";
+ case 'C':
+ return "char";
case 'I':
return "int";
case 'J':
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/utils/DescriptorUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/utils/DescriptorUtils.java
index 6694cbc..c928efa 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/utils/DescriptorUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/utils/DescriptorUtils.java
@@ -3,7 +3,28 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.utils;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+// TODO(b/444623706): Avoid the duplication and multiple DescriptorUtils classes by making a shared
+// library for R8 and keepanno.
public class DescriptorUtils {
+ public static final char DESCRIPTOR_PACKAGE_SEPARATOR = '/';
+ public static final char JAVA_PACKAGE_SEPARATOR = '.';
+
+ private static final Map<String, String> typeNameToLetterMap =
+ ImmutableMap.<String, String>builder()
+ .put("void", "V")
+ .put("boolean", "Z")
+ .put("byte", "B")
+ .put("short", "S")
+ .put("char", "C")
+ .put("int", "I")
+ .put("long", "J")
+ .put("float", "F")
+ .put("double", "D")
+ .build();
+
public static boolean isValidClassDescriptor(String string) {
if (string.length() < 3
|| string.charAt(0) != 'L'
@@ -22,4 +43,37 @@
}
return true;
}
+
+ private static String internalToDescriptor(
+ String typeName, boolean shorty, boolean ignorePrimitives) {
+ String descriptor = null;
+ if (!ignorePrimitives) {
+ descriptor = typeNameToLetterMap.get(typeName);
+ }
+ if (descriptor != null) {
+ return descriptor;
+ }
+ // Must be some array or object type.
+ if (shorty) {
+ return "L";
+ }
+ if (typeName.endsWith("[]")) {
+ return "["
+ + internalToDescriptor(
+ typeName.substring(0, typeName.length() - 2), shorty, ignorePrimitives);
+ }
+ // Must be an object type.
+ return "L" + typeName.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR) + ";";
+ }
+
+ /**
+ * Convert a Java type name to a descriptor string.
+ *
+ * @param typeName the java type name
+ * @return the descriptor string
+ */
+ public static String javaTypeToDescriptor(String typeName) {
+ assert typeName.indexOf(DESCRIPTOR_PACKAGE_SEPARATOR) == -1;
+ return internalToDescriptor(typeName, false, false);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionForInstantiationMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionForInstantiationMultipleConstructorsTest.java
index 87134e3..6b6f091 100644
--- a/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionForInstantiationMultipleConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionForInstantiationMultipleConstructorsTest.java
@@ -8,6 +8,8 @@
import androidx.annotation.keep.UsesReflectionToConstruct;
import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
@@ -182,6 +184,45 @@
expectedRulesJava(IntAndLongConstructorsClassName.class));
}
+ @Test
+ public void testPrimitiveTypesAsTypeName() throws Exception {
+ testExtractedRules(
+ ImmutableList.of(
+ setAnnotationOnMethod(
+ ClassWithAnnotation.class,
+ MethodPredicate.onName("foo"),
+ builder ->
+ builder
+ .setAnnotationClass(
+ Reference.classFromBinaryName(
+ Reference.classFromClass(UsesReflectionToConstruct.class)
+ .getBinaryName()
+ + "$Container"))
+ .buildArray(
+ "value",
+ builder1 ->
+ builder1
+ .setAnnotationField(
+ null,
+ builder2 ->
+ builder2
+ .setAnnotationClass(
+ Reference.classFromClass(
+ UsesReflectionToConstruct.class))
+ .setField("classConstant", KeptClass.class)
+ .setArray("parameterTypeNames", "Int"))
+ .setAnnotationField(
+ null,
+ builder3 ->
+ builder3
+ .setAnnotationClass(
+ Reference.classFromClass(
+ UsesReflectionToConstruct.class))
+ .setField("classConstant", KeptClass.class)
+ .setArray("parameterTypeNames", "Long"))))),
+ expectedRulesJava(ClassWithAnnotation.class));
+ }
+
static class IntAndLongConstructorsClassName {
@UsesReflectionToConstruct(
@@ -220,6 +261,21 @@
"com.android.tools.r8.keepanno.androidx.kt.IntAndLongArgsConstructorsClassName"));
}
+ // Test class without annotation to be used by multiple tests inserting annotations using a
+ // transformer.
+ static class ClassWithAnnotation {
+
+ public void foo(Class<KeptClass> clazz) throws Exception {
+ if (clazz != null) {
+ System.out.println(clazz.getDeclaredMethods().length);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ new ClassWithAnnotation().foo(System.nanoTime() > 0 ? KeptClass.class : null);
+ }
+ }
+
static class KeptClass {
KeptClass() {
System.out.println("<init>()");
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessMethodTest.java b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessMethodTest.java
index 8755cca..0b6e382 100644
--- a/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessMethodTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessMethodTest.java
@@ -239,6 +239,45 @@
}
@Test
+ public void testPrimitiveTypesAsTypeName() throws Exception {
+ testExtractedRules(
+ ImmutableList.of(
+ setAnnotationOnMethod(
+ ClassWithAnnotation.class,
+ MethodPredicate.onName("foo"),
+ builder ->
+ builder
+ .setAnnotationClass(
+ Reference.classFromClass(UsesReflectionToAccessMethod.class))
+ .setField("classConstant", KeptClass.class)
+ .setField("methodName", "m")
+ .setArray(
+ "parameterTypeNames",
+ "boolean",
+ "byte",
+ "short",
+ "int",
+ "long",
+ "float",
+ "double",
+ "char",
+ "Boolean",
+ "Byte",
+ "Short",
+ "Int",
+ "Long",
+ "Float",
+ "Double",
+ "Char")
+ .setField("returnTypeName", "Unit"))),
+ getExpectedRulesJava(
+ ClassWithAnnotation.class,
+ "{ void m(boolean, byte, short, int, long, float, double, char, boolean, byte, short,"
+ + " int, long, float, double, char); }",
+ "{ void m$default(...); }"));
+ }
+
+ @Test
public void testAnyReturnTypeAndAnyParametersKotlin() throws Exception {
testExtractedRulesAndRunKotlin(
compilationResults,