blob: cc2634a39e0a0b2245079fdc213fb1af401652ee [file] [log] [blame]
// 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 static com.android.tools.r8.kotlin.KotlinMetadataUtils.getCompatibleKotlinInfo;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteList;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmFieldSignature;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmMethodSignature;
import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
import static kotlinx.metadata.jvm.KotlinClassMetadata.Companion;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.Reporter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import kotlin.Metadata;
import kotlinx.metadata.KmClass;
import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmType;
import kotlinx.metadata.jvm.JvmExtensionsKt;
import kotlinx.metadata.jvm.JvmMethodSignature;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public class KotlinClassInfo implements KotlinClassLevelInfo {
private final int flags;
private final String name;
private final boolean nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin;
private final String moduleName;
private final List<KotlinConstructorInfo> constructorsWithNoBacking;
private final KotlinDeclarationContainerInfo declarationContainerInfo;
private final List<KotlinTypeParameterInfo> typeParameters;
private final List<KotlinTypeInfo> superTypes;
private final List<KotlinTypeReference> sealedSubClasses;
private final List<KotlinTypeReference> nestedClasses;
private final List<String> enumEntries;
private final KotlinVersionRequirementInfo versionRequirements;
private final KotlinTypeReference anonymousObjectOrigin;
private final String packageName;
private final KotlinLocalDelegatedPropertyInfo localDelegatedProperties;
private final int[] metadataVersion;
private final String inlineClassUnderlyingPropertyName;
private final KotlinTypeInfo inlineClassUnderlyingType;
private final int jvmFlags;
private final String companionObjectName;
// Collection of context receiver types
private final List<KotlinTypeInfo> contextReceiverTypes;
// List of tracked assignments of kotlin metadata.
private final KotlinMetadataMembersTracker originalMembersWithKotlinInfo;
private KotlinClassInfo(
int flags,
String name,
boolean nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin,
String moduleName,
KotlinDeclarationContainerInfo declarationContainerInfo,
List<KotlinTypeParameterInfo> typeParameters,
List<KotlinConstructorInfo> constructorsWithNoBacking,
List<KotlinTypeInfo> superTypes,
List<KotlinTypeReference> sealedSubClasses,
List<KotlinTypeReference> nestedClasses,
List<String> enumEntries,
KotlinVersionRequirementInfo versionRequirements,
KotlinTypeReference anonymousObjectOrigin,
String packageName,
KotlinLocalDelegatedPropertyInfo localDelegatedProperties,
int[] metadataVersion,
String inlineClassUnderlyingPropertyName,
KotlinTypeInfo inlineClassUnderlyingType,
KotlinMetadataMembersTracker originalMembersWithKotlinInfo,
int jvmFlags,
String companionObjectName,
List<KotlinTypeInfo> contextReceiverTypes) {
this.flags = flags;
this.name = name;
this.nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin =
nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin;
this.moduleName = moduleName;
this.declarationContainerInfo = declarationContainerInfo;
this.typeParameters = typeParameters;
this.constructorsWithNoBacking = constructorsWithNoBacking;
this.superTypes = superTypes;
this.sealedSubClasses = sealedSubClasses;
this.nestedClasses = nestedClasses;
this.enumEntries = enumEntries;
this.versionRequirements = versionRequirements;
this.anonymousObjectOrigin = anonymousObjectOrigin;
this.packageName = packageName;
this.localDelegatedProperties = localDelegatedProperties;
this.metadataVersion = metadataVersion;
this.inlineClassUnderlyingPropertyName = inlineClassUnderlyingPropertyName;
this.inlineClassUnderlyingType = inlineClassUnderlyingType;
this.originalMembersWithKotlinInfo = originalMembersWithKotlinInfo;
this.jvmFlags = jvmFlags;
this.companionObjectName = companionObjectName;
this.contextReceiverTypes = contextReceiverTypes;
}
public static KotlinClassInfo create(
KotlinClassMetadata.Class metadata,
String packageName,
int[] metadataVersion,
DexClass hostClass,
AppView<?> appView,
Consumer<DexEncodedMethod> keepByteCode) {
DexItemFactory factory = appView.dexItemFactory();
Reporter reporter = appView.reporter();
KmClass kmClass = metadata.getKmClass();
Map<String, DexEncodedField> fieldMap = new HashMap<>();
for (DexEncodedField field : hostClass.fields()) {
fieldMap.put(toJvmFieldSignature(field.getReference()).asString(), field);
}
Map<String, DexEncodedMethod> methodMap = new HashMap<>();
for (DexEncodedMethod method : hostClass.methods()) {
methodMap.put(toJvmMethodSignature(method.getReference()).asString(), method);
}
ImmutableList.Builder<KotlinConstructorInfo> notBackedConstructors = ImmutableList.builder();
KotlinMetadataMembersTracker originalMembersWithKotlinInfo =
new KotlinMetadataMembersTracker(appView);
for (KmConstructor kmConstructor : kmClass.getConstructors()) {
KotlinConstructorInfo constructorInfo =
KotlinConstructorInfo.create(kmConstructor, factory, reporter);
JvmMethodSignature signature = JvmExtensionsKt.getSignature(kmConstructor);
if (signature != null) {
DexEncodedMethod method = methodMap.get(signature.asString());
if (method != null) {
method.setKotlinMemberInfo(constructorInfo);
originalMembersWithKotlinInfo.add(method.getReference());
continue;
}
}
// We could not find a definition for the constructor - add it to ensure the same output.
notBackedConstructors.add(constructorInfo);
}
KotlinDeclarationContainerInfo container =
KotlinDeclarationContainerInfo.create(
kmClass,
methodMap,
fieldMap,
factory,
reporter,
keepByteCode,
originalMembersWithKotlinInfo);
KotlinTypeReference anonymousObjectOrigin = getAnonymousObjectOrigin(kmClass, factory);
boolean nameCanBeDeducedFromClassOrOrigin =
kmClass.name.equals(
KotlinMetadataUtils.getKotlinClassName(
hostClass, hostClass.getType().toDescriptorString()))
|| (anonymousObjectOrigin != null
&& kmClass.name.equals(anonymousObjectOrigin.toKotlinClassifier(true)));
return new KotlinClassInfo(
kmClass.getFlags(),
kmClass.name,
nameCanBeDeducedFromClassOrOrigin,
JvmExtensionsKt.getModuleName(kmClass),
container,
KotlinTypeParameterInfo.create(kmClass.getTypeParameters(), factory, reporter),
notBackedConstructors.build(),
getSuperTypes(kmClass.getSupertypes(), factory, reporter),
getSealedSubClasses(kmClass.getSealedSubclasses(), factory),
getNestedClasses(hostClass, kmClass.getNestedClasses(), factory),
setEnumEntries(kmClass, hostClass),
KotlinVersionRequirementInfo.create(kmClass.getVersionRequirements()),
anonymousObjectOrigin,
packageName,
KotlinLocalDelegatedPropertyInfo.create(
JvmExtensionsKt.getLocalDelegatedProperties(kmClass), factory, reporter),
metadataVersion,
kmClass.getInlineClassUnderlyingPropertyName(),
KotlinTypeInfo.create(kmClass.getInlineClassUnderlyingType(), factory, reporter),
originalMembersWithKotlinInfo,
JvmExtensionsKt.getJvmFlags(kmClass),
setCompanionObject(kmClass, hostClass, reporter),
ListUtils.map(
kmClass.getContextReceiverTypes(),
contextRecieverType -> KotlinTypeInfo.create(contextRecieverType, factory, reporter)));
}
private static KotlinTypeReference getAnonymousObjectOrigin(
KmClass kmClass, DexItemFactory factory) {
String anonymousObjectOriginName = JvmExtensionsKt.getAnonymousObjectOriginName(kmClass);
if (anonymousObjectOriginName != null) {
return KotlinTypeReference.fromBinaryNameOrKotlinClassifier(
anonymousObjectOriginName, factory, anonymousObjectOriginName);
}
return null;
}
private static List<KotlinTypeReference> getNestedClasses(
DexClass clazz, List<String> nestedClasses, DexItemFactory factory) {
ImmutableList.Builder<KotlinTypeReference> nestedTypes = ImmutableList.builder();
for (String nestedClass : nestedClasses) {
String binaryName =
clazz.type.toBinaryName() + DescriptorUtils.INNER_CLASS_SEPARATOR + nestedClass;
nestedTypes.add(
KotlinTypeReference.fromBinaryNameOrKotlinClassifier(binaryName, factory, nestedClass));
}
return nestedTypes.build();
}
private static List<KotlinTypeReference> getSealedSubClasses(
List<String> sealedSubClasses, DexItemFactory factory) {
ImmutableList.Builder<KotlinTypeReference> sealedTypes = ImmutableList.builder();
for (String sealedSubClass : sealedSubClasses) {
String binaryName =
sealedSubClass.replace(
DescriptorUtils.JAVA_PACKAGE_SEPARATOR, DescriptorUtils.INNER_CLASS_SEPARATOR);
sealedTypes.add(
KotlinTypeReference.fromBinaryNameOrKotlinClassifier(
binaryName, factory, sealedSubClass));
}
return sealedTypes.build();
}
private static List<KotlinTypeInfo> getSuperTypes(
List<KmType> superTypes, DexItemFactory factory, Reporter reporter) {
ImmutableList.Builder<KotlinTypeInfo> superTypeInfos = ImmutableList.builder();
for (KmType superType : superTypes) {
superTypeInfos.add(KotlinTypeInfo.create(superType, factory, reporter));
}
return superTypeInfos.build();
}
private static String setCompanionObject(KmClass kmClass, DexClass hostClass, Reporter reporter) {
String companionObjectName = kmClass.getCompanionObject();
if (companionObjectName == null) {
return companionObjectName;
}
for (DexEncodedField field : hostClass.fields()) {
if (field.getReference().name.toString().equals(companionObjectName)) {
field.setKotlinMemberInfo(new KotlinCompanionInfo(companionObjectName));
return companionObjectName;
}
}
reporter.warning(
KotlinMetadataDiagnostic.missingCompanionObject(hostClass, companionObjectName));
return companionObjectName;
}
private static List<String> setEnumEntries(KmClass kmClass, DexClass hostClass) {
List<String> enumEntries = kmClass.getEnumEntries();
if (enumEntries.isEmpty()) {
return enumEntries;
}
Collection<String> enumEntryStrings =
enumEntries.size() < 16 ? enumEntries : Sets.newHashSet(enumEntries);
hostClass
.fields()
.forEach(
field -> {
String fieldName = field.getName().toString();
if (enumEntryStrings.contains(fieldName)) {
field.setKotlinMemberInfo(new KotlinEnumEntryInfo(fieldName));
}
});
return enumEntries;
}
@Override
public boolean isClass() {
return true;
}
@Override
@SuppressWarnings("ReferenceEquality")
public KotlinClassInfo asClass() {
return this;
}
@Override
@SuppressWarnings("ReferenceEquality")
public Pair<Metadata, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
KmClass kmClass = new KmClass();
// TODO(b/154348683): Set flags.
kmClass.setFlags(flags);
// Set potentially renamed class name.
DexString originalDescriptor = clazz.type.descriptor;
DexString rewrittenDescriptor = appView.getNamingLens().lookupDescriptor(clazz.type);
boolean rewritten = !originalDescriptor.equals(rewrittenDescriptor);
if (!nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin) {
kmClass.setName(this.name);
} else {
String rewrittenName = null;
// When the class has an anonymousObjectOrigin and the name equals the identifier there, we
// keep the name tied to the anonymousObjectOrigin.
if (anonymousObjectOrigin != null
&& name.equals(anonymousObjectOrigin.toKotlinClassifier(true))) {
Box<String> rewrittenOrigin = new Box<>();
anonymousObjectOrigin.toRenamedBinaryNameOrDefault(rewrittenOrigin::set, appView, null);
if (rewrittenOrigin.isSet()) {
rewrittenName = "." + rewrittenOrigin.get();
}
}
if (rewrittenName == null) {
rewrittenName =
KotlinMetadataUtils.getKotlinClassName(clazz, rewrittenDescriptor.toString());
}
kmClass.setName(rewrittenName);
rewritten |= !name.equals(rewrittenName);
}
// Find a companion object.
boolean foundCompanion = false;
int numberOfEnumEntries = 0;
for (DexEncodedField field : clazz.fields()) {
KotlinFieldLevelInfo kotlinInfo = field.getKotlinInfo();
if (kotlinInfo.isCompanion()) {
rewritten |=
kotlinInfo
.asCompanion()
.rewrite(kmClass, field.getReference(), appView.getNamingLens());
foundCompanion = true;
} else if (kotlinInfo.isEnumEntry()) {
KotlinEnumEntryInfo kotlinEnumEntryInfo = kotlinInfo.asEnumEntry();
rewritten |=
kotlinEnumEntryInfo.rewrite(kmClass, field.getReference(), appView.getNamingLens());
if (numberOfEnumEntries >= enumEntries.size()
|| !enumEntries.get(numberOfEnumEntries).equals(kotlinEnumEntryInfo.getEnumEntry())) {
rewritten = true;
}
numberOfEnumEntries += 1;
}
}
// If we did not find a companion but it was there on input we have to emit a new metadata
// object.
if (!foundCompanion && companionObjectName != null) {
rewritten = true;
}
// If we could remove enum entries but were unable to rename them we still have to emit a new
// metadata object.
if (numberOfEnumEntries < enumEntries.size()) {
rewritten = true;
}
// Take all not backed constructors because we will never find them in definitions.
for (KotlinConstructorInfo constructorInfo : constructorsWithNoBacking) {
rewritten |= constructorInfo.rewrite(kmClass, null, appView);
}
// Find all constructors.
KotlinMetadataMembersTracker rewrittenReferences = new KotlinMetadataMembersTracker(appView);
for (DexEncodedMethod method : clazz.methods()) {
if (method.getKotlinInfo().isConstructor()) {
KotlinConstructorInfo constructorInfo = method.getKotlinInfo().asConstructor();
rewritten |= constructorInfo.rewrite(kmClass, method, appView);
rewrittenReferences.add(method.getReference());
}
}
// Rewrite functions, type-aliases and type-parameters.
rewritten |=
declarationContainerInfo.rewrite(
kmClass.getFunctions()::add,
kmClass.getProperties()::add,
kmClass.getTypeAliases()::add,
clazz,
appView,
rewrittenReferences);
// Rewrite type parameters.
rewritten |=
rewriteList(
appView, typeParameters, kmClass.getTypeParameters(), KotlinTypeParameterInfo::rewrite);
// Rewrite super types.
List<KmType> rewrittenSuperTypes = kmClass.getSupertypes();
for (KotlinTypeInfo superType : superTypes) {
// Ensure the rewritten super type is not this type.
DexType rewrittenSuperType =
superType.rewriteType(appView.graphLens(), appView.getKotlinMetadataLens());
if (clazz.getType() != rewrittenSuperType) {
rewritten |= superType.rewrite(rewrittenSuperTypes::add, appView);
} else {
rewritten = true;
}
}
// Rewrite nested classes.
List<String> rewrittenNestedClasses = kmClass.getNestedClasses();
for (KotlinTypeReference nestedClass : this.nestedClasses) {
Box<String> nestedDescriptorBox = new Box<>();
boolean nestedClassRewritten =
nestedClass.toRenamedBinaryNameOrDefault(nestedDescriptorBox::set, appView, null);
if (nestedDescriptorBox.isSet()) {
if (nestedClassRewritten) {
// If the class is a nested class, it should be on the form Foo.Bar$Baz, where Baz
// is the name we should record.
String nestedDescriptor = nestedDescriptorBox.get();
int innerClassIndex = nestedDescriptor.lastIndexOf(DescriptorUtils.INNER_CLASS_SEPARATOR);
rewrittenNestedClasses.add(nestedDescriptor.substring(innerClassIndex + 1));
} else {
rewrittenNestedClasses.add(nestedClass.getOriginalName());
}
}
rewritten |= nestedClassRewritten;
}
// Rewrite sealed sub-classes.
List<String> rewrittenSealedClasses = kmClass.getSealedSubclasses();
for (KotlinTypeReference sealedSubClass : sealedSubClasses) {
rewritten |=
sealedSubClass.toRenamedBinaryNameOrDefault(
sealedName -> {
if (sealedName != null) {
rewrittenSealedClasses.add(
sealedName.replace(
DescriptorUtils.INNER_CLASS_SEPARATOR,
DescriptorUtils.JAVA_PACKAGE_SEPARATOR));
}
},
appView,
null);
}
rewritten |= versionRequirements.rewrite(kmClass.getVersionRequirements()::addAll);
if (inlineClassUnderlyingPropertyName != null && inlineClassUnderlyingType != null) {
kmClass.setInlineClassUnderlyingPropertyName(inlineClassUnderlyingPropertyName);
rewritten |=
inlineClassUnderlyingType.rewrite(kmClass::setInlineClassUnderlyingType, appView);
}
rewritten |=
rewriteList(
appView,
contextReceiverTypes,
kmClass.getContextReceiverTypes(),
KotlinTypeInfo::rewrite);
JvmExtensionsKt.setJvmFlags(kmClass, jvmFlags);
JvmExtensionsKt.setModuleName(kmClass, moduleName);
if (anonymousObjectOrigin != null) {
rewritten |=
anonymousObjectOrigin.toRenamedBinaryNameOrDefault(
renamedAnon -> {
if (renamedAnon != null) {
JvmExtensionsKt.setAnonymousObjectOriginName(kmClass, renamedAnon);
}
},
appView,
null);
}
rewritten |=
localDelegatedProperties.rewrite(
JvmExtensionsKt.getLocalDelegatedProperties(kmClass)::add, appView);
return Pair.create(
Companion.writeClass(kmClass, getCompatibleKotlinInfo(), 0),
rewritten || !originalMembersWithKotlinInfo.isEqual(rewrittenReferences, appView));
}
@Override
public String getPackageName() {
return packageName;
}
@Override
public int[] getMetadataVersion() {
return metadataVersion;
}
@Override
public void trace(DexDefinitionSupplier definitionSupplier) {
forEachApply(constructorsWithNoBacking, constructor -> constructor::trace, definitionSupplier);
declarationContainerInfo.trace(definitionSupplier);
forEachApply(typeParameters, param -> param::trace, definitionSupplier);
forEachApply(superTypes, type -> type::trace, definitionSupplier);
forEachApply(sealedSubClasses, sealedClass -> sealedClass::trace, definitionSupplier);
forEachApply(nestedClasses, nested -> nested::trace, definitionSupplier);
forEachApply(contextReceiverTypes, nested -> nested::trace, definitionSupplier);
localDelegatedProperties.trace(definitionSupplier);
// TODO(b/154347404): trace enum entries.
if (anonymousObjectOrigin != null) {
anonymousObjectOrigin.trace(definitionSupplier);
}
}
}