| // 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); |
| } |
| } |
| } |