Extend @Metadata compatibility checks to JVM extensions and properties.
Test: tools/test.py *metadata*
Test: tools/run_on_as_app.py --app rover-android --shrinker r8
Test: tools/run_on_as_app.py --app tivi --shrinker r8
Test: tools/run_on_as_app.py --app Simple-Calendar --shrinker r8
Bug: 70169921
Change-Id: Icc0752ed01e0833bd0bda31daedcb18ba42f4fe5
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 77545740..815e721 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -250,7 +250,7 @@
if (function != null) {
// Found a compatible function that is likely asked to keep.
builder.add(method);
- } else if (!method.isKotlinProperty(properties)) {
+ } else if (!method.isKotlinProperty(properties, appView)) {
// This could be a newly merged method that is not part of properties.
builder.add(method);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index a928529..39977cc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -398,35 +398,20 @@
return null;
}
- // E.g., property `prop: T` is mapped to `getProp()T`, `setProp(T)V`, `prop$annotations()V`.
- // TODO(b/70169921): Handle different name patterns via @JvmName.
- boolean isKotlinProperty(List<KmProperty> properties) {
- // TODO(b/70169921): Avoid decoding.
- String methodName = method.name.toString();
- if (!methodName.startsWith("get")
- && !methodName.startsWith("set")
- && !methodName.endsWith("$annotations")) {
- return false;
+ boolean isKotlinProperty(List<KmProperty> properties, AppView<?> appView) {
+ return findCompatibleKotlinProperty(properties, appView) != null;
+ }
+
+ KmProperty findCompatibleKotlinProperty(List<KmProperty> properties, AppView<?> appView) {
+ if (isStaticMember()) {
+ return null;
}
for (KmProperty property : properties) {
- String propertyName = property.getName();
- assert propertyName.length() > 0;
- String annotations = propertyName + "$annotations";
- if (methodName.equals(annotations)) {
- return true;
- }
- String capitalized =
- Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
- String getter = "get" + capitalized;
- if (methodName.equals(getter)) {
- return true;
- }
- String setter = "set" + capitalized;
- if (methodName.equals(setter)) {
- return true;
+ if (KotlinMetadataSynthesizer.isCompatibleProperty(property, this, appView)) {
+ return property;
}
}
- return false;
+ return null;
}
public boolean isOnlyInlinedIntoNestMembers() {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
new file mode 100644
index 0000000..66208ad
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
@@ -0,0 +1,148 @@
+// Copyright (c) 2020, 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.kotlin;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.List;
+import kotlinx.metadata.KmExtensionType;
+import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmFunctionExtensionVisitor;
+import kotlinx.metadata.KmFunctionVisitor;
+import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.KmPropertyExtensionVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
+import kotlinx.metadata.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
+
+class KotlinMetadataJvmExtensionUtils {
+
+ private static boolean isValidJvmMethodSignature(String desc) {
+ return desc != null
+ && !desc.isEmpty()
+ && desc.charAt(0) == '('
+ && desc.lastIndexOf('(') == 0
+ && desc.indexOf(')') != -1
+ && desc.indexOf(')') == desc.lastIndexOf(')')
+ && desc.lastIndexOf(')') < desc.length();
+ }
+
+ /**
+ * Extract return type from {@link JvmMethodSignature}.
+ *
+ * Example of JVM signature is: `JvmMethodSignature("getX", "()Ljava/lang/Object;").`
+ * In this case, the return type is "Ljava/lang/Object;".
+ */
+ static String returnTypeFromJvmMethodSignature(JvmMethodSignature signature) {
+ if (signature == null) {
+ return null;
+ }
+ String desc = signature.getDesc();
+ if (!isValidJvmMethodSignature(desc)) {
+ return null;
+ }
+ int index = desc.lastIndexOf(')');
+ assert desc.charAt(0) == '(' && 0 < index && index < desc.length() : signature.asString();
+ return desc.substring(index + 1);
+ }
+
+ /**
+ * Extract parameters from {@link JvmMethodSignature}.
+ *
+ * Example of JVM signature is: `JvmMethodSignature("setX", "(Ljava/lang/Object;)V").`
+ * In this case, the parameter is the list with "Ljava/lang/Object;" as the first element.
+ */
+ static List<String> parameterTypesFromJvmMethodSignature(JvmMethodSignature signature) {
+ if (signature == null) {
+ return null;
+ }
+ String desc = signature.getDesc();
+ if (!isValidJvmMethodSignature(desc)) {
+ return null;
+ }
+ int index = desc.lastIndexOf(')');
+ assert desc.charAt(0) == '(' && 0 < index && index < desc.length() : signature.asString();
+ String params = desc.substring(1, index);
+ if (params.isEmpty()) {
+ return ImmutableList.of();
+ } else {
+ return Arrays.asList(params.split(","));
+ }
+ }
+
+ static class KmFunctionProcessor {
+ // Custom name via @JvmName("..."). Otherwise, null.
+ private JvmMethodSignature signature = null;
+
+ KmFunctionProcessor(KmFunction kmFunction) {
+ kmFunction.accept(new KmFunctionVisitor() {
+ @Override
+ public KmFunctionExtensionVisitor visitExtensions(KmExtensionType type) {
+ if (type != JvmFunctionExtensionVisitor.TYPE) {
+ return null;
+ }
+ return new JvmFunctionExtensionVisitor() {
+ @Override
+ public void visit(JvmMethodSignature desc) {
+ assert signature == null : signature.asString();
+ signature = desc;
+ }
+ };
+ }
+ });
+ }
+
+ JvmMethodSignature signature() {
+ return signature;
+ }
+ }
+
+ static class KmPropertyProcessor {
+ private JvmFieldSignature fieldSignature = null;
+ // Custom getter via @get:JvmName("..."). Otherwise, null.
+ private JvmMethodSignature getterSignature = null;
+ // Custom getter via @set:JvmName("..."). Otherwise, null.
+ private JvmMethodSignature setterSignature = null;
+
+ KmPropertyProcessor(KmProperty kmProperty) {
+ kmProperty.accept(new KmPropertyVisitor() {
+ @Override
+ public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) {
+ if (type != JvmPropertyExtensionVisitor.TYPE) {
+ return null;
+ }
+ return new JvmPropertyExtensionVisitor() {
+ @Override
+ public void visit(
+ int flags,
+ JvmFieldSignature fieldDesc,
+ JvmMethodSignature getterDesc,
+ JvmMethodSignature setterDesc) {
+ assert fieldSignature == null : fieldSignature.asString();
+ fieldSignature = fieldDesc;
+ assert getterSignature == null : getterSignature.asString();
+ getterSignature = getterDesc;
+ assert setterSignature == null : setterSignature.asString();
+ setterSignature = setterDesc;
+ }
+ };
+ }
+ });
+ }
+
+ JvmFieldSignature fieldSignature() {
+ return fieldSignature;
+ }
+
+ JvmMethodSignature getterSignature() {
+ return getterSignature;
+ }
+
+ JvmMethodSignature setterSignature() {
+ return setterSignature;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index ff9ad15..80069a2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.kotlin;
import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
+import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.parameterTypesFromJvmMethodSignature;
+import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.returnTypeFromJvmMethodSignature;
import static com.android.tools.r8.utils.DescriptorUtils.descriptorToInternalName;
import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
import static kotlinx.metadata.FlagsKt.flagsOf;
@@ -14,14 +16,18 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmFunctionProcessor;
+import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmPropertyProcessor;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Box;
import java.util.List;
import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmProperty;
import kotlinx.metadata.KmType;
import kotlinx.metadata.KmValueParameter;
+import kotlinx.metadata.jvm.JvmMethodSignature;
public class KotlinMetadataSynthesizer {
@@ -69,6 +75,13 @@
return kmType;
}
+ private static boolean isCompatible(String desc, DexType type) {
+ if (desc == null || type == null) {
+ return false;
+ }
+ return desc.equals(type.toDescriptorString());
+ }
+
private static boolean isCompatible(KmType kmType, DexType type, AppView<?> appView) {
if (kmType == null || type == null) {
return false;
@@ -85,8 +98,32 @@
return descriptor.equals(getDescriptorFromKmType(kmType));
}
+ private static boolean isCompatibleJvmMethodSignature(
+ JvmMethodSignature signature, DexEncodedMethod method) {
+ String methodName = method.method.name.toString();
+ if (!signature.getName().equals(methodName)) {
+ return false;
+ }
+ if (!isCompatible(
+ returnTypeFromJvmMethodSignature(signature), method.method.proto.returnType)) {
+ return false;
+ }
+ List<String> parameterTypes = parameterTypesFromJvmMethodSignature(signature);
+ if (parameterTypes == null || parameterTypes.size() != method.method.proto.parameters.size()) {
+ return false;
+ }
+ for (int i = 0; i < method.method.proto.parameters.size(); i++) {
+ if (!isCompatible(parameterTypes.get(i), method.method.proto.parameters.values[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public static boolean isCompatibleConstructor(
KmConstructor constructor, DexEncodedMethod method, AppView<?> appView) {
+ // Note that targets for @JvmName don't include constructor. So, it's not necessary to process
+ // JvmMethodSignature inside JvmConstructorExtension.
List<KmValueParameter> parameters = constructor.getValueParameters();
if (method.method.proto.parameters.size() != parameters.size()) {
return false;
@@ -102,6 +139,13 @@
public static boolean isCompatibleFunction(
KmFunction function, DexEncodedMethod method, AppView<?> appView) {
+ // Check if a custom name is set to avoid name clash.
+ KmFunctionProcessor kmFunctionProcessor = new KmFunctionProcessor(function);
+ JvmMethodSignature jvmMethodSignature = kmFunctionProcessor.signature();
+ if (jvmMethodSignature != null && isCompatibleJvmMethodSignature(jvmMethodSignature, method)) {
+ return true;
+ }
+
if (!function.getName().equals(method.method.name.toString())) {
return false;
}
@@ -121,9 +165,15 @@
return true;
}
- // TODO(b/70169921): Handling JVM extensions as well.
public static boolean isCompatibleExtension(
KmFunction extension, DexEncodedMethod method, AppView<?> appView) {
+ // Check if a custom name is set to avoid name clash.
+ KmFunctionProcessor kmFunctionProcessor = new KmFunctionProcessor(extension);
+ JvmMethodSignature jvmMethodSignature = kmFunctionProcessor.signature();
+ if (jvmMethodSignature != null && isCompatibleJvmMethodSignature(jvmMethodSignature, method)) {
+ return true;
+ }
+
if (!extension.getName().equals(method.method.name.toString())) {
return false;
}
@@ -149,6 +199,58 @@
return true;
}
+ public static boolean isCompatibleProperty(
+ KmProperty kmProperty, DexEncodedMethod method, AppView<?> appView) {
+ KmPropertyProcessor kmPropertyProcessor = new KmPropertyProcessor(kmProperty);
+ // Check if a custom getter is defined via @get:JvmName("myGetter").
+ JvmMethodSignature getterSignature = kmPropertyProcessor.getterSignature();
+ if (getterSignature != null && isCompatibleJvmMethodSignature(getterSignature, method)) {
+ return true;
+ }
+ // Check if a custom setter is defined via @set:JvmName("mySetter").
+ JvmMethodSignature setterSignature = kmPropertyProcessor.setterSignature();
+ if (setterSignature != null && isCompatibleJvmMethodSignature(setterSignature, method)) {
+ return true;
+ }
+
+ // E.g., property `prop: T` is mapped to `getProp()T`, `setProp(T)V`, `prop$annotations()V`.
+ // For boolean property, though, getter is mapped to `isProp()Z`.
+ // TODO(b/70169921): Avoid decoding.
+ String methodName = method.method.name.toString();
+ if (!methodName.startsWith("is")
+ && !methodName.startsWith("get")
+ && !methodName.startsWith("set")
+ && !methodName.endsWith("$annotations")) {
+ return false;
+ }
+
+ String propertyName = kmProperty.getName();
+ assert propertyName.length() > 0;
+ String annotations = propertyName + "$annotations";
+ if (methodName.equals(annotations)) {
+ return true;
+ }
+ String capitalized =
+ Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
+ String returnTypeDescriptor = getDescriptorFromKmType(kmProperty.returnType);
+ String getterPrefix =
+ returnTypeDescriptor != null && returnTypeDescriptor.endsWith("Boolean;") ? "is" : "get";
+ String getter = getterPrefix + capitalized;
+ if (methodName.equals(getter)
+ && method.method.proto.parameters.size() == 0
+ && isCompatible(kmProperty.returnType, method.method.proto.returnType, appView)) {
+ return true;
+ }
+ String setter = "set" + capitalized;
+ if (methodName.equals(setter)
+ && method.method.proto.returnType.isVoidType()
+ && method.method.proto.parameters.size() == 1
+ && isCompatible(kmProperty.returnType, method.method.proto.parameters.values[0], appView)) {
+ return true;
+ }
+ return false;
+ }
+
static KmConstructor toRenamedKmConstructor(
DexEncodedMethod method,
KmConstructor original,