package com.github.codeql import com.github.codeql.utils.* import com.github.codeql.utils.versions.* import com.semmle.extractor.java.OdasaOutput import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.ir.* import org.jetbrains.kotlin.backend.jvm.ir.* import org.jetbrains.kotlin.codegen.JvmCodegenUtil import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.types.addAnnotations import org.jetbrains.kotlin.ir.types.classFqName import org.jetbrains.kotlin.ir.types.classifierOrNull import org.jetbrains.kotlin.ir.types.classOrNull import org.jetbrains.kotlin.ir.types.isAny import org.jetbrains.kotlin.ir.types.isNullableAny import org.jetbrains.kotlin.ir.types.isPrimitiveType import org.jetbrains.kotlin.ir.types.makeNullable import org.jetbrains.kotlin.ir.types.typeOrNull import org.jetbrains.kotlin.ir.types.typeWith import org.jetbrains.kotlin.ir.types.typeWithArguments import org.jetbrains.kotlin.ir.types.IrDynamicType import org.jetbrains.kotlin.ir.types.IrErrorType import org.jetbrains.kotlin.ir.types.IrSimpleType import org.jetbrains.kotlin.ir.types.IrStarProjection import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.IrTypeArgument import org.jetbrains.kotlin.ir.types.IrTypeProjection import org.jetbrains.kotlin.ir.types.impl.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.sources.JavaSourceElement import org.jetbrains.kotlin.load.java.structure.* import org.jetbrains.kotlin.load.java.typeEnhancement.hasEnhancedNullability import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.NameUtils import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlin.resolve.descriptorUtil.propertyIfAccessor import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.util.OperatorNameConventions open class KotlinUsesExtractor( open val logger: Logger, open val tw: TrapWriter, val dependencyCollector: OdasaOutput.TrapFileManager?, val externalClassExtractor: ExternalDeclExtractor, val classInstanceStack: ClassInstanceStack, val primitiveTypeMapping: PrimitiveTypeMapping, val pluginContext: IrPluginContext, val globalExtensionState: KotlinExtractorGlobalState ) { fun referenceExternalClass(name: String) = getClassByFqName(pluginContext, FqName(name))?.owner.also { if (it == null) logger.warn("Unable to resolve external class $name") else extractExternalClassLater(it) } val javaLangObject by lazy { referenceExternalClass("java.lang.Object") } val javaLangObjectType by lazy { javaLangObject?.typeWith() } private fun usePackage(pkg: String): Label { return extractPackage(pkg) } fun extractPackage(pkg: String): Label { val pkgLabel = "@\"package;$pkg\"" val id: Label = tw.getLabelFor(pkgLabel, { tw.writePackages(it, pkg) }) return id } fun useFileClassType(f: IrFile) = TypeResults(TypeResult(extractFileClass(f), "", ""), TypeResult(fakeKotlinType(), "", "")) fun useFileClassType(fqName: FqName) = TypeResults( TypeResult(extractFileClass(fqName), "", ""), TypeResult(fakeKotlinType(), "", "") ) private fun useFileClassType(pkg: String, jvmName: String) = TypeResults( TypeResult(extractFileClass(pkg, jvmName), "", ""), TypeResult(fakeKotlinType(), "", "") ) fun extractFileClass(f: IrFile): Label { val pkg = f.packageFqName.asString() val jvmName = getFileClassName(f) val id = extractFileClass(pkg, jvmName) if (tw.lm.fileClassLocationsExtracted.add(f)) { val fileId = tw.mkFileId(f.path, false) val locId = tw.getWholeFileLocation(fileId) tw.writeHasLocation(id, locId) } return id } private fun extractFileClass(fqName: FqName): Label { val pkg = if (fqName.codeQlIsRoot()) "" else fqName.parent().asString() val jvmName = fqName.shortName().asString() return extractFileClass(pkg, jvmName) } private fun extractFileClass(pkg: String, jvmName: String): Label { val qualClassName = if (pkg.isEmpty()) jvmName else "$pkg.$jvmName" val label = "@\"class;$qualClassName\"" val id: Label = tw.getLabelFor(label) { val pkgId = extractPackage(pkg) tw.writeClasses_or_interfaces(it, jvmName, pkgId, it) tw.writeFile_class(it) addModifiers(it, "public", "final") } return id } data class UseClassInstanceResult( val typeResult: TypeResult, val javaClass: IrClass ) fun useType(t: IrType, context: TypeContext = TypeContext.OTHER): TypeResults { when (t) { is IrSimpleType -> return useSimpleType(t, context) else -> { logger.error("Unrecognised IrType: " + t.javaClass) return extractErrorType() } } } private fun extractJavaErrorType(): TypeResult { val typeId = tw.getLabelFor("@\"errorType\"") { tw.writeError_type(it) } return TypeResult(typeId, "", "") } private fun extractErrorType(): TypeResults { val javaResult = extractJavaErrorType() val kotlinTypeId = tw.getLabelFor("@\"errorKotlinType\"") { tw.writeKt_nullable_types(it, javaResult.id) } return TypeResults( javaResult, TypeResult(kotlinTypeId, "", "") ) } fun getJavaEquivalentClass(c: IrClass) = getJavaEquivalentClassId(c)?.let { getClassByClassId(pluginContext, it) }?.owner /** * Gets a KotlinFileExtractor based on this one, except it attributes locations to the file that * declares the given class. */ private fun withFileOfClass(cls: IrClass): KotlinFileExtractor { val clsFile = cls.fileOrNull if (this is KotlinFileExtractor && this.filePath == clsFile?.path) { return this } val newDeclarationStack = if (this is KotlinFileExtractor) this.declarationStack else KotlinFileExtractor.DeclarationStack() if (clsFile == null || isExternalDeclaration(cls)) { val filePath = getIrClassBinaryPath(cls) val newTrapWriter = tw.makeFileTrapWriter(filePath, true) val newLoggerTrapWriter = logger.dtw.makeFileTrapWriter(filePath, false) val newLogger = FileLogger(logger.loggerBase, newLoggerTrapWriter) return KotlinFileExtractor( newLogger, newTrapWriter, null, filePath, dependencyCollector, externalClassExtractor, classInstanceStack, primitiveTypeMapping, pluginContext, newDeclarationStack, globalExtensionState ) } val newTrapWriter = tw.makeSourceFileTrapWriter(clsFile, true) val newLoggerTrapWriter = logger.dtw.makeSourceFileTrapWriter(clsFile, false) val newLogger = FileLogger(logger.loggerBase, newLoggerTrapWriter) return KotlinFileExtractor( newLogger, newTrapWriter, null, clsFile.path, dependencyCollector, externalClassExtractor, classInstanceStack, primitiveTypeMapping, pluginContext, newDeclarationStack, globalExtensionState ) } // The Kotlin compiler internal representation of Outer.Inner.InnerInner is // InnerInner. This function returns just `R`. fun removeOuterClassTypeArgs( c: IrClass, argsIncludingOuterClasses: List? ): List? { return argsIncludingOuterClasses?.let { if (it.size > c.typeParameters.size) it.take(c.typeParameters.size) else null } ?: argsIncludingOuterClasses } private fun isStaticClass(c: IrClass) = c.visibility != DescriptorVisibilities.LOCAL && !c.isInner // Gets nested inner classes starting at `c` and proceeding outwards to the innermost enclosing // static class. // For example, for (java syntax) `class A { static class B { class C { class D { } } } }`, // `nonStaticParentsWithSelf(D)` = `[D, C, B]`. private fun parentsWithTypeParametersInScope(c: IrClass): List { val parentsList = c.parentsWithSelf.toList() val firstOuterClassIdx = parentsList.indexOfFirst { it is IrClass && isStaticClass(it) } return if (firstOuterClassIdx == -1) parentsList else parentsList.subList(0, firstOuterClassIdx + 1) } // Gets the type parameter symbols that are in scope for class `c` in Kotlin order (i.e. for // `class NotInScope { static class OutermostInScope { class QueryClass { } } }`, // `getTypeParametersInScope(QueryClass)` = `[C, D, A, B]`. private fun getTypeParametersInScope(c: IrClass) = parentsWithTypeParametersInScope(c).mapNotNull({ getTypeParameters(it) }).flatten() // Returns a map from `c`'s type variables in scope to type arguments // `argsIncludingOuterClasses`. // Hack for the time being: the substituted types are always nullable, to prevent downstream // code // from replacing a generic parameter by a primitive. As and when we extract Kotlin types we // will // need to track this information in more detail. private fun makeTypeGenericSubstitutionMap( c: IrClass, argsIncludingOuterClasses: List ) = getTypeParametersInScope(c) .map({ it.symbol }) .zip(argsIncludingOuterClasses.map { it.withQuestionMark(true) }) .toMap() fun makeGenericSubstitutionFunction( c: IrClass, argsIncludingOuterClasses: List ) = makeTypeGenericSubstitutionMap(c, argsIncludingOuterClasses).let { { x: IrType, useContext: TypeContext, pluginContext: IrPluginContext -> x.substituteTypeAndArguments(it, useContext, pluginContext) } } // The Kotlin compiler internal representation of Outer.Inner.InnerInner.someFunction.LocalClass is LocalClass. This // function returns [A, B, C, D, E, F, G, H, I, J]. private fun orderTypeArgsLeftToRight( c: IrClass, argsIncludingOuterClasses: List? ): List? { if (argsIncludingOuterClasses.isNullOrEmpty()) return argsIncludingOuterClasses val ret = ArrayList() // Iterate over nested inner classes starting at `c`'s surrounding top-level or static // nested class and ending at `c`, from the outermost inwards: val truncatedParents = parentsWithTypeParametersInScope(c) for (parent in truncatedParents.reversed()) { val parentTypeParameters = getTypeParameters(parent) val firstArgIdx = argsIncludingOuterClasses.size - (ret.size + parentTypeParameters.size) ret.addAll( argsIncludingOuterClasses.subList( firstArgIdx, firstArgIdx + parentTypeParameters.size ) ) } return ret } // `typeArgs` can be null to describe a raw generic type. // For non-generic types it will be zero-length list. fun useClassInstance( c: IrClass, typeArgs: List?, inReceiverContext: Boolean = false ): UseClassInstanceResult { val substituteClass = getJavaEquivalentClass(c) val extractClass = substituteClass ?: c // `KFunction1` is substituted by `KFunction`. The last type argument is the // return type. // Similarly Function23 and above get replaced by kotlin.jvm.functions.FunctionN with only // one type arg, the result type. // References to SomeGeneric where SomeGeneric is declared SomeGeneric are extracted // as if they were references to the unbound type SomeGeneric. val extractedTypeArgs = when { extractClass.symbol.isKFunction() && typeArgs != null && typeArgs.isNotEmpty() -> listOf(typeArgs.last()) extractClass.fqNameWhenAvailable == FqName("kotlin.jvm.functions.FunctionN") && typeArgs != null && typeArgs.isNotEmpty() -> listOf(typeArgs.last()) typeArgs != null && isUnspecialised(c, typeArgs, logger) -> listOf() else -> typeArgs } val classTypeResult = addClassLabel(extractClass, extractedTypeArgs, inReceiverContext) // Extract both the Kotlin and equivalent Java classes, so that we have database entries // for both even if all internal references to the Kotlin type are substituted. if (c != extractClass) { extractClassLaterIfExternal(c) } return UseClassInstanceResult(classTypeResult, extractClass) } private fun extractClassLaterIfExternal(c: IrClass) { if (isExternalDeclaration(c)) { extractExternalClassLater(c) } } private fun extractExternalEnclosingClassLater(d: IrDeclaration) { when (val parent = d.parent) { is IrClass -> extractExternalClassLater(parent) is IrFunction -> extractExternalEnclosingClassLater(parent) is IrFile -> logger.error("extractExternalEnclosingClassLater but no enclosing class.") is IrExternalPackageFragment -> { // The parent is a (multi)file class. We don't need // extract it separately. } else -> logger.error( "Unrecognised extractExternalEnclosingClassLater ${parent.javaClass} for ${d.javaClass}" ) } } private fun propertySignature(p: IrProperty) = ((p.getter ?: p.setter)?.extensionReceiverParameter?.let { useType(erase(it.type)).javaResult.signature } ?: "") fun getTrapFileSignature(d: IrDeclaration) = when (d) { is IrFunction -> // Note we erase the parameter types before calling useType even though the // signature should be the same // in order to prevent an infinite loop through useTypeParameter -> // useDeclarationParent -> useFunction // -> extractFunctionLaterIfExternalFileMember, which would result for `fun f(t: // T) { ... }` for example. (listOfNotNull(d.extensionReceiverParameter) + d.valueParameters) .map { useType(erase(it.type)).javaResult.signature } .joinToString(separator = ",", prefix = "(", postfix = ")") is IrProperty -> propertySignature(d) + externalClassExtractor.propertySignature is IrField -> (d.correspondingPropertySymbol?.let { propertySignature(it.owner) } ?: "") + externalClassExtractor.fieldSignature else -> "unknown signature" .also { logger.warn("Trap file signature requested for unexpected element $d") } } private fun extractParentExternalClassLater(d: IrDeclaration) { val p = d.parent when (p) { is IrClass -> extractExternalClassLater(p) is IrExternalPackageFragment -> { // The parent is a (multi)file class. We don't need to // extract it separately. } else -> { logger.warn("Unexpected parent type ${p.javaClass} for external file class member") } } } private fun extractPropertyLaterIfExternalFileMember(p: IrProperty) { if (isExternalFileClassMember(p)) { extractParentExternalClassLater(p) val signature = getTrapFileSignature(p) dependencyCollector?.addDependency(p, signature) externalClassExtractor.extractLater(p, signature) } } private fun extractFieldLaterIfExternalFileMember(f: IrField) { if (isExternalFileClassMember(f)) { extractParentExternalClassLater(f) val signature = getTrapFileSignature(f) dependencyCollector?.addDependency(f, signature) externalClassExtractor.extractLater(f, signature) } } private fun extractFunctionLaterIfExternalFileMember(f: IrFunction) { if (isExternalFileClassMember(f)) { extractParentExternalClassLater(f) (f as? IrSimpleFunction)?.correspondingPropertySymbol?.let { extractPropertyLaterIfExternalFileMember(it.owner) // No need to extract the function specifically, as the property's // getters and setters are extracted alongside it return } val signature = getTrapFileSignature(f) dependencyCollector?.addDependency(f, signature) externalClassExtractor.extractLater(f, signature) } } fun extractExternalClassLater(c: IrClass) { dependencyCollector?.addDependency(c) externalClassExtractor.extractLater(c) } private fun tryReplaceAndroidSyntheticClass(c: IrClass): IrClass { // The Android Kotlin Extensions Gradle plugin introduces synthetic functions, fields and // classes. The most // obvious signature is that they lack any supertype information even though they are not // root classes. // If possible, replace them by a real version of the same class. if ( c.superTypes.isNotEmpty() || c.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB || c.hasEqualFqName(FqName("java.lang.Object")) ) return c return globalExtensionState.syntheticToRealClassMap.getOrPut(c) { val qualifiedName = c.fqNameWhenAvailable if (qualifiedName == null) { logger.warn( "Failed to replace synthetic class ${c.name} because it has no fully qualified name" ) return@getOrPut null } val result = getClassByFqName(pluginContext, qualifiedName)?.owner if (result != null) { logger.info("Replaced synthetic class ${c.name} with its real equivalent") return@getOrPut result } // The above doesn't work for (some) generated nested classes, such as R$id, which // should be R.id val fqn = qualifiedName.asString() if (fqn.indexOf('$') >= 0) { val nested = getClassByFqName(pluginContext, fqn.replace('$', '.'))?.owner if (nested != null) { logger.info( "Replaced synthetic nested class ${c.name} with its real equivalent" ) return@getOrPut nested } } logger.warn("Failed to replace synthetic class ${c.name}") return@getOrPut null } ?: c } private fun tryReplaceFunctionInSyntheticClass( f: IrFunction, getClassReplacement: (IrClass) -> IrClass ): IrFunction { val parentClass = f.parent as? IrClass ?: return f val replacementClass = getClassReplacement(parentClass) if (replacementClass === parentClass) return f return globalExtensionState.syntheticToRealFunctionMap.getOrPut(f) { val result = replacementClass.declarations.findSubType { replacementDecl -> replacementDecl.name == f.name && replacementDecl.valueParameters.size == f.valueParameters.size && replacementDecl.valueParameters.zip(f.valueParameters).all { erase(it.first.type) == erase(it.second.type) } } if (result == null) { logger.warn("Failed to replace synthetic class function ${f.name}") } else { logger.info("Replaced synthetic class function ${f.name} with its real equivalent") } result } ?: f } fun tryReplaceSyntheticFunction(f: IrFunction): IrFunction { val androidReplacement = tryReplaceFunctionInSyntheticClass(f) { tryReplaceAndroidSyntheticClass(it) } return tryReplaceFunctionInSyntheticClass(androidReplacement) { tryReplaceParcelizeRawType(it)?.first ?: it } } fun tryReplaceAndroidSyntheticField(f: IrField): IrField { val parentClass = f.parent as? IrClass ?: return f val replacementClass = tryReplaceAndroidSyntheticClass(parentClass) if (replacementClass === parentClass) return f return globalExtensionState.syntheticToRealFieldMap.getOrPut(f) { val result = replacementClass.declarations.findSubType { replacementDecl -> replacementDecl.name == f.name } ?: replacementClass.declarations .findSubType { it.backingField?.name == f.name } ?.backingField if (result == null) { logger.warn("Failed to replace synthetic class field ${f.name}") } else { logger.info("Replaced synthetic class field ${f.name} with its real equivalent") } result } ?: f } private fun tryReplaceType( cBeforeReplacement: IrClass, argsIncludingOuterClassesBeforeReplacement: List? ): Pair?> { val c = tryReplaceAndroidSyntheticClass(cBeforeReplacement) val p = tryReplaceParcelizeRawType(c) return Pair(p?.first ?: c, p?.second ?: argsIncludingOuterClassesBeforeReplacement) } private fun avoidInfiniteRecursion( pair: Pair?> ): Pair?> { val c = pair.first val args = pair.second if (args != null && args.isNotEmpty() && classInstanceStack.possiblyCyclicExtraction(c, args)) { logger.warn("Making use of ${c.name} a raw type to avoid infinite recursion") return Pair(c, null) } else { return pair } } // `typeArgs` can be null to describe a raw generic type. // For non-generic types it will be zero-length list. private fun addClassLabel( cBeforeReplacement: IrClass, argsIncludingOuterClassesBeforeReplacement: List?, inReceiverContext: Boolean = false ): TypeResult { val replaced = avoidInfiniteRecursion(tryReplaceType(cBeforeReplacement, argsIncludingOuterClassesBeforeReplacement)) val replacedClass = replaced.first val replacedArgsIncludingOuterClasses = replaced.second val classLabelResult = getClassLabel(replacedClass, replacedArgsIncludingOuterClasses) var instanceSeenBefore = true val classLabel: Label = tw.getLabelFor(classLabelResult.classLabel) { instanceSeenBefore = false extractClassLaterIfExternal(replacedClass) } if ( replacedArgsIncludingOuterClasses == null || replacedArgsIncludingOuterClasses.isNotEmpty() ) { // If this is a generic type instantiation or a raw type then it has no // source entity, so we need to extract it here val shouldExtractClassDetails = inReceiverContext && tw.lm.genericSpecialisationsExtracted.add(classLabelResult.classLabel) if (!instanceSeenBefore || shouldExtractClassDetails) { this.withFileOfClass(replacedClass) .extractClassInstance( classLabel, replacedClass, replacedArgsIncludingOuterClasses, !instanceSeenBefore, shouldExtractClassDetails ) } } val fqName = replacedClass.fqNameWhenAvailable val signature = if (replacedClass.isAnonymousObject) { null } else if (fqName == null) { logger.error("Unable to find signature/fqName for ${replacedClass.name}") null } else { fqName.asString() } return TypeResult(classLabel, signature, classLabelResult.shortName) } private fun tryReplaceParcelizeRawType(c: IrClass): Pair?>? { if ( c.superTypes.isNotEmpty() || c.origin != IrDeclarationOrigin.DEFINED || c.hasEqualFqName(FqName("java.lang.Object")) ) { return null } val fqName = c.fqNameWhenAvailable if (fqName == null) { return null } fun tryGetPair(arity: Int): Pair?>? { val replaced = getClassByFqName(pluginContext, fqName)?.owner ?: return null return Pair( replaced, List(arity) { makeTypeProjection(pluginContext.irBuiltIns.anyNType, Variance.INVARIANT) } ) } // The list of types handled here match // https://github.com/JetBrains/kotlin/blob/d7c7d1efd2c0983c13b175e9e4b1cda979521159/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidSymbols.kt // Specifically, types are added for generic types created in AndroidSymbols.kt. // This replacement is from a raw type to its matching parameterized type with `Object` type // arguments. return when (fqName.asString()) { "java.util.ArrayList" -> tryGetPair(1) "java.util.LinkedHashMap" -> tryGetPair(2) "java.util.LinkedHashSet" -> tryGetPair(1) "java.util.List" -> tryGetPair(1) "java.util.TreeMap" -> tryGetPair(2) "java.util.TreeSet" -> tryGetPair(1) "java.lang.Class" -> tryGetPair(1) else -> null } } private fun useAnonymousClass(c: IrClass) = tw.lm.anonymousTypeMapping.getOrPut(c) { TypeResults( TypeResult(tw.getFreshIdLabel(), "", ""), TypeResult(fakeKotlinType(), "TODO", "TODO") ) } fun fakeKotlinType(): Label { val fakeKotlinPackageId: Label = tw.getLabelFor("@\"FakeKotlinPackage\"", { tw.writePackages(it, "fake.kotlin") }) val fakeKotlinClassId: Label = tw.getLabelFor( "@\"FakeKotlinClass\"", { tw.writeClasses_or_interfaces(it, "FakeKotlinClass", fakeKotlinPackageId, it) } ) val fakeKotlinTypeId: Label = tw.getLabelFor( "@\"FakeKotlinType\"", { tw.writeKt_nullable_types(it, fakeKotlinClassId) } ) return fakeKotlinTypeId } // `args` can be null to describe a raw generic type. // For non-generic types it will be zero-length list. fun useSimpleTypeClass( c: IrClass, args: List?, hasQuestionMark: Boolean ): TypeResults { val classInstanceResult = useClassInstance(c, args) val javaClassId = classInstanceResult.typeResult.id val kotlinQualClassName = getUnquotedClassLabel(c, args).classLabel val javaResult = classInstanceResult.typeResult val kotlinResult = if (true) TypeResult(fakeKotlinType(), "TODO", "TODO") else if (hasQuestionMark) { val kotlinSignature = "$kotlinQualClassName?" // TODO: Is this right? val kotlinLabel = "@\"kt_type;nullable;$kotlinQualClassName\"" val kotlinId: Label = tw.getLabelFor(kotlinLabel, { tw.writeKt_nullable_types(it, javaClassId) }) TypeResult(kotlinId, kotlinSignature, "TODO") } else { val kotlinSignature = kotlinQualClassName // TODO: Is this right? val kotlinLabel = "@\"kt_type;notnull;$kotlinQualClassName\"" val kotlinId: Label = tw.getLabelFor(kotlinLabel, { tw.writeKt_notnull_types(it, javaClassId) }) TypeResult(kotlinId, kotlinSignature, "TODO") } return TypeResults(javaResult, kotlinResult) } // Given either a primitive array or a boxed array, returns primitive arrays unchanged, // but returns boxed arrays with a nullable, invariant component type, with any nested arrays // similarly transformed. For example, Array> would become Array?> // Array<*> will become Array. private fun getInvariantNullableArrayType(arrayType: IrSimpleType): IrSimpleType = if (arrayType.isPrimitiveArray()) arrayType else { val componentType = arrayType.getArrayElementTypeCodeQL(pluginContext.irBuiltIns) val componentTypeBroadened = when (componentType) { is IrSimpleType -> if (isArray(componentType)) getInvariantNullableArrayType(componentType) else componentType else -> componentType } val unchanged = componentType == componentTypeBroadened && (arrayType.arguments[0] as? IrTypeProjection)?.variance == Variance.INVARIANT && componentType.isNullableCodeQL() if (unchanged) arrayType else IrSimpleTypeImpl( arrayType.classifier, true, listOf(makeTypeProjection(componentTypeBroadened, Variance.INVARIANT)), listOf() ) } /* Kotlin arrays can be broken down as: isArray(t) |- t.isBoxedArrayCodeQL | |- t.isArray() e.g. Array, Array | |- t.isNullableArray() e.g. Array?, Array? |- t.isPrimitiveArray() e.g. BooleanArray For the corresponding Java types: Boxed arrays are represented as e.g. java.lang.Boolean[]. Primitive arrays are represented as e.g. boolean[]. */ private fun isArray(t: IrType) = t.isBoxedArrayCodeQL || t.isPrimitiveArray() data class ArrayInfo( val elementTypeResults: TypeResults, val componentTypeResults: TypeResults, val dimensions: Int ) /** * `t` is somewhere in a stack of array types, or possibly the element type of the innermost * array. For example, in `Array>`, we will be called with `t` being * `Array>`, then `Array`, then `Int`. `isPrimitiveArray` is true if we are * immediately nested inside a primitive array. */ private fun useArrayType(t: IrType, isPrimitiveArray: Boolean): ArrayInfo { if (!isArray(t)) { val nullableT = if (t.isPrimitiveType() && !isPrimitiveArray) t.makeNullable() else t val typeResults = useType(nullableT) return ArrayInfo(typeResults, typeResults, 0) } if (t !is IrSimpleType) { logger.error("Unexpected non-simple array type: ${t.javaClass}") return ArrayInfo(extractErrorType(), extractErrorType(), 0) } val arrayClass = t.classifier.owner if (arrayClass !is IrClass) { logger.error("Unexpected owner type for array type: ${arrayClass.javaClass}") return ArrayInfo(extractErrorType(), extractErrorType(), 0) } // Because Java's arrays are covariant, Kotlin will render // Array as Object[], Array> as Object[][] etc. val elementType = if ( (t.arguments.singleOrNull() as? IrTypeProjection)?.variance == Variance.IN_VARIANCE ) { pluginContext.irBuiltIns.anyType } else { t.getArrayElementTypeCodeQL(pluginContext.irBuiltIns) } val recInfo = useArrayType(elementType, t.isPrimitiveArray()) val javaShortName = recInfo.componentTypeResults.javaResult.shortName + "[]" val kotlinShortName = recInfo.componentTypeResults.kotlinResult.shortName + "[]" val elementTypeLabel = recInfo.elementTypeResults.javaResult.id val componentTypeLabel = recInfo.componentTypeResults.javaResult.id val dimensions = recInfo.dimensions + 1 val id = tw.getLabelFor("@\"array;$dimensions;{$elementTypeLabel}\"") { tw.writeArrays(it, javaShortName, elementTypeLabel, dimensions, componentTypeLabel) extractClassSupertypes( arrayClass, it, ExtractSupertypesMode.Specialised(t.arguments) ) // array.length val length = tw.getLabelFor("@\"field;{$it};length\"") val intTypeIds = useType(pluginContext.irBuiltIns.intType) tw.writeFields(length, "length", intTypeIds.javaResult.id, it) tw.writeFieldsKotlinType(length, intTypeIds.kotlinResult.id) addModifiers(length, "public", "final") // Note we will only emit one `clone()` method per Java array type, so we choose // `Array` as its Kotlin // return type, where C is the component type with any nested arrays themselves // invariant and nullable. val kotlinCloneReturnType = getInvariantNullableArrayType(t).makeNullable() val kotlinCloneReturnTypeLabel = useType(kotlinCloneReturnType).kotlinResult.id val clone = tw.getLabelFor("@\"callable;{$it}.clone(){$it}\"") tw.writeMethods(clone, "clone", "clone()", it, it, clone) tw.writeMethodsKotlinType(clone, kotlinCloneReturnTypeLabel) addModifiers(clone, "public") } val javaResult = TypeResult(id, recInfo.componentTypeResults.javaResult.signature + "[]", javaShortName) val kotlinResult = TypeResult( fakeKotlinType(), recInfo.componentTypeResults.kotlinResult.signature + "[]", kotlinShortName ) val typeResults = TypeResults(javaResult, kotlinResult) return ArrayInfo(recInfo.elementTypeResults, typeResults, dimensions) } enum class TypeContext { RETURN, GENERIC_ARGUMENT, OTHER } private fun useSimpleType(s: IrSimpleType, context: TypeContext): TypeResults { if (s.abbreviation != null) { // TODO: Extract this information } // We use this when we don't actually have an IrClass for a class // we want to refer to // TODO: Eliminate the need for this if possible fun makeClass(pkgName: String, className: String): Label { val pkgId = extractPackage(pkgName) val label = "@\"class;$pkgName.$className\"" val classId: Label = tw.getLabelFor(label, { tw.writeClasses_or_interfaces(it, className, pkgId, it) }) return classId } fun primitiveType( kotlinClass: IrClass, primitiveName: String?, otherIsPrimitive: Boolean, javaClass: IrClass, kotlinPackageName: String, kotlinClassName: String ): TypeResults { // Note the use of `hasEnhancedNullability` here covers cases like `@NotNull Integer`, // which must be extracted as `Integer` not `int`. val javaResult = if ( (context == TypeContext.RETURN || (context == TypeContext.OTHER && otherIsPrimitive)) && !s.isNullableCodeQL() && getKotlinType(s)?.hasEnhancedNullability() != true && primitiveName != null ) { val label: Label = tw.getLabelFor( "@\"type;$primitiveName\"", { tw.writePrimitives(it, primitiveName) } ) TypeResult(label, primitiveName, primitiveName) } else { addClassLabel(javaClass, listOf()) } val kotlinClassId = useClassInstance(kotlinClass, listOf()).typeResult.id val kotlinResult = if (true) TypeResult(fakeKotlinType(), "TODO", "TODO") else if (s.isNullableCodeQL()) { val kotlinSignature = "$kotlinPackageName.$kotlinClassName?" // TODO: Is this right? val kotlinLabel = "@\"kt_type;nullable;$kotlinPackageName.$kotlinClassName\"" val kotlinId: Label = tw.getLabelFor( kotlinLabel, { tw.writeKt_nullable_types(it, kotlinClassId) } ) TypeResult(kotlinId, kotlinSignature, "TODO") } else { val kotlinSignature = "$kotlinPackageName.$kotlinClassName" // TODO: Is this right? val kotlinLabel = "@\"kt_type;notnull;$kotlinPackageName.$kotlinClassName\"" val kotlinId: Label = tw.getLabelFor(kotlinLabel, { tw.writeKt_notnull_types(it, kotlinClassId) }) TypeResult(kotlinId, kotlinSignature, "TODO") } return TypeResults(javaResult, kotlinResult) } val owner = s.classifier.owner val primitiveInfo = primitiveTypeMapping.getPrimitiveInfo(s) when { primitiveInfo != null -> { if (owner is IrClass) { return primitiveType( owner, primitiveInfo.primitiveName, primitiveInfo.otherIsPrimitive, primitiveInfo.javaClass, primitiveInfo.kotlinPackageName, primitiveInfo.kotlinClassName ) } else { logger.error( "Got primitive info for non-class (${owner.javaClass}) for ${s.render()}" ) return extractErrorType() } } (s.isBoxedArrayCodeQL && s.arguments.isNotEmpty()) || s.isPrimitiveArray() -> { val arrayInfo = useArrayType(s, false) return arrayInfo.componentTypeResults } owner is IrClass -> { val args = if (s.codeQlIsRawType()) null else s.arguments return useSimpleTypeClass(owner, args, s.isNullableCodeQL()) } owner is IrTypeParameter -> { val javaResult = useTypeParameter(owner) val aClassId = makeClass("kotlin", "TypeParam") // TODO: Wrong val kotlinResult = if (true) TypeResult(fakeKotlinType(), "TODO", "TODO") else if (s.isNullableCodeQL()) { val kotlinSignature = "${javaResult.signature}?" // TODO: Wrong val kotlinLabel = "@\"kt_type;nullable;type_param\"" // TODO: Wrong val kotlinId: Label = tw.getLabelFor(kotlinLabel, { tw.writeKt_nullable_types(it, aClassId) }) TypeResult(kotlinId, kotlinSignature, "TODO") } else { val kotlinSignature = javaResult.signature // TODO: Wrong val kotlinLabel = "@\"kt_type;notnull;type_param\"" // TODO: Wrong val kotlinId: Label = tw.getLabelFor(kotlinLabel, { tw.writeKt_notnull_types(it, aClassId) }) TypeResult(kotlinId, kotlinSignature, "TODO") } return TypeResults(javaResult, kotlinResult) } else -> { logger.error("Unrecognised IrSimpleType: " + s.javaClass + ": " + s.render()) return extractErrorType() } } } private fun parentOf(d: IrDeclaration): IrDeclarationParent { if (d is IrField) { return getFieldParent(d) } return d.parent } fun useDeclarationParentOf( // The declaration d: IrDeclaration, // Whether the type of entity whose parent this is can be a // top-level entity in the JVM's eyes. If so, then its parent may // be a file; otherwise, if dp is a file foo.kt, then the parent // is really the JVM class FooKt. canBeTopLevel: Boolean, classTypeArguments: List? = null, inReceiverContext: Boolean = false ): Label? { val parent = parentOf(d) if (parent is IrExternalPackageFragment) { // This is in a file class. val fqName = getFileClassFqName(d) if (fqName == null) { logger.error( "Can't get FqName for declaration in external package fragment ${d.javaClass}" ) return null } return extractFileClass(fqName) } return useDeclarationParent(parent, canBeTopLevel, classTypeArguments, inReceiverContext) } // Generally, useDeclarationParentOf should be used instead of // calling this directly, as this cannot handle // IrExternalPackageFragment fun useDeclarationParent( // The declaration parent according to Kotlin dp: IrDeclarationParent, // Whether the type of entity whose parent this is can be a // top-level entity in the JVM's eyes. If so, then its parent may // be a file; otherwise, if dp is a file foo.kt, then the parent // is really the JVM class FooKt. canBeTopLevel: Boolean, classTypeArguments: List? = null, inReceiverContext: Boolean = false ): Label? = when (dp) { is IrFile -> if (canBeTopLevel) { usePackage(dp.packageFqName.asString()) } else { extractFileClass(dp) } is IrClass -> if (classTypeArguments != null) { useClassInstance(dp, classTypeArguments, inReceiverContext).typeResult.id } else { val replacedType = tryReplaceParcelizeRawType(dp) if (replacedType == null) { useClassSource(dp) } else { useClassInstance(replacedType.first, replacedType.second, inReceiverContext) .typeResult .id } } is IrFunction -> useFunction(dp) is IrExternalPackageFragment -> { logger.error("Unable to handle IrExternalPackageFragment as an IrDeclarationParent") null } else -> { logger.error("Unrecognised IrDeclarationParent: " + dp.javaClass) null } } private val IrDeclaration.isAnonymousFunction get() = this is IrSimpleFunction && name == SpecialNames.NO_NAME_PROVIDED data class FunctionNames(val nameInDB: String, val kotlinName: String) @OptIn(ObsoleteDescriptorBasedAPI::class) private fun getJvmModuleName(f: IrFunction) = NameUtils.sanitizeAsJavaIdentifier( getJvmModuleNameForDeserializedDescriptor(f.descriptor) ?: JvmCodegenUtil.getModuleName(pluginContext.moduleDescriptor) ) fun getFunctionShortName(f: IrFunction): FunctionNames { if (f.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA || f.isAnonymousFunction) return FunctionNames( OperatorNameConventions.INVOKE.asString(), OperatorNameConventions.INVOKE.asString() ) fun getSuffixIfInternal() = if ( f.visibility == DescriptorVisibilities.INTERNAL && f !is IrConstructor && !(f.parent is IrFile || isExternalFileClassMember(f)) ) { "\$" + getJvmModuleName(f) } else { "" } (f as? IrSimpleFunction)?.correspondingPropertySymbol?.let { val propName = it.owner.name.asString() val getter = it.owner.getter val setter = it.owner.setter if (it.owner.parentClassOrNull?.kind == ClassKind.ANNOTATION_CLASS) { if (getter == null) { logger.error( "Expected to find a getter for a property inside an annotation class" ) return FunctionNames(propName, propName) } else { val jvmName = getJvmName(getter) return FunctionNames(jvmName ?: propName, propName) } } val maybeFunctionName = when (f) { getter -> JvmAbi.getterName(propName) setter -> JvmAbi.setterName(propName) else -> { logger.error( "Function has a corresponding property, but is neither the getter nor the setter" ) null } } maybeFunctionName?.let { defaultFunctionName -> val suffix = if ( f.visibility == DescriptorVisibilities.PRIVATE && f.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR ) { "\$private" } else { getSuffixIfInternal() } return FunctionNames( getJvmName(f) ?: "$defaultFunctionName$suffix", defaultFunctionName ) } } return FunctionNames( getJvmName(f) ?: "${f.name.asString()}${getSuffixIfInternal()}", f.name.asString() ) } // This excludes class type parameters that show up in (at least) constructors' typeParameters // list. fun getFunctionTypeParameters(f: IrFunction): List { return if (f is IrConstructor) f.typeParameters else f.typeParameters.filter { it.parent == f } } private fun getTypeParameters(dp: IrDeclarationParent): List = when (dp) { is IrClass -> dp.typeParameters is IrFunction -> getFunctionTypeParameters(dp) else -> listOf() } private fun getEnclosingClass(it: IrDeclarationParent): IrClass? = when (it) { is IrClass -> it is IrFunction -> getEnclosingClass(it.parent) else -> null } val javaUtilCollection by lazy { referenceExternalClass("java.util.Collection") } val wildcardCollectionType by lazy { javaUtilCollection?.let { it.symbol.typeWithArguments(listOf(IrStarProjectionImpl)) } } private fun makeCovariant(t: IrTypeArgument) = t.typeOrNull?.let { makeTypeProjection(it, Variance.OUT_VARIANCE) } ?: t private fun makeArgumentsCovariant(t: IrType) = (t as? IrSimpleType)?.let { t.toBuilder() .also { b -> b.arguments = b.arguments.map(this::makeCovariant) } .buildSimpleType() } ?: t fun eraseCollectionsMethodParameterType( t: IrType, collectionsMethodName: String, paramIdx: Int ) = when (collectionsMethodName) { "contains", "remove", "containsKey", "containsValue", "get", "indexOf", "lastIndexOf" -> javaLangObjectType "getOrDefault" -> if (paramIdx == 0) javaLangObjectType else null "containsAll", "removeAll", "retainAll" -> wildcardCollectionType // Kotlin defines these like addAll(Collection); Java uses addAll(Collection) "putAll", "addAll" -> makeArgumentsCovariant(t) else -> null } ?: t private fun overridesFunctionDefinedOn(f: IrFunction, packageName: String, className: String) = (f as? IrSimpleFunction)?.let { it.overriddenSymbols.any { overridden -> overridden.owner.parentClassOrNull?.let { defnClass -> defnClass.name.asString() == className && defnClass.packageFqName?.asString() == packageName } ?: false } } ?: false @OptIn(ObsoleteDescriptorBasedAPI::class) fun overridesCollectionsMethodWithAlteredParameterTypes(f: IrFunction) = BuiltinMethodsWithSpecialGenericSignature .getOverriddenBuiltinFunctionWithErasedValueParametersInJava(f.descriptor) != null || (f.name.asString() == "putAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableMap")) || (f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableCollection")) || (f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableList")) private val jvmWildcardAnnotation = FqName("kotlin.jvm.JvmWildcard") private val jvmWildcardSuppressionAnnotation = FqName("kotlin.jvm.JvmSuppressWildcards") private fun arrayExtendsAdditionAllowed(t: IrSimpleType): Boolean = // Note the array special case includes Array<*>, which does permit adding `? extends ...` // (making `? extends Object[]` in that case) // Surprisingly Array does permit this as well, though the contravariant array lowers // to Object[] so this ends up `? extends Object[]` as well. t.arguments[0].let { when (it) { is IrTypeProjection -> when (it.variance) { Variance.INVARIANT -> false Variance.IN_VARIANCE -> !(it.type.isAny() || it.type.isNullableAny()) Variance.OUT_VARIANCE -> extendsAdditionAllowed(it.type) } else -> true } } private fun extendsAdditionAllowed(t: IrType) = if (t.isBoxedArrayCodeQL) { if (t is IrSimpleType) { arrayExtendsAdditionAllowed(t) } else { logger.warn("Boxed array of unexpected kind ${t.javaClass}") // Return false, for no particular reason false } } else { ((t as? IrSimpleType)?.classOrNull?.owner?.isFinalClass) != true } private fun wildcardAdditionAllowed( v: Variance, t: IrType, addByDefault: Boolean, javaVariance: Variance? ) = when { t.hasAnnotation(jvmWildcardAnnotation) -> true // If a Java declaration specifies a variance, introduce it even if it's pointless (e.g. // ? extends FinalClass, or ? super Object) javaVariance == v -> true !addByDefault -> false v == Variance.IN_VARIANCE -> !(t.isNullableAny() || t.isAny()) v == Variance.OUT_VARIANCE -> extendsAdditionAllowed(t) else -> false } // Returns true if `t` has `@JvmSuppressWildcards` or `@JvmSuppressWildcards(true)`, // false if it has `@JvmSuppressWildcards(false)`, // and null if the annotation is not present. @Suppress("UNCHECKED_CAST") private fun getWildcardSuppressionDirective(t: IrAnnotationContainer): Boolean? = t.getAnnotation(jvmWildcardSuppressionAnnotation)?.let { @Suppress("USELESS_CAST") // `as? Boolean` is not needed for Kotlin < 2.1 (it.getValueArgument(0) as? CodeQLIrConst)?.value as? Boolean ?: true } private fun addJavaLoweringArgumentWildcards( p: IrTypeParameter, t: IrTypeArgument, addByDefault: Boolean, javaType: JavaType? ): IrTypeArgument = (t as? IrTypeProjection)?.let { val newAddByDefault = getWildcardSuppressionDirective(it.type)?.not() ?: addByDefault val newBase = addJavaLoweringWildcards(it.type, newAddByDefault, javaType) // Note javaVariance == null means we don't have a Java type to conform to -- for // example if this is a Kotlin source definition. val javaVariance = javaType?.let { jType -> when (jType) { is JavaWildcardType -> if (jType.isExtends) Variance.OUT_VARIANCE else Variance.IN_VARIANCE else -> Variance.INVARIANT } } val newVariance = if ( it.variance == Variance.INVARIANT && p.variance != Variance.INVARIANT && // The next line forbids inferring a wildcard type when we have a // corresponding Java type with conflicting variance. // For example, Java might declare f(Comparable cs), in which // case we shouldn't add a `? super ...` // wildcard. Note if javaType is unknown (e.g. this is a Kotlin source // element), we assume wildcards should be added. (javaVariance == null || javaVariance == p.variance) && wildcardAdditionAllowed(p.variance, it.type, newAddByDefault, javaVariance) ) p.variance else it.variance if (newBase !== it.type || newVariance != it.variance) makeTypeProjection(newBase, newVariance) else null } ?: t private fun getJavaTypeArgument(jt: JavaType, idx: Int): JavaType? = when (jt) { is JavaWildcardType -> jt.bound?.let { getJavaTypeArgument(it, idx) } is JavaClassifierType -> jt.typeArguments.getOrNull(idx) is JavaArrayType -> if (idx == 0) jt.componentType else null else -> null } fun addJavaLoweringWildcards(t: IrType, addByDefault: Boolean, javaType: JavaType?): IrType = (t as? IrSimpleType)?.let { val newAddByDefault = getWildcardSuppressionDirective(t)?.not() ?: addByDefault val typeParams = it.classOrNull?.owner?.typeParameters ?: return t val newArgs = typeParams.zip(it.arguments).mapIndexed { idx, pair -> addJavaLoweringArgumentWildcards( pair.first, pair.second, newAddByDefault, javaType?.let { jt -> getJavaTypeArgument(jt, idx) } ) } return if (newArgs.zip(it.arguments).all { pair -> pair.first === pair.second }) t else it.toBuilder().also { builder -> builder.arguments = newArgs }.buildSimpleType() } ?: t /* * This is the normal getFunctionLabel function to use. If you want * to refer to the function in its source class then * classTypeArgsIncludingOuterClasses should be null. Otherwise, it * is the list of type arguments that need to be applied to its * enclosing classes to get the instantiation that this function is * in. */ fun getFunctionLabel( f: IrFunction, classTypeArgsIncludingOuterClasses: List? ): String? { val parentId = useDeclarationParentOf(f, false, classTypeArgsIncludingOuterClasses, true) if (parentId == null) { logger.error("Couldn't get parent ID for function label") return null } return getFunctionLabel(f, parentId, classTypeArgsIncludingOuterClasses) } /* * There are some pairs of classes (e.g. `kotlin.Throwable` and * `java.lang.Throwable`) which are really just 2 different names * for the same class. However, we extract them as separate * classes. When extracting `kotlin.Throwable`'s methods, if we * looked up the parent ID ourselves, we would get as ID for * `java.lang.Throwable`, which isn't what we want. So we have to * allow it to be passed in. * * `maybeParameterList` can be supplied to override the function's * value parameters; this is used for generating labels of overloads * that omit one or more parameters that has a default value specified. */ @OptIn(ObsoleteDescriptorBasedAPI::class) fun getFunctionLabel( f: IrFunction, parentId: Label, classTypeArgsIncludingOuterClasses: List?, maybeParameterList: List? = null ): String = getFunctionLabel( f.parent, parentId, getFunctionShortName(f).nameInDB, (maybeParameterList ?: f.valueParameters).map { it.type }, getAdjustedReturnType(f), f.extensionReceiverParameter?.type, getFunctionTypeParameters(f), classTypeArgsIncludingOuterClasses, overridesCollectionsMethodWithAlteredParameterTypes(f), getJavaCallable(f), !getInnermostWildcardSupppressionAnnotation(f) ) /* * This function actually generates the label for a function. * Sometimes, a function is only generated by kotlinc when writing a * class file, so there is no corresponding `IrFunction` for it. * This function therefore takes all the constituent parts of a * function instead. */ fun getFunctionLabel( // The parent of the function; normally f.parent. parent: IrDeclarationParent, // The ID of the function's parent, or null if we should work it out ourselves. parentId: Label, // The name of the function; normally f.name.asString(). name: String, // The types of the value parameters that the functions takes; normally // f.valueParameters.map { it.type }. parameterTypes: List, // The return type of the function; normally f.returnType. returnType: IrType, // The extension receiver of the function, if any; normally // f.extensionReceiverParameter?.type. extensionParamType: IrType?, // The type parameters of the function. This does not include type parameters of enclosing // classes. functionTypeParameters: List, // The type arguments of enclosing classes of the function. classTypeArgsIncludingOuterClasses: List?, // If true, this method implements a Java Collections interface (Collection, Map or List) // and may need // parameter erasure to match the way this class will appear to an external consumer of the // .class file. overridesCollectionsMethod: Boolean, // The Java signature of this callable, if known. javaSignature: JavaMember?, // If true, Java wildcards implied by Kotlin type parameter variance should be added by // default to this function's value parameters' types. // (Return-type wildcard addition is always off by default) addParameterWildcardsByDefault: Boolean, // The prefix used in the label. "callable", unless a property label is created, then it's // "property". prefix: String = "callable" ): String { val allParamTypes = if (extensionParamType == null) parameterTypes else listOf(extensionParamType) + parameterTypes val substitutionMap = classTypeArgsIncludingOuterClasses?.let { notNullArgs -> if (notNullArgs.isEmpty()) { null } else { val enclosingClass = getEnclosingClass(parent) enclosingClass?.let { notNullClass -> makeTypeGenericSubstitutionMap(notNullClass, notNullArgs) } } } val getIdForFunctionLabel = { it: IndexedValue -> // Kotlin rewrites certain Java collections types adding additional generic // constraints-- for example, // Collection.remove(Object) because Collection.remove(Collection::E) in the Kotlin // universe. // If this has happened, erase the type again to get the correct Java signature. val maybeAmendedForCollections = if (overridesCollectionsMethod) eraseCollectionsMethodParameterType(it.value, name, it.index) else it.value // Add any wildcard types that the Kotlin compiler would add in the Java lowering of // this function: val withAddedWildcards = addJavaLoweringWildcards( maybeAmendedForCollections, addParameterWildcardsByDefault, javaSignature?.let { sig -> getJavaValueParameterType(sig, it.index) } ) // Now substitute any class type parameters in: val maybeSubbed = withAddedWildcards.substituteTypeAndArguments( substitutionMap, TypeContext.OTHER, pluginContext ) // Finally, mimic the Java extractor's behaviour by naming functions with type // parameters for their erased types; // those without type parameters are named for the generic type. val maybeErased = if (functionTypeParameters.isEmpty()) maybeSubbed else erase(maybeSubbed) "{${useType(maybeErased).javaResult.id}}" } val paramTypeIds = allParamTypes .withIndex() .joinToString(separator = ",", transform = getIdForFunctionLabel) val labelReturnType = if (name == "") pluginContext.irBuiltIns.unitType else erase( returnType.substituteTypeAndArguments( substitutionMap, TypeContext.RETURN, pluginContext ) ) // Note that `addJavaLoweringWildcards` is not required here because the return type used to // form the function // label is always erased. val returnTypeId = useType(labelReturnType, TypeContext.RETURN).javaResult.id // This suffix is added to generic methods (and constructors) to match the Java extractor's // behaviour. // Comments in that extractor indicates it didn't want the label of the callable to clash // with the raw // method (and presumably that disambiguation is never needed when the method belongs to a // parameterized // instance of a generic class), but as of now I don't know when the raw method would be // referred to. val typeArgSuffix = if ( functionTypeParameters.isNotEmpty() && classTypeArgsIncludingOuterClasses.isNullOrEmpty() ) "<${functionTypeParameters.size}>" else "" return "@\"$prefix;{$parentId}.$name($paramTypeIds){$returnTypeId}$typeArgSuffix\"" } val javaLangClass by lazy { referenceExternalClass("java.lang.Class") } fun kClassToJavaClass(t: IrType): IrType { when (t) { is IrSimpleType -> { if (t.classifier == pluginContext.irBuiltIns.kClassClass) { javaLangClass?.let { jlc -> return jlc.symbol.typeWithArguments(t.arguments) } } else { t.classOrNull?.let { tCls -> if (t.isBoxedArrayCodeQL) { (t.arguments.singleOrNull() as? IrTypeProjection)?.let { elementTypeArg -> val elementType = elementTypeArg.type val replacedElementType = kClassToJavaClass(elementType) if (replacedElementType !== elementType) { val newArg = makeTypeProjection( replacedElementType, elementTypeArg.variance ) return tCls .typeWithArguments(listOf(newArg)) .codeQlWithHasQuestionMark(t.isNullableCodeQL()) } } } } } } is IrDynamicType -> {} is IrErrorType -> {} } return t } fun isAnnotationClassField(f: IrField) = f.correspondingPropertySymbol?.let { isAnnotationClassProperty(it) } ?: false private fun isAnnotationClassProperty(p: IrPropertySymbol) = p.owner.parentClassOrNull?.kind == ClassKind.ANNOTATION_CLASS fun getAdjustedReturnType(f: IrFunction): IrType { // Replace annotation val accessor types as needed: (f as? IrSimpleFunction)?.correspondingPropertySymbol?.let { if (isAnnotationClassProperty(it) && f == it.owner.getter) { val replaced = kClassToJavaClass(f.returnType) if (replaced != f.returnType) return replaced } } // The return type of `java.util.concurrent.ConcurrentHashMap.keySet/0` is defined as // `Set` in the stubs inside the Android SDK. // This does not match the Java SDK return type: `ConcurrentHashMap.KeySetView`, so // it's adjusted here. // This is a deliberate change in the Android SDK: // https://github.com/AndroidSDKSources/android-sdk-sources-for-api-level-31/blob/2c56b25f619575bea12f9c5520ed2259620084ac/java/util/concurrent/ConcurrentHashMap.java#L1244-L1249 // The annotation on the source is not visible in the android.jar, so we can't make the // change based on that. // TODO: there are other instances of `dalvik.annotation.codegen.CovariantReturnType` in the // Android SDK, we should handle those too if they cause DB inconsistencies val parentClass = f.parentClassOrNull if ( parentClass == null || parentClass.fqNameWhenAvailable?.asString() != "java.util.concurrent.ConcurrentHashMap" || getFunctionShortName(f).nameInDB != "keySet" || f.valueParameters.isNotEmpty() || f.returnType.classFqName?.asString() != "kotlin.collections.MutableSet" ) { return f.returnType } val otherKeySet = parentClass.declarations.findSubType { it.name.asString() == "keySet" && it.valueParameters.size == 1 } ?: return f.returnType return otherKeySet.returnType.codeQlWithHasQuestionMark(false) } @OptIn(ObsoleteDescriptorBasedAPI::class) fun getJavaCallable(f: IrFunction) = (f.descriptor.source as? JavaSourceElement)?.javaElement as? JavaMember fun getJavaValueParameterType(m: JavaMember, idx: Int) = when (m) { is JavaMethod -> m.valueParameters[idx].type is JavaConstructor -> m.valueParameters[idx].type else -> null } fun getInnermostWildcardSupppressionAnnotation(d: IrDeclaration) = getWildcardSuppressionDirective(d) ?: // Note not using `parentsWithSelf` as that only works if `d` is an IrDeclarationParent d.parents .filterIsInstance() .mapNotNull { getWildcardSuppressionDirective(it) } .firstOrNull() ?: false /** * Class to hold labels for generated classes around local functions, lambdas, function * references, and property references. */ open class GeneratedClassLabels( val type: TypeResults, val constructor: Label, val constructorBlock: Label ) /** * Class to hold labels generated for locally visible functions, such as * - local functions, * - lambdas, and * - wrappers around function references. */ class LocallyVisibleFunctionLabels( type: TypeResults, constructor: Label, constructorBlock: Label, val function: Label ) : GeneratedClassLabels(type, constructor, constructorBlock) /** * Gets the labels for functions belonging to * - local functions, and * - lambdas. */ fun getLocallyVisibleFunctionLabels(f: IrFunction): LocallyVisibleFunctionLabels { if (!f.isLocalFunction()) { logger.error("Extracting a non-local function as a local one") } var res = tw.lm.locallyVisibleFunctionLabelMapping[f] if (res == null) { val javaResult = TypeResult(tw.getFreshIdLabel(), "", "") val kotlinResult = TypeResult(tw.getFreshIdLabel(), "", "") tw.writeKt_notnull_types(kotlinResult.id, javaResult.id) res = LocallyVisibleFunctionLabels( TypeResults(javaResult, kotlinResult), tw.getFreshIdLabel(), tw.getFreshIdLabel(), tw.getFreshIdLabel() ) tw.lm.locallyVisibleFunctionLabelMapping[f] = res } return res } fun getExistingLocallyVisibleFunctionLabel(f: IrFunction): Label? { if (!f.isLocalFunction()) { return null } return tw.lm.locallyVisibleFunctionLabelMapping[f]?.function } private fun kotlinFunctionToJavaEquivalent(f: IrFunction, noReplace: Boolean): IrFunction = if (noReplace) f else f.parentClassOrNull?.let { parentClass -> getJavaEquivalentClass(parentClass)?.let { javaClass -> if (javaClass != parentClass) { var jvmName = getFunctionShortName(f).nameInDB if ( f.name.asString() == "get" && parentClass.fqNameWhenAvailable?.asString() == "kotlin.String" ) { // `kotlin.String.get` has an equivalent `java.lang.String.get`, which // in turn will be stored in the DB as `java.lang.String.charAt`. // Maybe all operators should be handled the same way, but so far I only // found this case that needed to be special cased. This is the // only operator in `JvmNames.specialFunctions` jvmName = "get" } // Look for an exact type match... javaClass.declarations.findSubType { decl -> !decl.isFakeOverride && decl.name.asString() == jvmName && decl.valueParameters.size == f.valueParameters.size && decl.valueParameters.zip(f.valueParameters).all { p -> erase(p.first.type).classifierOrNull == erase(p.second.type).classifierOrNull } } ?: // Or check property accessors: (f.propertyIfAccessor as? IrProperty)?.let { kotlinProp -> val javaProp = javaClass.declarations.findSubType { decl -> decl.name == kotlinProp.name } if (javaProp?.getter?.name == f.name) javaProp.getter else if (javaProp?.setter?.name == f.name) javaProp.setter else null } ?: run { val parentFqName = parentClass.fqNameWhenAvailable?.asString() logger.warn( "Couldn't find a Java equivalent function to $parentFqName.${f.name.asString()} in ${javaClass.fqNameWhenAvailable?.asString()}" ) null } } else null } } ?: f fun isPrivate(d: IrDeclaration) = when (d) { is IrDeclarationWithVisibility -> d.visibility.let { it == DescriptorVisibilities.PRIVATE || it == DescriptorVisibilities.PRIVATE_TO_THIS } else -> false } fun useFunction( f: IrFunction, classTypeArgsIncludingOuterClasses: List? = null, noReplace: Boolean = false ): Label? { if (f.isLocalFunction()) { val ids = getLocallyVisibleFunctionLabels(f) return ids.function.cast() } val javaFun = kotlinFunctionToJavaEquivalent(f, noReplace) val parentId = useDeclarationParentOf(javaFun, false, classTypeArgsIncludingOuterClasses, true) if (parentId == null) { logger.error("Couldn't find parent ID for function ${f.name.asString()}") return null } return useFunction(f, javaFun, parentId, classTypeArgsIncludingOuterClasses) } fun useFunction( f: IrFunction, parentId: Label, classTypeArgsIncludingOuterClasses: List?, noReplace: Boolean = false ): Label { if (f.isLocalFunction()) { val ids = getLocallyVisibleFunctionLabels(f) return ids.function.cast() } val javaFun = kotlinFunctionToJavaEquivalent(f, noReplace) return useFunction(f, javaFun, parentId, classTypeArgsIncludingOuterClasses) } private fun useFunction( f: IrFunction, javaFun: IrFunction, parentId: Label, classTypeArgsIncludingOuterClasses: List? ): Label { val label = getFunctionLabel(javaFun, parentId, classTypeArgsIncludingOuterClasses) val id: Label = tw.getLabelFor(label) { extractPrivateSpecialisedDeclaration(f, classTypeArgsIncludingOuterClasses) } if (isExternalDeclaration(javaFun)) { extractFunctionLaterIfExternalFileMember(javaFun) extractExternalEnclosingClassLater(javaFun) } return id } private fun extractPrivateSpecialisedDeclaration( d: IrDeclaration, classTypeArgsIncludingOuterClasses: List? ) { // Note here `classTypeArgsIncludingOuterClasses` being null doesn't signify a raw receiver // type but rather that no type args were supplied. // This is because a call to a private method can only be observed inside Kotlin code, and // Kotlin can't represent raw types. if ( this is KotlinFileExtractor && isPrivate(d) && classTypeArgsIncludingOuterClasses != null && classTypeArgsIncludingOuterClasses.isNotEmpty() ) { d.parent.let { when (it) { is IrClass -> this.extractDeclarationPrototype( d, useClassInstance(it, classTypeArgsIncludingOuterClasses).typeResult.id, classTypeArgsIncludingOuterClasses ) else -> logger.warnElement( "Unable to extract specialised declaration that isn't a member of a class", d ) } } } } fun getTypeArgumentLabel(arg: IrTypeArgument): TypeResultWithoutSignature { fun extractBoundedWildcard( wildcardKind: Int, wildcardLabelStr: String, wildcardShortName: String, boundLabel: Label ): Label = tw.getLabelFor(wildcardLabelStr) { wildcardLabel -> tw.writeWildcards(wildcardLabel, wildcardShortName, wildcardKind) tw.writeHasLocation(wildcardLabel, tw.unknownLocation) tw.getLabelFor("@\"bound;0;{$wildcardLabel}\"") { tw.writeTypeBounds(it, boundLabel, 0, wildcardLabel) } } // Note this function doesn't return a signature because type arguments are never // incorporated into function signatures. return when (arg) { is IrStarProjection -> { val anyTypeLabel = useType(pluginContext.irBuiltIns.anyType).javaResult.id.cast() TypeResultWithoutSignature( extractBoundedWildcard(1, "@\"wildcard;\"", "?", anyTypeLabel), Unit, "?" ) } is IrTypeProjection -> { val boundResults = useType(arg.type, TypeContext.GENERIC_ARGUMENT) val boundLabel = boundResults.javaResult.id.cast() if (arg.variance == Variance.INVARIANT) boundResults.javaResult.cast().forgetSignature() else { val keyPrefix = if (arg.variance == Variance.IN_VARIANCE) "super" else "extends" val wildcardKind = if (arg.variance == Variance.IN_VARIANCE) 2 else 1 val wildcardShortName = "? $keyPrefix ${boundResults.javaResult.shortName}" TypeResultWithoutSignature( extractBoundedWildcard( wildcardKind, "@\"wildcard;$keyPrefix{$boundLabel}\"", wildcardShortName, boundLabel ), Unit, wildcardShortName ) } } else -> { logger.error("Unexpected type argument.") extractJavaErrorType().forgetSignature() } } } data class ClassLabelResults(val classLabel: String, val shortName: String) /** * This returns the `X` in c's label `@"class;X"`. * * `argsIncludingOuterClasses` can be null to describe a raw generic type. For non-generic types * it will be zero-length list. */ private fun getUnquotedClassLabel( c: IrClass, argsIncludingOuterClasses: List? ): ClassLabelResults { val pkg = c.packageFqName?.asString() ?: "" val cls = c.name.asString() val label = if (c.isAnonymousObject) "{${useAnonymousClass(c).javaResult.id}}" else when (val parent = c.parent) { is IrClass -> { "${getUnquotedClassLabel(parent, listOf()).classLabel}\$$cls" } is IrFunction -> { "{${useFunction(parent)}}.$cls" } is IrField -> { "{${useField(parent)}}.$cls" } else -> { if (pkg.isEmpty()) cls else "$pkg.$cls" } } val reorderedArgs = orderTypeArgsLeftToRight(c, argsIncludingOuterClasses) val typeArgLabels = reorderedArgs?.map { getTypeArgumentLabel(it) } val typeArgsShortName = if (typeArgLabels == null) "<>" else if (typeArgLabels.isEmpty()) "" else typeArgLabels.takeLast(c.typeParameters.size).joinToString( prefix = "<", postfix = ">", separator = "," ) { it.shortName } val shortNamePrefix = if (c.isAnonymousObject) "" else cls return ClassLabelResults( label + (typeArgLabels?.joinToString(separator = "") { ";{${it.id}}" } ?: "<>"), shortNamePrefix + typeArgsShortName ) } // `args` can be null to describe a raw generic type. // For non-generic types it will be zero-length list. fun getClassLabel( c: IrClass, argsIncludingOuterClasses: List? ): ClassLabelResults { val unquotedLabel = getUnquotedClassLabel(c, argsIncludingOuterClasses) return ClassLabelResults("@\"class;${unquotedLabel.classLabel}\"", unquotedLabel.shortName) } fun useClassSource(c: IrClass): Label { // For source classes, the label doesn't include any type arguments val classTypeResult = addClassLabel(c, listOf()) return classTypeResult.id } fun getTypeParameterParentLabel(param: IrTypeParameter) = param.parent.let { when (it) { is IrClass -> useClassSource(it) is IrFunction -> (if (this is KotlinFileExtractor) this.declarationStack .findOverriddenAttributes(it) ?.takeUnless { // When extracting the `static fun f$default(...)` that accompanies // `fun f(val x: T? = defaultExpr, ...)`, // `f$default` has no type parameters, and so there is no // `f$default::T` to refer to. // We have no good way to extract references to `T` in // `defaultExpr`, so we just fall back on describing it // in terms of `f::T`, even though that type variable ought to be // out of scope here. attribs -> attribs.typeParameters?.isEmpty() == true } ?.id else null) ?: useFunction(it, noReplace = true) else -> { logger.error("Unexpected type parameter parent $it") null } } } fun getTypeParameterLabel(param: IrTypeParameter): String { // Use this instead of `useDeclarationParent` so we can use useFunction with noReplace = // true, // ensuring that e.g. a method-scoped type variable declared on kotlin.String.transform // gets // a different name to the corresponding java.lang.String.transform , even though // useFunction // will usually replace references to one function with the other. val parentLabel = getTypeParameterParentLabel(param) return "@\"typevar;{$parentLabel};${param.name}\"" } private fun useTypeParameter(param: IrTypeParameter) = TypeResult( tw.getLabelFor(getTypeParameterLabel(param)), useType(eraseTypeParameter(param)).javaResult.signature, param.name.asString() ) private fun extractModifier(m: String): Label { val modifierLabel = "@\"modifier;$m\"" val id: Label = tw.getLabelFor(modifierLabel, { tw.writeModifiers(it, m) }) return id } fun addModifiers(modifiable: Label, vararg modifiers: String) = modifiers.forEach { tw.writeHasModifier(modifiable, extractModifier(it)) } sealed class ExtractSupertypesMode { object Unbound : ExtractSupertypesMode() object Raw : ExtractSupertypesMode() data class Specialised(val typeArgs: List) : ExtractSupertypesMode() } /** * Extracts the supertypes of class `c`, either the unbound version, raw version or a * specialisation to particular type arguments, depending on the value of `mode`. `id` is the * label of this class or class instantiation. * * For example, for type `List` if `mode` `Specialised([String])` then we will extract the * supertypes of `List`, i.e. `Appendable` etc, or if `mode` is `Unbound` we * will extract `Appendable` where `E` is the type variable declared as `List`. Finally if * `mode` is `Raw` we will extract the raw type `Appendable`, represented in QL as * `Appendable<>`. * * Argument `inReceiverContext` will be passed onto the `useClassInstance` invocation for each * supertype. */ fun extractClassSupertypes( c: IrClass, id: Label, mode: ExtractSupertypesMode = ExtractSupertypesMode.Unbound, inReceiverContext: Boolean = false ) { extractClassSupertypes( c.superTypes, c.typeParameters, id, c.isInterfaceLike, mode, inReceiverContext ) } fun extractClassSupertypes( superTypes: List, typeParameters: List, id: Label, isInterface: Boolean, mode: ExtractSupertypesMode = ExtractSupertypesMode.Unbound, inReceiverContext: Boolean = false ) { // Note we only need to substitute type args here because it is illegal to directly extend a // type variable. // (For example, we can't have `class A : E`, but can have `class A : Comparable`) val subbedSupertypes = when (mode) { is ExtractSupertypesMode.Specialised -> { superTypes.map { it.substituteTypeArguments(typeParameters, mode.typeArgs) } } else -> superTypes } for (t in subbedSupertypes) { when (t) { is IrSimpleType -> { when (val owner = t.classifier.owner) { is IrClass -> { val typeArgs = if (t.arguments.isNotEmpty() && mode is ExtractSupertypesMode.Raw) null else t.arguments val l = useClassInstance(owner, typeArgs, inReceiverContext).typeResult.id if (isInterface || !owner.isInterfaceLike) { tw.writeExtendsReftype(id, l) } else { tw.writeImplInterface(id.cast(), l.cast()) } } else -> { logger.error( "Unexpected simple type supertype: " + t.javaClass + ": " + t.render() ) } } } else -> { logger.error("Unexpected supertype: " + t.javaClass + ": " + t.render()) } } } } fun useValueDeclaration(d: IrValueDeclaration): Label? = when (d) { is IrValueParameter -> useValueParameter(d, null) is IrVariable -> useVariable(d) else -> { logger.error("Unrecognised IrValueDeclaration: " + d.javaClass) null } } /** * Returns `t` with generic types replaced by raw types, and type parameters replaced by their * first bound. * * Note that `Array` is retained (with `T` itself erased) because these are expected to be * lowered to Java arrays, which are not generic. */ fun erase(t: IrType): IrType { if (t is IrSimpleType) { val classifier = t.classifier val owner = classifier.owner if (owner is IrTypeParameter) { return eraseTypeParameter(owner) } if (owner is IrClass) { if (t.isBoxedArrayCodeQL) { val elementType = t.getArrayElementTypeCodeQL(pluginContext.irBuiltIns) val erasedElementType = erase(elementType) return owner .typeWith(erasedElementType) .codeQlWithHasQuestionMark(t.isNullableCodeQL()) } return if (t.arguments.isNotEmpty()) t.addAnnotations(listOf(RawTypeAnnotation.annotationConstructor)) else t } } return t } private fun eraseTypeParameter(t: IrTypeParameter) = erase(t.superTypes[0]) fun getValueParameterLabel(parentId: Label?, idx: Int) = "@\"params;{$parentId};$idx\"" /** * Gets the label for `vp` in the context of function instance `parent`, or in that of its * declaring function if `parent` is null. */ fun getValueParameterLabel(vp: IrValueParameter, parent: Label?): String { val declarationParent = vp.parent val overriddenParentAttributes = (declarationParent as? IrFunction)?.let { (this as? KotlinFileExtractor)?.declarationStack?.findOverriddenAttributes(it) } val parentId = parent ?: overriddenParentAttributes?.id ?: useDeclarationParentOf(vp, false) val idxBase = overriddenParentAttributes?.valueParameters?.indexOf(vp) ?: parameterIndexExcludingReceivers(vp) val idxOffset = if ( declarationParent is IrFunction && declarationParent.extensionReceiverParameter != null ) // For extension functions increase the index to match what the java extractor sees: 1 else 0 val idx = idxBase + idxOffset if (idx < 0) { // We're not extracting this and this@TYPE parameters of functions: logger.error("Unexpected negative index for parameter") } return getValueParameterLabel(parentId, idx) } fun useValueParameter( vp: IrValueParameter, parent: Label? ): Label = tw.getLabelFor(getValueParameterLabel(vp, parent)) private fun isDirectlyExposableCompanionObjectField(f: IrField) = f.hasAnnotation(FqName("kotlin.jvm.JvmField")) || f.correspondingPropertySymbol?.owner?.let { it.isConst || it.isLateinit } ?: false private fun getFieldParent(f: IrField) = f.parentClassOrNull?.let { if (it.isCompanion && isDirectlyExposableCompanionObjectField(f)) it.parent else null } ?: f.parent fun isDirectlyExposedCompanionObjectField(f: IrField) = getFieldParent(f) != f.parent // Gets a field's corresponding property's extension receiver type, if any fun getExtensionReceiverType(f: IrField) = f.correspondingPropertySymbol?.owner?.let { (it.getter ?: it.setter)?.extensionReceiverParameter?.type } fun getFieldLabel(f: IrField): String { val parentId = useDeclarationParentOf(f, false) // Distinguish backing fields of properties based on their extension receiver type; // otherwise two extension properties declared in the same enclosing context will get // clashing trap labels. These are always private, so we can just make up a label without // worrying about their names as seen from Java. val extensionPropertyDiscriminator = getExtensionReceiverType(f)?.let { "extension;${useType(it).javaResult.id}" } ?: "" return "@\"field;{$parentId};$extensionPropertyDiscriminator${f.name.asString()}\"" } fun useField(f: IrField): Label = tw.getLabelFor(getFieldLabel(f)).also { extractFieldLaterIfExternalFileMember(f) } fun getPropertyLabel(p: IrProperty): String? { val parentId = useDeclarationParentOf(p, false) if (parentId == null) { return null } else { return getPropertyLabel(p, parentId, null) } } private fun getPropertyLabel( p: IrProperty, parentId: Label, classTypeArgsIncludingOuterClasses: List? ): String { val getter = p.getter val setter = p.setter val func = getter ?: setter val ext = func?.extensionReceiverParameter return if (ext == null) { "@\"property;{$parentId};${p.name.asString()}\"" } else { val returnType = getter?.returnType ?: setter?.valueParameters?.singleOrNull()?.type ?: pluginContext.irBuiltIns.unitType val typeParams = getFunctionTypeParameters(func) getFunctionLabel( p.parent, parentId, p.name.asString(), listOf(), returnType, ext.type, typeParams, classTypeArgsIncludingOuterClasses, overridesCollectionsMethod = false, javaSignature = null, addParameterWildcardsByDefault = false, prefix = "property" ) } } fun useProperty( p: IrProperty, parentId: Label, classTypeArgsIncludingOuterClasses: List? ) = tw.getLabelFor( getPropertyLabel(p, parentId, classTypeArgsIncludingOuterClasses) ) { extractPropertyLaterIfExternalFileMember(p) extractPrivateSpecialisedDeclaration(p, classTypeArgsIncludingOuterClasses) } fun getEnumEntryLabel(ee: IrEnumEntry): String { val parentId = useDeclarationParentOf(ee, false) return "@\"field;{$parentId};${ee.name.asString()}\"" } fun useEnumEntry(ee: IrEnumEntry): Label = tw.getLabelFor(getEnumEntryLabel(ee)) fun getTypeAliasLabel(ta: IrTypeAlias): String { val parentId = useDeclarationParentOf(ta, true) return "@\"type_alias;{$parentId};${ta.name.asString()}\"" } fun useTypeAlias(ta: IrTypeAlias): Label = tw.getLabelFor(getTypeAliasLabel(ta)) fun useVariable(v: IrVariable): Label { return tw.getVariableLabelFor(v) } }