blob: 6e959b6d4c69e3ced72c00ecd6422a2a4fd664b3 [file] [log] [blame]
// Copyright (c) 2019, 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 static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
import static com.android.tools.r8.kotlin.Kotlin.NAME;
import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
import static kotlinx.metadata.FlagsKt.flagsOf;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
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.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
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.JvmExtensionsKt;
class KotlinMetadataSynthesizer {
static boolean isExtension(KmFunction kmFunction) {
return kmFunction.getReceiverParameterType() != null;
}
static boolean isExtension(KmProperty kmProperty) {
return kmProperty.getReceiverParameterType() != null;
}
static KmType toKmType(String descriptor) {
KmType kmType = new KmType(flagsOf());
kmType.visitClass(descriptorToKotlinClassifier(descriptor));
return kmType;
}
static String toRenamedClassifier(
DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
// E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
assert convertedType != null;
return descriptorToKotlinClassifier(convertedType.toDescriptorString());
}
// E.g., [Ljava/lang/String; -> kotlin/Array
if (type.isArrayType()) {
return NAME + "/Array";
}
// For library or classpath class, synthesize @Metadata always.
// For a program class, make sure it is live.
if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
return null;
}
DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
// For library or classpath class, we should not have renamed it.
DexClass clazz = appView.definitionFor(type);
assert clazz == null || clazz.isProgramClass() || renamedType == type
: type.toSourceString() + " -> " + renamedType.toSourceString();
return descriptorToKotlinClassifier(renamedType.toDescriptorString());
}
static KmType toRenamedKmType(
DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
String classifier = toRenamedClassifier(type, appView, lens);
if (classifier == null) {
return null;
}
// TODO(b/70169921): Mysterious, why attempts to properly set flags bothers kotlinc?
// and/or why wiping out flags works for KmType but not KmFunction?!
KmType kmType = new KmType(flagsOf());
kmType.visitClass(classifier);
// TODO(b/70169921): Need to set arguments as type parameter.
// E.g., for kotlin/Function1<P1, R>, P1 and R are recorded inside KmType.arguments, which
// enables kotlinc to resolve `this` type and return type of the lambda.
return kmType;
}
static KmConstructor toRenamedKmConstructor(
DexEncodedMethod method,
AppView<AppInfoWithLiveness> appView,
NamingLens lens) {
// Make sure it is an instance initializer and live.
if (!method.isInstanceInitializer()
|| !appView.appInfo().liveMethods.contains(method.method)) {
return null;
}
KmConstructor kmConstructor = new KmConstructor(method.accessFlags.getAsKotlinFlags());
JvmExtensionsKt.setSignature(kmConstructor, toJvmMethodSignature(method.method));
List<KmValueParameter> parameters = kmConstructor.getValueParameters();
if (!populateKmValueParameters(parameters, method, appView, lens)) {
return null;
}
return kmConstructor;
}
static KmFunction toRenamedKmFunction(
DexEncodedMethod method,
AppView<AppInfoWithLiveness> appView,
NamingLens lens) {
// For library overrides, synthesize @Metadata always.
// For regular methods, make sure it is live or pinned.
if (!method.isLibraryMethodOverride().isTrue()
&& !appView.appInfo().isPinned(method.method)
&& !appView.appInfo().liveMethods.contains(method.method)) {
return null;
}
DexMethod renamedMethod = lens.lookupMethod(method.method, appView.dexItemFactory());
// For a library method override, we should not have renamed it.
assert !method.isLibraryMethodOverride().isTrue() || renamedMethod.name == method.method.name
: method.toSourceString() + " -> " + renamedMethod.toSourceString();
// TODO(b/70169921): Should we keep kotlin-specific flags only while synthesizing the base
// value from general JVM flags?
int flag =
appView.appInfo().isPinned(method.method) && method.getKotlinMemberInfo() != null
? method.getKotlinMemberInfo().flag
: method.accessFlags.getAsKotlinFlags();
KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString());
JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(method.method));
KmType kmReturnType = toRenamedKmType(method.method.proto.returnType, appView, lens);
if (kmReturnType == null) {
return null;
}
kmFunction.setReturnType(kmReturnType);
if (method.isKotlinExtensionFunction()) {
assert method.method.proto.parameters.values.length > 0
: method.method.toSourceString();
KmType kmReceiverType =
toRenamedKmType(method.method.proto.parameters.values[0], appView, lens);
if (kmReceiverType == null) {
return null;
}
kmFunction.setReceiverParameterType(kmReceiverType);
}
List<KmValueParameter> parameters = kmFunction.getValueParameters();
if (!populateKmValueParameters(parameters, method, appView, lens)) {
return null;
}
return kmFunction;
}
private static boolean populateKmValueParameters(
List<KmValueParameter> parameters,
DexEncodedMethod method,
AppView<AppInfoWithLiveness> appView,
NamingLens lens) {
boolean isExtension = method.isKotlinExtensionFunction();
for (int i = isExtension ? 1 : 0; i < method.method.proto.parameters.values.length; i++) {
DexType parameterType = method.method.proto.parameters.values[i];
DebugLocalInfo debugLocalInfo = method.getParameterInfo().get(i);
String parameterName = debugLocalInfo != null ? debugLocalInfo.name.toString() : ("p" + i);
KotlinValueParameterInfo valueParameterInfo =
method.getKotlinMemberInfo().getValueParameterInfo(isExtension ? i - 1 : i);
KmValueParameter kmValueParameter =
toRewrittenKmValueParameter(
valueParameterInfo, parameterType, parameterName, appView, lens);
if (kmValueParameter == null) {
return false;
}
parameters.add(kmValueParameter);
}
return true;
}
private static KmValueParameter toRewrittenKmValueParameter(
KotlinValueParameterInfo valueParameterInfo,
DexType parameterType,
String candidateParameterName,
AppView<AppInfoWithLiveness> appView,
NamingLens lens) {
KmType kmParamType = toRenamedKmType(parameterType, appView, lens);
if (kmParamType == null) {
return null;
}
int flag = valueParameterInfo != null ? valueParameterInfo.flag : flagsOf();
String name = valueParameterInfo != null ? valueParameterInfo.name : candidateParameterName;
KmValueParameter kmValueParameter = new KmValueParameter(flag, name);
kmValueParameter.setType(kmParamType);
if (valueParameterInfo != null && valueParameterInfo.isVararg) {
if (!parameterType.isArrayType()) {
return null;
}
DexType elementType = parameterType.toBaseType(appView.dexItemFactory());
KmType kmElementType = toRenamedKmType(elementType, appView, lens);
if (kmElementType == null) {
return null;
}
kmValueParameter.setVarargElementType(kmElementType);
}
return kmValueParameter;
}
/**
* A group of a field, and methods that correspond to a Kotlin property.
*
* <p>
* va(l|r) prop: T
*
* is converted to a backing field with signature `T prop` if necessary. If the property is a pure
* computation, e.g., return `this` or access to the status of other properties, a field is not
* needed for the property.
* <p>
* Depending on property visibility, getter is defined with signature `T getProp()` (or `isProp`
* for boolean property).
* <p>
* If it's editable, i.e., declared as `var`, setter is defined with signature `void setProp(T)`.
* <p>
* Yet another addition, `void prop$annotations()`, seems(?) to be used to store associated
* annotations.
*
* <p>
* Currently, it's unclear how users can selectively keep each. Assuming every piece is kept by
* more general rules, such as keeping non-private members, we expect to find those as a whole.
* When rewriting a corresponding property, each information is used to update corresponding part,
* e.g., field to update the return type of the property, getter to update jvmMethodSignature of
* getter, and so on.
*/
static class KmPropertyGroup {
final int flag;
final String name;
final DexEncodedField field;
final DexEncodedMethod getter;
final DexEncodedMethod setter;
final DexEncodedMethod annotations;
final boolean isExtension;
private KmPropertyGroup(
int flag,
String name,
DexEncodedField field,
DexEncodedMethod getter,
DexEncodedMethod setter,
DexEncodedMethod annotations,
boolean isExtension) {
this.flag = flag;
this.name = name;
this.field = field;
this.getter = getter;
this.setter = setter;
this.annotations = annotations;
this.isExtension = isExtension;
}
static Builder builder(int flag, String name) {
return new Builder(flag, name);
}
static class Builder {
private final int flag;
private final String name;
private DexEncodedField field;
private DexEncodedMethod getter;
private DexEncodedMethod setter;
private DexEncodedMethod annotations;
private boolean isExtensionGetter;
private boolean isExtensionSetter;
private boolean isExtensionAnnotations;
private Builder(int flag, String name) {
this.flag = flag;
this.name = name;
}
Builder foundBackingField(DexEncodedField field) {
this.field = field;
return this;
}
Builder foundGetter(DexEncodedMethod getter) {
this.getter = getter;
return this;
}
Builder foundSetter(DexEncodedMethod setter) {
this.setter = setter;
return this;
}
Builder foundAnnotations(DexEncodedMethod annotations) {
this.annotations = annotations;
return this;
}
Builder isExtensionGetter() {
this.isExtensionGetter = true;
return this;
}
Builder isExtensionSetter() {
this.isExtensionSetter = true;
return this;
}
Builder isExtensionAnnotations() {
this.isExtensionAnnotations = true;
return this;
}
KmPropertyGroup build() {
boolean isExtension = isExtensionGetter || isExtensionSetter || isExtensionAnnotations;
// If this is an extension property, everything should be an extension.
if (isExtension) {
if (getter != null && !isExtensionGetter) {
return null;
}
if (setter != null && !isExtensionSetter) {
return null;
}
if (annotations != null && !isExtensionAnnotations) {
return null;
}
}
return new KmPropertyGroup(flag, name, field, getter, setter, annotations, isExtension);
}
}
KmProperty toRenamedKmProperty(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
KmProperty kmProperty = new KmProperty(flag, name, flagsOf(), flagsOf());
KmType kmPropertyType = null;
KmType kmReceiverType = null;
// A flag to indicate we can rename the property name. This will become false if any member
// is pinned. Then, we conservatively assume that users want the property to be pinned too.
// That is, we won't rename the property even though some other members could be renamed.
boolean canChangePropertyName = true;
// A candidate property name. Not overwritten by the following members, hence the order of
// preference: a backing field, getter, and setter.
String renamedPropertyName = name;
if (field != null) {
if (appView.appInfo().isPinned(field.field)) {
canChangePropertyName = false;
}
DexField renamedField = lens.lookupField(field.field, appView.dexItemFactory());
if (canChangePropertyName && renamedField.name != field.field.name) {
renamedPropertyName = renamedField.name.toString();
}
kmPropertyType = toRenamedKmType(field.field.type, appView, lens);
if (kmPropertyType != null) {
kmProperty.setReturnType(kmPropertyType);
}
JvmExtensionsKt.setFieldSignature(kmProperty, toJvmFieldSignature(renamedField));
}
GetterSetterCriteria criteria = checkGetterCriteria();
if (criteria == GetterSetterCriteria.VIOLATE) {
return null;
}
if (criteria == GetterSetterCriteria.MET) {
assert getter != null
: "checkGetterCriteria: " + this.toString();
if (isExtension) {
assert getter.method.proto.parameters.size() == 1
: "checkGetterCriteria: " + this.toString();
kmReceiverType = toRenamedKmType(getter.method.proto.parameters.values[0], appView, lens);
if (kmReceiverType != null) {
kmProperty.setReceiverParameterType(kmReceiverType);
}
}
if (kmPropertyType == null) {
// The property type is not set yet.
kmPropertyType = toRenamedKmType(getter.method.proto.returnType, appView, lens);
if (kmPropertyType != null) {
kmProperty.setReturnType(kmPropertyType);
}
} else {
// If property type is set already (via backing field), make sure it's consistent.
KmType kmPropertyTypeFromGetter =
toRenamedKmType(getter.method.proto.returnType, appView, lens);
if (!getDescriptorFromKmType(kmPropertyType)
.equals(getDescriptorFromKmType(kmPropertyTypeFromGetter))) {
return null;
}
}
if (appView.appInfo().isPinned(getter.method)) {
canChangePropertyName = false;
}
DexMethod renamedGetter = lens.lookupMethod(getter.method, appView.dexItemFactory());
if (canChangePropertyName
&& renamedGetter.name != getter.method.name
&& renamedPropertyName.equals(name)) {
renamedPropertyName = renamedGetter.name.toString();
}
kmProperty.setGetterFlags(getter.accessFlags.getAsKotlinFlags());
JvmExtensionsKt.setGetterSignature(kmProperty, toJvmMethodSignature(renamedGetter));
}
criteria = checkSetterCriteria();
if (criteria == GetterSetterCriteria.VIOLATE) {
return null;
}
if (criteria == GetterSetterCriteria.MET) {
assert setter != null && setter.method.proto.parameters.size() >= 1
: "checkSetterCriteria: " + this.toString();
if (isExtension) {
assert setter.method.proto.parameters.size() == 2
: "checkSetterCriteria: " + this.toString();
if (kmReceiverType == null) {
kmReceiverType =
toRenamedKmType(setter.method.proto.parameters.values[0], appView, lens);
if (kmReceiverType != null) {
kmProperty.setReceiverParameterType(kmReceiverType);
}
} else {
// If the receiver type for the extension property is set already (via getter),
// make sure it's consistent.
KmType kmReceiverTypeFromSetter =
toRenamedKmType(setter.method.proto.parameters.values[0], appView, lens);
if (!getDescriptorFromKmType(kmReceiverType)
.equals(getDescriptorFromKmType(kmReceiverTypeFromSetter))) {
return null;
}
}
}
int valueIndex = isExtension ? 1 : 0;
DexType valueType = setter.method.proto.parameters.values[valueIndex];
if (kmPropertyType == null) {
// The property type is not set yet.
kmPropertyType = toRenamedKmType(valueType, appView, lens);
if (kmPropertyType != null) {
kmProperty.setReturnType(kmPropertyType);
}
} else {
// If property type is set already (via either backing field or getter),
// make sure it's consistent.
KmType kmPropertyTypeFromSetter = toRenamedKmType(valueType, appView, lens);
if (!getDescriptorFromKmType(kmPropertyType)
.equals(getDescriptorFromKmType(kmPropertyTypeFromSetter))) {
return null;
}
}
KotlinValueParameterInfo valueParameterInfo =
setter.getKotlinMemberInfo().getValueParameterInfo(valueIndex);
KmValueParameter kmValueParameter =
toRewrittenKmValueParameter(valueParameterInfo, valueType, "value", appView, lens);
if (kmValueParameter != null) {
kmProperty.setSetterParameter(kmValueParameter);
}
if (appView.appInfo().isPinned(setter.method)) {
canChangePropertyName = false;
}
DexMethod renamedSetter = lens.lookupMethod(setter.method, appView.dexItemFactory());
if (canChangePropertyName
&& renamedSetter.name != setter.method.name
&& renamedPropertyName.equals(name)) {
renamedPropertyName = renamedSetter.name.toString();
}
kmProperty.setSetterFlags(setter.accessFlags.getAsKotlinFlags());
JvmExtensionsKt.setSetterSignature(kmProperty, toJvmMethodSignature(renamedSetter));
}
// If the property type remains null at the end, bail out to synthesize this property.
if (kmPropertyType == null) {
return null;
}
// For extension property, if the receiver type remains null at the end, bail out too.
if (isExtension && kmReceiverType == null) {
return null;
}
// Rename the property name if and only if none of participating members is pinned, and
// any of them is indeed renamed (to a new name).
if (canChangePropertyName && !renamedPropertyName.equals(name)) {
kmProperty.setName(renamedPropertyName);
}
return kmProperty;
}
enum GetterSetterCriteria {
NOT_AVAILABLE,
MET,
VIOLATE
}
// Getter should look like:
// 1) T getProp(); for regular property, or
// 2) T getProp(R); for extension property, where
// T is a property type, and R is a receiver type.
private GetterSetterCriteria checkGetterCriteria() {
if (getter == null) {
return GetterSetterCriteria.NOT_AVAILABLE;
}
// Property type will be checked against a backing field type if any.
// And if they're different, we won't synthesize KmProperty for that.
// Checking parameter size.
if (isExtension) {
return getter.method.proto.parameters.size() == 1
? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
} else {
return getter.method.proto.parameters.size() == 0
? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
}
}
// Setter should look like:
// 1) void setProp(T); for regular property, or
// 2) void setProp(R, T); for extension property, where
// T is a property type, and R is a receiver type.
private GetterSetterCriteria checkSetterCriteria() {
if (setter == null) {
return GetterSetterCriteria.NOT_AVAILABLE;
}
if (!setter.method.proto.returnType.isVoidType()) {
return GetterSetterCriteria.VIOLATE;
}
// Property type will be checked against a backing field type if any.
// And if they're different, we won't synthesize KmProperty for that.
// Plus, receiver type will be checked, too, against a getter.
if (isExtension) {
return setter.method.proto.parameters.size() == 2
? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
} else {
return setter.method.proto.parameters.size() == 1
? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("KmPropertyGroup {").append(System.lineSeparator());
builder.append(" name: ").append(name).append(System.lineSeparator());
builder.append(" isExtension: ").append(isExtension).append(System.lineSeparator());
if (field != null) {
builder.append(" field: ")
.append(field.toSourceString())
.append(System.lineSeparator());
}
if (getter != null) {
builder
.append(" getter: ")
.append(getter.toSourceString())
.append(System.lineSeparator());
}
if (setter != null) {
builder
.append(" setter: ")
.append(setter.toSourceString())
.append(System.lineSeparator());
}
if (annotations != null) {
builder
.append(" annotations: ")
.append(annotations.toSourceString())
.append(System.lineSeparator());
}
builder.append("}");
return builder.toString();
}
}
}