package com.github.codeql import com.github.codeql.comments.CommentExtractorLighterAST import com.github.codeql.comments.CommentExtractorPSI import com.github.codeql.utils.* import com.github.codeql.utils.versions.* import com.semmle.extractor.java.OdasaOutput import java.io.Closeable import java.util.* import kotlin.collections.ArrayList import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.pop import org.jetbrains.kotlin.builtins.functions.BuiltInFunctionArity import org.jetbrains.kotlin.config.JvmAnalysisFlags import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.java.JavaVisibilities import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget import org.jetbrains.kotlin.ir.builders.declarations.* import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.lazy.IrLazyFunction import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.types.classFqName import org.jetbrains.kotlin.ir.types.classifierOrFail 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.typeOrNull import org.jetbrains.kotlin.ir.types.typeWith import org.jetbrains.kotlin.ir.types.typeWithArguments 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.makeTypeProjection import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.load.java.JvmAnnotationNames import org.jetbrains.kotlin.load.java.NOT_NULL_ANNOTATIONS import org.jetbrains.kotlin.load.java.NULLABLE_ANNOTATIONS import org.jetbrains.kotlin.load.java.sources.JavaSourceElement import org.jetbrains.kotlin.load.java.structure.JavaAnnotation import org.jetbrains.kotlin.load.java.structure.JavaClass import org.jetbrains.kotlin.load.java.structure.JavaConstructor import org.jetbrains.kotlin.load.java.structure.JavaMethod import org.jetbrains.kotlin.load.java.structure.JavaTypeParameter import org.jetbrains.kotlin.load.java.structure.JavaTypeParameterListOwner import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull open class KotlinFileExtractor( override val logger: FileLogger, override val tw: FileTrapWriter, val linesOfCode: LinesOfCode?, val filePath: String, dependencyCollector: OdasaOutput.TrapFileManager?, externalClassExtractor: ExternalDeclExtractor, primitiveTypeMapping: PrimitiveTypeMapping, pluginContext: IrPluginContext, val declarationStack: DeclarationStack, globalExtensionState: KotlinExtractorGlobalState, ) : KotlinUsesExtractor( logger, tw, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, globalExtensionState ) { val usesK2 = usesK2(pluginContext) val metaAnnotationSupport = MetaAnnotationSupport(logger, pluginContext, this) private inline fun with(kind: String, element: IrElement, f: () -> T): T { val name = when (element) { is IrFile -> element.name is IrDeclarationWithName -> element.name.asString() else -> "" } val loc = tw.getLocationString(element) val context = logger.loggerBase.extractorContextStack context.push(ExtractorContext(kind, element, name, loc)) try { val depth = context.size val depthDescription = "${"-".repeat(depth)} (${depth.toString()})" logger.trace("$depthDescription: Starting a $kind ($name) at $loc") val result = f() logger.trace("$depthDescription: Finished a $kind ($name) at $loc") return result } catch (exception: Exception) { throw Exception("While extracting a $kind ($name) at $loc", exception) } finally { context.pop() } } fun extractFileContents(file: IrFile, id: Label) { with("file", file) { val locId = tw.getWholeFileLocation() val pkg = file.packageFqName.asString() val pkgId = extractPackage(pkg) tw.writeHasLocation(id, locId) tw.writeCupackage(id, pkgId) val exceptionOnFile = System.getenv("CODEQL_KOTLIN_INTERNAL_EXCEPTION_WHILE_EXTRACTING_FILE") if (exceptionOnFile != null) { if (exceptionOnFile.lowercase() == file.name.lowercase()) { throw Exception("Internal testing exception") } } file.declarations.forEach { extractDeclaration( it, extractPrivateMembers = true, extractFunctionBodies = true, extractAnnotations = true ) if (it is IrProperty || it is IrField || it is IrFunction) { externalClassExtractor.writeStubTrapFile(it, getTrapFileSignature(it)) } } extractStaticInitializer(file, { extractFileClass(file) }) val psiCommentsExtracted = CommentExtractorPSI(this, file, tw.fileId).extract() val lighterAstCommentsExtracted = CommentExtractorLighterAST(this, file, tw.fileId).extract() if (psiCommentsExtracted == lighterAstCommentsExtracted) { if (psiCommentsExtracted) { logger.warnElement( "Found both PSI and LighterAST comments in ${file.path}.", file ) } else { logger.warnElement("Comments could not be processed in ${file.path}.", file) } } if (!declarationStack.isEmpty()) { logger.errorElement( "Declaration stack is not empty after processing the file", file ) } linesOfCode?.linesOfCodeInFile(id) externalClassExtractor.writeStubTrapFile(file) } } private fun javaBinaryDeclaresMethod(c: IrClass, name: String) = ((c.source as? JavaSourceElement)?.javaElement as? BinaryJavaClass)?.methods?.any { it.name.asString() == name } private fun isJavaBinaryDeclaration(f: IrFunction) = f.parentClassOrNull?.let { javaBinaryDeclaresMethod(it, f.name.asString()) } ?: false private fun isJavaBinaryObjectMethodRedeclaration(d: IrDeclaration) = when (d) { is IrFunction -> when (d.name.asString()) { "toString" -> d.valueParameters.isEmpty() "hashCode" -> d.valueParameters.isEmpty() "equals" -> d.valueParameters.singleOrNull()?.type?.isNullableAny() ?: false else -> false } && isJavaBinaryDeclaration(d) else -> false } private fun FunctionDescriptor.tryIsHiddenToOvercomeSignatureClash(d: IrFunction): Boolean { // `org.jetbrains.kotlin.ir.descriptors.IrBasedClassConstructorDescriptor.isHiddenToOvercomeSignatureClash` // throws one exception or other in Kotlin 2, depending on the version. // TODO: We need a replacement for this for Kotlin 2 try { return this.isHiddenToOvercomeSignatureClash } catch (e: NotImplementedError) { if (!usesK2) { logger.warnElement("Couldn't query if element is fake, deciding it's not.", d, e) } return false } catch (e: IllegalStateException) { if (!usesK2) { logger.warnElement("Couldn't query if element is fake, deciding it's not.", d, e) } return false } } @OptIn(ObsoleteDescriptorBasedAPI::class) fun isFake(d: IrDeclarationWithVisibility): Boolean { val hasFakeVisibility = d.visibility.let { it is DelegatedDescriptorVisibility && it.delegate == Visibilities.InvisibleFake } || d.isFakeOverride if (hasFakeVisibility && !isJavaBinaryObjectMethodRedeclaration(d)) return true return (d as? IrFunction)?.descriptor?.tryIsHiddenToOvercomeSignatureClash(d) == true } private fun shouldExtractDecl(declaration: IrDeclaration, extractPrivateMembers: Boolean) = extractPrivateMembers || !isPrivate(declaration) fun extractDeclaration( declaration: IrDeclaration, extractPrivateMembers: Boolean, extractFunctionBodies: Boolean, extractAnnotations: Boolean ) { with("declaration", declaration) { if (!shouldExtractDecl(declaration, extractPrivateMembers)) return when (declaration) { is IrClass -> { if (isExternalDeclaration(declaration)) { extractExternalClassLater(declaration) } else { extractClassSource( declaration, extractDeclarations = true, extractStaticInitializer = true, extractPrivateMembers = extractPrivateMembers, extractFunctionBodies = extractFunctionBodies ) } } is IrFunction -> { val parentId = useDeclarationParentOf(declaration, false)?.cast() if (parentId != null) { extractFunction( declaration, parentId, extractBody = extractFunctionBodies, extractMethodAndParameterTypeAccesses = extractFunctionBodies, extractAnnotations = extractAnnotations, null, listOf() ) } Unit } is IrAnonymousInitializer -> { // Leaving this intentionally empty. init blocks are extracted during class // extraction. } is IrProperty -> { val parentId = useDeclarationParentOf(declaration, false)?.cast() if (parentId != null) { extractProperty( declaration, parentId, extractBackingField = true, extractFunctionBodies = extractFunctionBodies, extractPrivateMembers = extractPrivateMembers, extractAnnotations = extractAnnotations, null, listOf() ) } Unit } is IrEnumEntry -> { val parentId = useDeclarationParentOf(declaration, false)?.cast() if (parentId != null) { extractEnumEntry( declaration, parentId, extractPrivateMembers, extractFunctionBodies ) } Unit } is IrField -> { val parentId = useDeclarationParentOf(declaration, false)?.cast() if (parentId != null) { // For consistency with the Java extractor, enum entries get type accesses // only if we're extracting from .kt source (i.e., when // `extractFunctionBodies` is set) extractField( declaration, parentId, extractAnnotationEnumTypeAccesses = extractFunctionBodies ) } Unit } is IrTypeAlias -> extractTypeAlias(declaration) else -> logger.errorElement( "Unrecognised IrDeclaration: " + declaration.javaClass, declaration ) } } } private fun extractTypeParameter( tp: IrTypeParameter, apparentIndex: Int, javaTypeParameter: JavaTypeParameter? ): Label? { with("type parameter", tp) { val parentId = getTypeParameterParentLabel(tp) ?: return null val id = tw.getLabelFor(getTypeParameterLabel(tp)) // Note apparentIndex does not necessarily equal `tp.index`, because at least // constructor type parameters // have indices offset from the type parameters of the constructed class (i.e. the // parameter S of // `class Generic { public Generic(T t, S s) { ... } }` will have `tp.index` 1, // not 0). tw.writeTypeVars(id, tp.name.asString(), apparentIndex, parentId) val locId = tw.getLocation(tp) tw.writeHasLocation(id, locId) // Annoyingly, we have no obvious way to pair up the bounds of an IrTypeParameter and a // JavaTypeParameter // because JavaTypeParameter provides a Collection not an ordered list, so we can only // do our best here: fun tryGetJavaBound(idx: Int) = when (tp.superTypes.size) { 1 -> javaTypeParameter?.upperBounds?.singleOrNull() else -> (javaTypeParameter?.upperBounds as? List)?.getOrNull(idx) } tp.superTypes.forEachIndexed { boundIdx, bound -> if (!(bound.isAny() || bound.isNullableAny())) { tw.getLabelFor("@\"bound;$boundIdx;{$id}\"") { // Note we don't look for @JvmSuppressWildcards here because it doesn't seem // to have any impact // on kotlinc adding wildcards to type parameter bounds. val boundWithWildcards = addJavaLoweringWildcards(bound, true, tryGetJavaBound(tp.index)) tw.writeTypeBounds( it, useType(boundWithWildcards).javaResult.id.cast(), boundIdx, id ) } } } if (tp.isReified) { addModifiers(id, "reified") } if (tp.variance == Variance.IN_VARIANCE) { addModifiers(id, "in") } else if (tp.variance == Variance.OUT_VARIANCE) { addModifiers(id, "out") } // extractAnnotations(tp, id) // TODO: introduce annotations once they can be disambiguated from bounds, which are // also child expressions. return id } } private fun extractVisibility( elementForLocation: IrElement, id: Label, v: DescriptorVisibility ) { with("visibility", elementForLocation) { when (v) { DescriptorVisibilities.PRIVATE -> addModifiers(id, "private") DescriptorVisibilities.PRIVATE_TO_THIS -> addModifiers(id, "private") DescriptorVisibilities.PROTECTED -> addModifiers(id, "protected") DescriptorVisibilities.PUBLIC -> addModifiers(id, "public") DescriptorVisibilities.INTERNAL -> addModifiers(id, "internal") DescriptorVisibilities.LOCAL -> if (elementForLocation is IrFunction && elementForLocation.isLocalFunction()) { // The containing class is `private`. addModifiers(id, "public") } else { addVisibilityModifierToLocalOrAnonymousClass(id) } is DelegatedDescriptorVisibility -> { when (v.delegate) { JavaVisibilities.ProtectedStaticVisibility -> { addModifiers(id, "protected") addModifiers(id, "static") } JavaVisibilities.PackageVisibility -> { // default java visibility (top level) } JavaVisibilities.ProtectedAndPackage -> { addModifiers(id, "protected") } else -> logger.errorElement( "Unexpected delegated visibility: $v", elementForLocation ) } } else -> logger.errorElement("Unexpected visibility: $v", elementForLocation) } } } private fun extractClassModifiers(c: IrClass, id: Label) { with("class modifiers", c) { when (c.modality) { Modality.FINAL -> addModifiers(id, "final") Modality.SEALED -> addModifiers(id, "sealed") Modality.OPEN -> {} // This is the default Modality.ABSTRACT -> addModifiers(id, "abstract") else -> logger.errorElement("Unexpected class modality: ${c.modality}", c) } extractVisibility(c, id, c.visibility) } } fun extractClassInstance( classLabel: Label, c: IrClass, argsIncludingOuterClasses: List?, shouldExtractOutline: Boolean, shouldExtractDetails: Boolean ) { DeclarationStackAdjuster(c).use { if (shouldExtractOutline) { extractClassWithoutMembers(c, argsIncludingOuterClasses) } if (shouldExtractDetails) { val supertypeMode = if (argsIncludingOuterClasses == null) ExtractSupertypesMode.Raw else ExtractSupertypesMode.Specialised(argsIncludingOuterClasses) extractClassSupertypes(c, classLabel, supertypeMode, true) extractNonPrivateMemberPrototypes(c, argsIncludingOuterClasses, classLabel) } } } // `argsIncludingOuterClasses` can be null to describe a raw generic type. // For non-generic types it will be zero-length list. private fun extractClassWithoutMembers( c: IrClass, argsIncludingOuterClasses: List? ): Label { with("class instance", c) { if (argsIncludingOuterClasses?.isEmpty() == true) { logger.error("Instance without type arguments: " + c.name.asString()) } val classLabelResults = getClassLabel(c, argsIncludingOuterClasses) val id = tw.getLabelFor(classLabelResults.classLabel) val pkg = c.packageFqName?.asString() ?: "" val cls = classLabelResults.shortName val pkgId = extractPackage(pkg) // TODO: There's lots of duplication between this and extractClassSource. // Can we share it? val sourceId = useClassSource(c) tw.writeClasses_or_interfaces(id, cls, pkgId, sourceId) if (c.isInterfaceLike) { tw.writeIsInterface(id) } else { val kind = c.kind if (kind == ClassKind.ENUM_CLASS) { tw.writeIsEnumType(id) } else if ( kind != ClassKind.CLASS && kind != ClassKind.OBJECT && kind != ClassKind.ENUM_ENTRY ) { logger.errorElement("Unrecognised class kind $kind", c) } } val typeArgs = removeOuterClassTypeArgs(c, argsIncludingOuterClasses) if (typeArgs != null) { // From 1.9, the list might change when we call erase, // so we make a copy that it is safe to iterate over. val typeArgsCopy = typeArgs.toList() for ((idx, arg) in typeArgsCopy.withIndex()) { val argId = getTypeArgumentLabel(arg).id tw.writeTypeArgs(argId, idx, id) } tw.writeIsParameterized(id) } else { tw.writeIsRaw(id) } extractClassModifiers(c, id) extractClassSupertypes( c, id, if (argsIncludingOuterClasses == null) ExtractSupertypesMode.Raw else ExtractSupertypesMode.Specialised(argsIncludingOuterClasses) ) val locId = getLocation(c, argsIncludingOuterClasses) tw.writeHasLocation(id, locId) // Extract the outer <-> inner class relationship, passing on any type arguments in // excess to this class' parameters if this is an inner class. // For example, in `class Outer { inner class Inner { } }`, `Inner` // nests within `Outer` and raw `Inner<>` within `Outer<>`, // but for a similar non-`inner` (in Java terms, static nested) class both `Inner` // and `Inner<>` nest within the unbound type `Outer`. val useBoundOuterType = (c.isInner || c.isLocal) && (c.parents.firstNotNullOfOrNull { when (it) { is IrClass -> when { it.typeParameters.isNotEmpty() -> true // Type parameters visible to this class -- extract an // enclosing bound or raw type. !(it.isInner || it.isLocal) -> false // No type parameters seen yet, and this is a static // class -- extract an enclosing unbound type. else -> null // No type parameters seen here, but may be visible // enclosing type parameters; keep searching. } else -> null // Look through enclosing non-class entities (this may need to // change) } } ?: false) extractEnclosingClass( c.parent, id, c, locId, if (useBoundOuterType) argsIncludingOuterClasses?.drop(c.typeParameters.size) else listOf() ) return id } } private fun getLocation( decl: IrDeclaration, typeArgs: List? ): Label { return if (typeArgs != null && typeArgs.isNotEmpty()) { val binaryPath = getIrDeclarationBinaryPath(decl) if (binaryPath == null) { tw.getLocation(decl) } else { val newTrapWriter = tw.makeFileTrapWriter(binaryPath, true) newTrapWriter.getWholeFileLocation() } } else { tw.getLocation(decl) } } private fun makeTypeParamSubstitution( c: IrClass, argsIncludingOuterClasses: List? ) = when (argsIncludingOuterClasses) { null -> { x: IrType, _: TypeContext, _: IrPluginContext -> x.toRawType() } else -> makeGenericSubstitutionFunction(c, argsIncludingOuterClasses) } fun extractDeclarationPrototype( d: IrDeclaration, parentId: Label, argsIncludingOuterClasses: List?, typeParamSubstitutionQ: TypeSubstitution? = null ) { val typeParamSubstitution = typeParamSubstitutionQ ?: when (val parent = d.parent) { is IrClass -> makeTypeParamSubstitution(parent, argsIncludingOuterClasses) else -> { logger.warnElement("Unable to extract prototype of local declaration", d) return } } when (d) { is IrFunction -> extractFunction( d, parentId, extractBody = false, extractMethodAndParameterTypeAccesses = false, extractAnnotations = false, typeParamSubstitution, argsIncludingOuterClasses ) is IrProperty -> extractProperty( d, parentId, extractBackingField = false, extractFunctionBodies = false, extractPrivateMembers = false, extractAnnotations = false, typeParamSubstitution, argsIncludingOuterClasses ) else -> {} } } // `argsIncludingOuterClasses` can be null to describe a raw generic type. // For non-generic types it will be zero-length list. private fun extractNonPrivateMemberPrototypes( c: IrClass, argsIncludingOuterClasses: List?, id: Label ) { with("member prototypes", c) { val typeParamSubstitution = makeTypeParamSubstitution(c, argsIncludingOuterClasses) c.declarations.map { if (shouldExtractDecl(it, false)) { extractDeclarationPrototype( it, id, argsIncludingOuterClasses, typeParamSubstitution ) } } } } private fun extractLocalTypeDeclStmt( c: IrClass, callable: Label, parent: Label, idx: Int ) { val id = extractClassSource( c, extractDeclarations = true, extractStaticInitializer = true, extractPrivateMembers = true, extractFunctionBodies = true ) extractLocalTypeDeclStmt(id, c, callable, parent, idx) } private fun extractLocalTypeDeclStmt( id: Label, locElement: IrElement, callable: Label, parent: Label, idx: Int ) { val stmtId = tw.getFreshIdLabel() tw.writeStmts_localtypedeclstmt(stmtId, parent, idx, callable) tw.writeIsLocalClassOrInterface(id, stmtId) val locId = tw.getLocation(locElement) tw.writeHasLocation(stmtId, locId) } private fun extractObinitFunction(c: IrClass, parentId: Label) { // add method: val obinitLabel = getObinitLabel(c, parentId) val obinitId = tw.getLabelFor(obinitLabel) val returnType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN) tw.writeMethods( obinitId, "", "()", returnType.javaResult.id, parentId, obinitId ) tw.writeMethodsKotlinType(obinitId, returnType.kotlinResult.id) val locId = tw.getLocation(c) tw.writeHasLocation(obinitId, locId) addModifiers(obinitId, "private") // add body: val blockId = extractBlockBody(obinitId, locId) extractDeclInitializers(c.declarations, false) { Pair(blockId, obinitId) } } private val javaLangDeprecated by lazy { referenceExternalClass("java.lang.Deprecated") } private val javaLangDeprecatedConstructor by lazy { javaLangDeprecated?.constructors?.singleOrNull() } private fun replaceKotlinDeprecatedAnnotation( annotations: List ): List { val shouldReplace = annotations.any { (it.type as? IrSimpleType)?.classFqName?.asString() == "kotlin.Deprecated" } && annotations.none { it.type.classOrNull == javaLangDeprecated?.symbol } val jldConstructor = javaLangDeprecatedConstructor if (!shouldReplace || jldConstructor == null) return annotations return annotations.filter { (it.type as? IrSimpleType)?.classFqName?.asString() != "kotlin.Deprecated" } + // Note we lose any arguments to @java.lang.Deprecated that were written in source. IrConstructorCallImpl.fromSymbolOwner( UNDEFINED_OFFSET, UNDEFINED_OFFSET, jldConstructor.returnType, jldConstructor.symbol, 0 ) } private fun extractAnnotations( c: IrAnnotationContainer, annotations: List, parent: Label, extractEnumTypeAccesses: Boolean ) { val origin = (c as? IrDeclaration)?.origin ?: run { logger.warn("Unexpected annotation container: $c") return } val replacedAnnotations = if (origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB) replaceKotlinDeprecatedAnnotation(annotations) else annotations val groupedAnnotations = metaAnnotationSupport.groupRepeatableAnnotations(replacedAnnotations) for ((idx, constructorCall: IrConstructorCall) in groupedAnnotations.sortedBy { v -> v.type.classFqName?.asString() }.withIndex()) { extractAnnotation(constructorCall, parent, idx, extractEnumTypeAccesses) } } private fun extractAnnotations( c: IrAnnotationContainer, parent: Label, extractEnumTypeAccesses: Boolean ) { extractAnnotations(c, c.annotations, parent, extractEnumTypeAccesses) } private fun extractAnnotation( constructorCall: IrConstructorCall, parent: Label, idx: Int, extractEnumTypeAccesses: Boolean, contextLabel: String? = null ): Label { // Erase the type here because the JVM lowering erases the annotation type, and so the Java // extractor will see it in erased form. val t = useType(erase(constructorCall.type)) val annotationContextLabel = contextLabel ?: "{${t.javaResult.id}}" val id = tw.getLabelFor("@\"annotation;{$parent};$annotationContextLabel\"") tw.writeExprs_declannotation(id, t.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, t.kotlinResult.id) val locId = tw.getLocation(constructorCall) tw.writeHasLocation(id, locId) for (i in 0 until constructorCall.valueArgumentsCount) { val param = constructorCall.symbol.owner.valueParameters[i] val prop = constructorCall.symbol.owner.parentAsClass.declarations .filterIsInstance() .first { it.name == param.name } val v = constructorCall.getValueArgument(i) ?: param.defaultValue?.expression val getter = prop.getter if (getter == null) { logger.warnElement("Expected annotation property to define a getter", prop) } else { val getterId = useFunction(getter) if (getterId == null) { logger.errorElement("Couldn't get ID for getter", getter) } else { val exprId = extractAnnotationValueExpression( v, id, i, "{$getterId}", getter.returnType, extractEnumTypeAccesses ) if (exprId != null) { tw.writeAnnotValue(id, getterId, exprId) } } } } return id } private fun extractAnnotationValueExpression( v: IrExpression?, parent: Label, idx: Int, contextLabel: String, contextType: IrType?, extractEnumTypeAccesses: Boolean ): Label? { fun exprId() = tw.getLabelFor("@\"annotationExpr;{$parent};$idx\"") return when (v) { is CodeQLIrConst<*> -> { extractConstant(v, parent, idx, null, null, overrideId = exprId()) } is IrGetEnumValue -> { extractEnumValue( v, parent, idx, null, null, extractTypeAccess = extractEnumTypeAccesses, overrideId = exprId() ) } is IrClassReference -> { val classRefId = exprId() val typeAccessId = tw.getLabelFor("@\"annotationExpr;{$classRefId};0\"") extractClassReference( v, parent, idx, null, null, overrideId = classRefId, typeAccessOverrideId = typeAccessId, useJavaLangClassType = true ) } is IrConstructorCall -> { extractAnnotation(v, parent, idx, extractEnumTypeAccesses, contextLabel) } is IrVararg -> { tw.getLabelFor("@\"annotationarray;{$parent};$contextLabel\"").also { arrayId -> // Use the context type (i.e., the type the annotation expects, not the actual // type of the array) // because the Java extractor fills in array types using the same technique. // These should only // differ for generic annotations. if (contextType == null) { logger.warnElement( "Expected an annotation array to have an enclosing context", v ) } else { val type = useType(kClassToJavaClass(contextType)) tw.writeExprs_arrayinit(arrayId, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(arrayId, type.kotlinResult.id) tw.writeHasLocation(arrayId, tw.getLocation(v)) v.elements.forEachIndexed { index, irVarargElement -> run { val argExpr = when (irVarargElement) { is IrExpression -> irVarargElement is IrSpreadElement -> irVarargElement.expression else -> { logger.errorElement( "Unrecognised IrVarargElement: " + irVarargElement.javaClass, irVarargElement ) null } } extractAnnotationValueExpression( argExpr, arrayId, index, "child;$index", null, extractEnumTypeAccesses ) } } } } } // is IrErrorExpression // null // Note: emitting an ErrorExpr here would induce an inconsistency if this annotation is // later seen from source or by the Java extractor, // in both of which cases the real value will get extracted. else -> null } } fun extractClassSource( c: IrClass, extractDeclarations: Boolean, extractStaticInitializer: Boolean, extractPrivateMembers: Boolean, extractFunctionBodies: Boolean ): Label { with("class source", c) { DeclarationStackAdjuster(c).use { val id = useClassSource(c) val pkg = c.packageFqName?.asString() ?: "" val cls = if (c.isAnonymousObject) "" else c.name.asString() val pkgId = extractPackage(pkg) tw.writeClasses_or_interfaces(id, cls, pkgId, id) if (c.isInterfaceLike) { tw.writeIsInterface(id) if (c.kind == ClassKind.ANNOTATION_CLASS) { tw.writeIsAnnotType(id) } } else { val kind = c.kind if (kind == ClassKind.ENUM_CLASS) { tw.writeIsEnumType(id) } else if ( kind != ClassKind.CLASS && kind != ClassKind.OBJECT && kind != ClassKind.ENUM_ENTRY ) { logger.warnElement("Unrecognised class kind $kind", c) } if (c.origin == IrDeclarationOrigin.FILE_CLASS) { tw.writeFile_class(id) } if (c.isData) { tw.writeKtDataClasses(id) } } val locId = tw.getLocation(c) tw.writeHasLocation(id, locId) extractEnclosingClass(c.parent, id, c, locId, listOf()) val javaClass = (c.source as? JavaSourceElement)?.javaElement as? JavaClass c.typeParameters.mapIndexed { idx, param -> extractTypeParameter(param, idx, javaClass?.typeParameters?.getOrNull(idx)) } if (extractDeclarations) { if (c.kind == ClassKind.ANNOTATION_CLASS) { c.declarations.filterIsInstance().forEach { val getter = it.getter if (getter == null) { logger.warnElement( "Expected an annotation property to have a getter", it ) } else { extractFunction( getter, id, extractBody = false, extractMethodAndParameterTypeAccesses = extractFunctionBodies, extractAnnotations = true, null, listOf() ) ?.also { functionLabel -> tw.writeIsAnnotElem(functionLabel.cast()) } } } } else { try { c.declarations.forEach { extractDeclaration( it, extractPrivateMembers = extractPrivateMembers, extractFunctionBodies = extractFunctionBodies, extractAnnotations = true ) } if (extractStaticInitializer) extractStaticInitializer(c, { id }) extractJvmStaticProxyMethods( c, id, extractPrivateMembers, extractFunctionBodies ) } catch (e: IllegalArgumentException) { // A Kotlin bug causes this to throw: https://youtrack.jetbrains.com/issue/KT-63847/K2-IllegalStateException-IrFieldPublicSymbolImpl-for-java.time-Clock.OffsetClock.offset0-is-already-bound // TODO: This should either be removed or log something, once the bug is fixed } } } if (c.isNonCompanionObject) { // For `object MyObject { ... }`, the .class has an // automatically-generated `public static final MyObject INSTANCE` // field that may be referenced from Java code, and is used in our // IrGetObjectValue support. We therefore need to fabricate it // here. val instance = useObjectClassInstance(c) val type = useSimpleTypeClass(c, emptyList(), false) tw.writeFields(instance.id, instance.name, type.javaResult.id, id) tw.writeFieldsKotlinType(instance.id, type.kotlinResult.id) tw.writeHasLocation(instance.id, locId) addModifiers(instance.id, "public", "static", "final") tw.writeClass_object(id, instance.id) } if (c.isObject) { addModifiers(id, "static") } if (extractFunctionBodies && needsObinitFunction(c)) { extractObinitFunction(c, id) } extractClassModifiers(c, id) extractClassSupertypes( c, id, inReceiverContext = true ) // inReceiverContext = true is specified to force extraction of member prototypes // of base types linesOfCode?.linesOfCodeInDeclaration(c, id) val additionalAnnotations = if ( c.kind == ClassKind.ANNOTATION_CLASS && c.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB ) metaAnnotationSupport.generateJavaMetaAnnotations(c, extractFunctionBodies) else listOf() extractAnnotations( c, c.annotations + additionalAnnotations, id, extractFunctionBodies ) if (extractFunctionBodies && !c.isAnonymousObject && !c.isLocal) externalClassExtractor.writeStubTrapFile(c) return id } } } val jvmStaticFqName = FqName("kotlin.jvm.JvmStatic") private fun extractJvmStaticProxyMethods( c: IrClass, classId: Label, extractPrivateMembers: Boolean, extractFunctionBodies: Boolean ) { // Add synthetic forwarders for any JvmStatic methods or properties: val companionObject = c.companionObject() ?: return val cType = c.typeWith() val companionType = companionObject.typeWith() fun makeProxyFunction(f: IrFunction) { // Emit a function in class `c` that delegates to the same function defined on // `c.CompanionInstance`. val proxyFunctionId = tw.getLabelFor(getFunctionLabel(f, classId, listOf())) // We extract the function prototype with its ID overridden to belong to `c` not the // companion object, // but suppress outputting the body, which we will replace with a delegating call below. forceExtractFunction( f, classId, extractBody = false, extractMethodAndParameterTypeAccesses = extractFunctionBodies, extractAnnotations = false, typeSubstitution = null, classTypeArgsIncludingOuterClasses = listOf(), extractOrigin = false, OverriddenFunctionAttributes(id = proxyFunctionId) ) addModifiers(proxyFunctionId, "static") tw.writeCompiler_generated( proxyFunctionId, CompilerGeneratedKinds.JVMSTATIC_PROXY_METHOD.kind ) if (extractFunctionBodies) { val realFunctionLocId = tw.getLocation(f) extractExpressionBody(proxyFunctionId, realFunctionLocId).also { returnId -> extractRawMethodAccess( f, realFunctionLocId, f.returnType, proxyFunctionId, returnId, 0, returnId, f.valueParameters.size, { argParent, idxOffset -> f.valueParameters.forEachIndexed { idx, param -> val syntheticParamId = useValueParameter(param, proxyFunctionId) extractVariableAccess( syntheticParamId, param.type, realFunctionLocId, argParent, idxOffset + idx, proxyFunctionId, returnId ) } }, companionType, { callId -> val companionField = useCompanionObjectClassInstance(companionObject)?.id extractVariableAccess( companionField, companionType, realFunctionLocId, callId, -1, proxyFunctionId, returnId ) .also { varAccessId -> extractTypeAccessRecursive( cType, realFunctionLocId, varAccessId, -1, proxyFunctionId, returnId ) } }, null ) } } } companionObject.declarations.forEach { if (shouldExtractDecl(it, extractPrivateMembers)) { val wholeDeclAnnotated = it.hasAnnotation(jvmStaticFqName) when (it) { is IrFunction -> { if (wholeDeclAnnotated) { makeProxyFunction(it) if (it.hasAnnotation(jvmOverloadsFqName)) { extractGeneratedOverloads( it, classId, classId, extractFunctionBodies, extractMethodAndParameterTypeAccesses = extractFunctionBodies, typeSubstitution = null, classTypeArgsIncludingOuterClasses = listOf() ) } } } is IrProperty -> { it.getter?.let { getter -> if (wholeDeclAnnotated || getter.hasAnnotation(jvmStaticFqName)) makeProxyFunction(getter) } it.setter?.let { setter -> if (wholeDeclAnnotated || setter.hasAnnotation(jvmStaticFqName)) makeProxyFunction(setter) } } } } } } /** * This function traverses the declaration-parent hierarchy upwards, and retrieves the enclosing * class of a class to extract the `enclInReftype` relation. Additionally, it extracts a * companion field for a companion object into its parent class. * * Note that the nested class can also be a local class declared inside a function, so the * upwards traversal is skipping the non-class parents. Also, in some cases the file class is * the enclosing one, which has no IR representation. */ private fun extractEnclosingClass( declarationParent: IrDeclarationParent, // The declaration parent of the element for which we are // extracting the enclosing class innerId: Label, // ID of the inner class innerClass: IrClass?, // The inner class, if available. It's not available if the enclosing class of // a generated class is being extracted innerLocId: Label, // Location of the inner class parentClassTypeArguments: List< IrTypeArgument >? // Type arguments of the parent class. If `parentClassTypeArguments` is null, the // parent class is a raw type ) { with("enclosing class", declarationParent) { var parent: IrDeclarationParent? = declarationParent while (parent != null) { if (parent is IrClass) { val parentId = useClassInstance(parent, parentClassTypeArguments).typeResult.id tw.writeEnclInReftype(innerId, parentId) if (innerClass != null && innerClass.isCompanion) { // If we are a companion then our parent has a // public static final ParentClass$CompanionObjectClass // CompanionObjectName; // that we need to fabricate here val instance = useCompanionObjectClassInstance(innerClass) if (instance != null) { val type = useSimpleTypeClass(innerClass, emptyList(), false) tw.writeFields( instance.id, instance.name, type.javaResult.id, parentId ) tw.writeFieldsKotlinType(instance.id, type.kotlinResult.id) tw.writeHasLocation(instance.id, innerLocId) addModifiers(instance.id, "public", "static", "final") tw.writeType_companion_object(parentId, instance.id, innerId) } } break } else if (parent is IrFile) { if (innerClass != null && !innerClass.isLocal) { // We don't have to extract file class containers for classes except for // local classes break } if (this.filePath != parent.path) { logger.error("Unexpected file parent found") } val fileId = extractFileClass(parent) tw.writeEnclInReftype(innerId, fileId) break } parent = (parent as? IrDeclaration)?.parent } } } private data class FieldResult(val id: Label, val name: String) private fun useCompanionObjectClassInstance(c: IrClass): FieldResult? { val parent = c.parent if (!c.isCompanion) { logger.error("Using companion instance for non-companion class") return null } else if (parent !is IrClass) { logger.error("Using companion instance for non-companion class") return null } else { val parentId = useClassInstance(parent, listOf()).typeResult.id val instanceName = c.name.asString() val instanceLabel = "@\"field;{$parentId};$instanceName\"" val instanceId: Label = tw.getLabelFor(instanceLabel) return FieldResult(instanceId, instanceName) } } private fun useObjectClassInstance(c: IrClass): FieldResult { if (!c.isNonCompanionObject) { logger.error("Using instance for non-object class") } val classId = useClassInstance(c, listOf()).typeResult.id val instanceName = "INSTANCE" val instanceLabel = "@\"field;{$classId};$instanceName\"" val instanceId: Label = tw.getLabelFor(instanceLabel) return FieldResult(instanceId, instanceName) } @OptIn(ObsoleteDescriptorBasedAPI::class) private fun hasSynthesizedParameterNames(f: IrFunction) = f.descriptor.hasSynthesizedParameterNames() private fun extractValueParameter( vp: IrValueParameter, parent: Label, idx: Int, typeSubstitution: TypeSubstitution?, parentSourceDeclaration: Label, classTypeArgsIncludingOuterClasses: List?, extractTypeAccess: Boolean, locOverride: Label? = null ): TypeResults { with("value parameter", vp) { val location = locOverride ?: getLocation(vp, classTypeArgsIncludingOuterClasses) val maybeAlteredType = (vp.parent as? IrFunction)?.let { if (overridesCollectionsMethodWithAlteredParameterTypes(it)) eraseCollectionsMethodParameterType(vp.type, it.name.asString(), idx) else if ( (vp.parent as? IrConstructor)?.parentClassOrNull?.kind == ClassKind.ANNOTATION_CLASS ) kClassToJavaClass(vp.type) else null } ?: vp.type val javaType = (vp.parent as? IrFunction)?.let { getJavaCallable(it)?.let { jCallable -> getJavaValueParameterType(jCallable, idx) } } val typeWithWildcards = addJavaLoweringWildcards( maybeAlteredType, !getInnermostWildcardSupppressionAnnotation(vp), javaType ) val substitutedType = typeSubstitution?.let { it(typeWithWildcards, TypeContext.OTHER, pluginContext) } ?: typeWithWildcards val id = useValueParameter(vp, parent) if (extractTypeAccess) { extractTypeAccessRecursive(substitutedType, location, id, -1) } val syntheticParameterNames = isUnderscoreParameter(vp) || ((vp.parent as? IrFunction)?.let { hasSynthesizedParameterNames(it) } ?: true) val javaParameter = when (val callable = (vp.parent as? IrFunction)?.let { getJavaCallable(it) }) { is JavaConstructor -> callable.valueParameters.getOrNull(idx) is JavaMethod -> callable.valueParameters.getOrNull(idx) else -> null } val extraAnnotations = listOfNotNull( getNullabilityAnnotation( vp.type, vp.origin, vp.annotations, javaParameter?.annotations ) ) extractAnnotations(vp, vp.annotations + extraAnnotations, id, extractTypeAccess) return extractValueParameter( id, substitutedType, vp.name.asString(), location, parent, idx, useValueParameter(vp, parentSourceDeclaration), syntheticParameterNames, vp.isVararg, vp.isNoinline, vp.isCrossinline ) } } private fun extractValueParameter( id: Label, t: IrType, name: String, locId: Label, parent: Label, idx: Int, paramSourceDeclaration: Label, syntheticParameterNames: Boolean, isVararg: Boolean, isNoinline: Boolean, isCrossinline: Boolean ): TypeResults { val type = useType(t) tw.writeParams(id, type.javaResult.id, idx, parent, paramSourceDeclaration) tw.writeParamsKotlinType(id, type.kotlinResult.id) tw.writeHasLocation(id, locId) if (!syntheticParameterNames) { tw.writeParamName(id, name) } if (isVararg) { tw.writeIsVarargsParam(id) } if (isNoinline) { addModifiers(id, "noinline") } if (isCrossinline) { addModifiers(id, "crossinline") } return type } /** * mkContainerLabel is a lambda so that we get laziness: If the container is a file, then we * don't want to extract the file class unless something actually needs it. */ private fun extractStaticInitializer( container: IrDeclarationContainer, mkContainerLabel: () -> Label ) { with("static initializer extraction", container) { extractDeclInitializers(container.declarations, true) { val containerId = mkContainerLabel() val clinitLabel = getFunctionLabel( container, containerId, "", listOf(), pluginContext.irBuiltIns.unitType, extensionParamType = null, functionTypeParameters = listOf(), classTypeArgsIncludingOuterClasses = listOf(), overridesCollectionsMethod = false, javaSignature = null, addParameterWildcardsByDefault = false ) val clinitId = tw.getLabelFor(clinitLabel) val returnType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN) tw.writeMethods( clinitId, "", "()", returnType.javaResult.id, containerId, clinitId ) tw.writeMethodsKotlinType(clinitId, returnType.kotlinResult.id) tw.writeCompiler_generated( clinitId, CompilerGeneratedKinds.CLASS_INITIALISATION_METHOD.kind ) val locId = tw.getWholeFileLocation() tw.writeHasLocation(clinitId, locId) addModifiers(clinitId, "static") // add and return body block: Pair(extractBlockBody(clinitId, locId), clinitId) } } } private fun extractInstanceInitializerBlock( parent: StmtParent, enclosingConstructor: IrConstructor ) { with("object initializer block", enclosingConstructor) { val constructorId = useFunction(enclosingConstructor) if (constructorId == null) { logger.errorElement("Cannot get ID for constructor", enclosingConstructor) return } val enclosingClass = enclosingConstructor.parentClassOrNull if (enclosingClass == null) { logger.errorElement("Constructor's parent is not a class", enclosingConstructor) return } // Don't make this block lazily since we need to insert something at the given // parent.idx position, // and in the case where there are no initializers to emit an empty block is an // acceptable filler. val initBlockId = tw.getFreshIdLabel().also { tw.writeStmts_block(it, parent.parent, parent.idx, constructorId) val locId = tw.getLocation(enclosingConstructor) tw.writeHasLocation(it, locId) } extractDeclInitializers(enclosingClass.declarations, false) { Pair(initBlockId, constructorId) } } } private fun extractDeclInitializers( declarations: List, extractStaticInitializers: Boolean, makeEnclosingBlock: () -> Pair, Label> ) { val blockAndFunctionId by lazy { makeEnclosingBlock() } // Extract field initializers and init blocks (the latter can only occur in object // initializers) var idx = 0 fun extractFieldInitializer(f: IrDeclaration) { val static: Boolean val initializer: IrExpressionBody? val lhsType: TypeResults? val vId: Label? val isAnnotationClassField: Boolean if (f is IrField) { static = f.isStatic initializer = f.initializer isAnnotationClassField = isAnnotationClassField(f) lhsType = useType(if (isAnnotationClassField) kClassToJavaClass(f.type) else f.type) vId = useField(f) } else if (f is IrEnumEntry) { static = true initializer = f.initializerExpression isAnnotationClassField = false lhsType = getEnumEntryType(f) if (lhsType == null) { return } vId = useEnumEntry(f) } else { return } if (static != extractStaticInitializers || initializer == null) { return } val expr = initializer.expression val declLocId = tw.getLocation(f) extractExpressionStmt( declLocId, blockAndFunctionId.first, idx++, blockAndFunctionId.second ) .also { stmtId -> val type = if (isAnnotationClassField) kClassToJavaClass(expr.type) else expr.type extractAssignExpr(type, declLocId, stmtId, 0, blockAndFunctionId.second, stmtId) .also { assignmentId -> tw.writeKtInitializerAssignment(assignmentId) extractVariableAccess( vId, lhsType, declLocId, assignmentId, 0, blockAndFunctionId.second, stmtId ) .also { lhsId -> if (static) { extractStaticTypeAccessQualifier( f, lhsId, declLocId, blockAndFunctionId.second, stmtId ) } } extractExpressionExpr( expr, blockAndFunctionId.second, assignmentId, 1, stmtId ) } } } for (decl in declarations) { when (decl) { is IrProperty -> { decl.backingField?.let { extractFieldInitializer(it) } } is IrField -> { extractFieldInitializer(decl) } is IrEnumEntry -> { extractFieldInitializer(decl) } is IrAnonymousInitializer -> { if (decl.isStatic != extractStaticInitializers) { continue } for (stmt in decl.body.statements) { extractStatement( stmt, blockAndFunctionId.second, blockAndFunctionId.first, idx++ ) } } else -> continue } } } private fun isKotlinDefinedInterface(cls: IrClass?) = cls != null && cls.isInterface && cls.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB private fun needsInterfaceForwarder(f: IrFunction) = // jvmDefaultModeEnabledIsEnabled means that -Xjvm-default=all or all-compatibility was // used, in which case real Java default interfaces are used, and we don't need to do // anything. // Otherwise, for a Kotlin-defined method inheriting a Kotlin-defined default, we need to // create a synthetic method like // `int f(int x) { return super.InterfaceWithDefault.f(x); }`, because kotlinc will generate // a public method and Java callers may directly target it. // (NB. kotlinc's actual implementation strategy is different -- it makes an inner class // called InterfaceWithDefault$DefaultImpls and stores the default methods // there to allow default method usage in Java < 8, but this is hopefully niche. !jvmDefaultModeEnabledIsEnabled( pluginContext.languageVersionSettings .getFlag(JvmAnalysisFlags.jvmDefaultMode)) && f.parentClassOrNull.let { it != null && it.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB && it.modality != Modality.ABSTRACT } && f.realOverrideTarget.let { it != f && (it as? IrSimpleFunction)?.modality != Modality.ABSTRACT && isKotlinDefinedInterface(it.parentClassOrNull) } private fun makeInterfaceForwarder( f: IrFunction, parentId: Label, extractBody: Boolean, extractMethodAndParameterTypeAccesses: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List? ) = forceExtractFunction( f, parentId, extractBody = false, extractMethodAndParameterTypeAccesses, extractAnnotations = false, typeSubstitution, classTypeArgsIncludingOuterClasses, overriddenAttributes = OverriddenFunctionAttributes( visibility = DescriptorVisibilities.PUBLIC, modality = Modality.OPEN ) ) .also { functionId -> tw.writeCompiler_generated( functionId, CompilerGeneratedKinds.INTERFACE_FORWARDER.kind ) if (extractBody) { val realFunctionLocId = tw.getLocation(f) val inheritedDefaultFunction = f.realOverrideTarget val directlyInheritedSymbol = when (f) { is IrSimpleFunction -> f.overriddenSymbols.find { it.owner === inheritedDefaultFunction } ?: f.overriddenSymbols.find { it.owner.realOverrideTarget === inheritedDefaultFunction } ?: inheritedDefaultFunction.symbol else -> inheritedDefaultFunction .symbol // This is strictly invalid, since we shouldn't use // A.super.f(...) where A may not be a direct supertype, // but this path should also be unreachable. } val defaultDefiningInterfaceType = (directlyInheritedSymbol.owner.parentClassOrNull ?: return functionId) .typeWith() extractExpressionBody(functionId, realFunctionLocId).also { returnId -> extractRawMethodAccess( f, realFunctionLocId, f.returnType, functionId, returnId, 0, returnId, f.valueParameters.size, { argParentId, idxOffset -> f.valueParameters.mapIndexed { idx, param -> val syntheticParamId = useValueParameter(param, functionId) extractVariableAccess( syntheticParamId, param.type, realFunctionLocId, argParentId, idxOffset + idx, functionId, returnId ) } }, f.dispatchReceiverParameter?.type, { callId -> extractSuperAccess( defaultDefiningInterfaceType, functionId, callId, -1, returnId, realFunctionLocId ) }, null ) } } } private fun extractFunction( f: IrFunction, parentId: Label, extractBody: Boolean, extractMethodAndParameterTypeAccesses: Boolean, extractAnnotations: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List? ) = if (isFake(f)) { if (needsInterfaceForwarder(f)) makeInterfaceForwarder( f, parentId, extractBody, extractMethodAndParameterTypeAccesses, typeSubstitution, classTypeArgsIncludingOuterClasses ) else null } else { // Work around an apparent bug causing redeclarations of `fun toString(): String` // specifically in interfaces loaded from Java classes show up like fake overrides. val overriddenVisibility = if (f.isFakeOverride && isJavaBinaryObjectMethodRedeclaration(f)) OverriddenFunctionAttributes(visibility = DescriptorVisibilities.PUBLIC) else null forceExtractFunction( f, parentId, extractBody, extractMethodAndParameterTypeAccesses, extractAnnotations, typeSubstitution, classTypeArgsIncludingOuterClasses, overriddenAttributes = overriddenVisibility ) .also { // The defaults-forwarder function is a static utility, not a member, so we only // need to extract this for the unspecialised instance of this class. if (classTypeArgsIncludingOuterClasses.isNullOrEmpty()) extractDefaultsFunction( f, parentId, extractBody, extractMethodAndParameterTypeAccesses ) extractGeneratedOverloads( f, parentId, null, extractBody, extractMethodAndParameterTypeAccesses, typeSubstitution, classTypeArgsIncludingOuterClasses ) } } private fun extractDefaultsFunction( f: IrFunction, parentId: Label, extractBody: Boolean, extractMethodAndParameterTypeAccesses: Boolean ) { if (f.valueParameters.none { it.defaultValue != null }) return val id = getDefaultsMethodLabel(f) if (id == null) { logger.errorElement("Cannot get defaults method label for function", f) return } val locId = getLocation(f, null) val extReceiver = f.extensionReceiverParameter val dispatchReceiver = if (f.shouldExtractAsStatic) null else f.dispatchReceiverParameter val parameterTypes = getDefaultsMethodArgTypes(f) val allParamTypeResults = parameterTypes.mapIndexed { i, paramType -> val paramId = tw.getLabelFor(getValueParameterLabel(id, i)) extractValueParameter( paramId, paramType, "p$i", locId, id, i, paramId, isVararg = false, syntheticParameterNames = true, isCrossinline = false, isNoinline = false ) .also { if (extractMethodAndParameterTypeAccesses) extractTypeAccess(useType(paramType), locId, paramId, -1) } } val paramsSignature = allParamTypeResults.joinToString(separator = ",", prefix = "(", postfix = ")") { signatureOrWarn(it.javaResult, f) } val shortName = getDefaultsMethodName(f) if (f.symbol is IrConstructorSymbol) { val constrId = id.cast() extractConstructor(constrId, shortName, paramsSignature, parentId, constrId) } else { val methodId = id.cast() extractMethod( methodId, locId, shortName, erase(f.returnType), paramsSignature, parentId, methodId, origin = null, extractTypeAccess = extractMethodAndParameterTypeAccesses ) addModifiers(id, "static") if (extReceiver != null) { val idx = if (dispatchReceiver != null) 1 else 0 val extendedType = allParamTypeResults[idx] tw.writeKtExtensionFunctions( methodId, extendedType.javaResult.id, extendedType.kotlinResult.id ) } } tw.writeHasLocation(id, locId) if ( f.visibility != DescriptorVisibilities.PRIVATE && f.visibility != DescriptorVisibilities.PRIVATE_TO_THIS ) { // Private methods have package-private (default) visibility $default methods; all other // visibilities seem to produce a public $default method. addModifiers(id, "public") } tw.writeCompiler_generated(id, CompilerGeneratedKinds.DEFAULT_ARGUMENTS_METHOD.kind) if (extractBody) { val nonSyntheticParams = listOfNotNull(dispatchReceiver) + f.valueParameters // This stack entry represents as if we're extracting the 'real' function `f`, giving // the indices of its non-synthetic parameters // such that when we extract the default expressions below, any reference to f's nth // parameter will resolve to f$default's // n + o'th parameter, where `o` is the parameter offset caused by adding any dispatch // receiver to the parameter list. // Note we don't need to add the extension receiver here because `useValueParameter` // always assumes an extension receiver // will be prepended if one exists. val realFunctionId = useFunction(f, parentId, null) DeclarationStackAdjuster( f, OverriddenFunctionAttributes( id, id, locId, nonSyntheticParams, typeParameters = listOf(), isStatic = true ) ) .use { val realParamsVarId = getValueParameterLabel(id, parameterTypes.size - 2) val intType = pluginContext.irBuiltIns.intType val paramIdxOffset = listOf(dispatchReceiver, f.extensionReceiverParameter).count { it != null } extractBlockBody(id, locId).also { blockId -> var nextStmt = 0 // For each parameter with a default, sub in the default value if the caller // hasn't supplied a value: f.valueParameters.forEachIndexed { paramIdx, param -> val defaultVal = param.defaultValue if (defaultVal != null) { extractIfStmt(locId, blockId, nextStmt++, id).also { ifId -> // if (realParams & thisParamBit == 0) ... extractEqualsExpression(locId, ifId, 0, id, ifId).also { eqId -> extractAndbitExpression(intType, locId, eqId, 0, id, ifId) .also { opId -> extractConstantInteger( 1 shl paramIdx, locId, opId, 0, id, ifId ) extractVariableAccess( tw.getLabelFor(realParamsVarId), intType, locId, opId, 1, id, ifId ) } extractConstantInteger(0, locId, eqId, 1, id, ifId) } // thisParamVar = defaultExpr... extractExpressionStmt(locId, ifId, 1, id).also { exprStmtId -> extractAssignExpr( param.type, locId, exprStmtId, 0, id, exprStmtId ) .also { assignId -> extractVariableAccess( tw.getLabelFor( getValueParameterLabel( id, paramIdx + paramIdxOffset ) ), param.type, locId, assignId, 0, id, exprStmtId ) extractExpressionExpr( defaultVal.expression, id, assignId, 1, exprStmtId ) } } } } } // Now call the real function: if (f is IrConstructor) { tw.getFreshIdLabel().also { thisCallId -> tw.writeStmts_constructorinvocationstmt( thisCallId, blockId, nextStmt++, id ) tw.writeHasLocation(thisCallId, locId) f.valueParameters.forEachIndexed { idx, param -> extractVariableAccess( tw.getLabelFor(getValueParameterLabel(id, idx)), param.type, locId, thisCallId, idx, id, thisCallId ) } tw.writeCallableBinding(thisCallId, realFunctionId) } } else { tw.getFreshIdLabel().also { returnId -> tw.writeStmts_returnstmt(returnId, blockId, nextStmt++, id) tw.writeHasLocation(returnId, locId) extractMethodAccessWithoutArgs( f.returnType, locId, id, returnId, 0, returnId, realFunctionId ) .also { thisCallId -> val realFnIdxOffset = if (f.extensionReceiverParameter != null) 1 else 0 val paramMappings = f.valueParameters.mapIndexed { idx, param -> Triple( param.type, idx + paramIdxOffset, idx + realFnIdxOffset ) } + listOfNotNull( dispatchReceiver?.let { Triple(it.type, 0, -1) }, extReceiver?.let { Triple( it.type, if (dispatchReceiver != null) 1 else 0, 0 ) } ) paramMappings.forEach { (type, fromIdx, toIdx) -> extractVariableAccess( tw.getLabelFor( getValueParameterLabel(id, fromIdx) ), type, locId, thisCallId, toIdx, id, returnId ) } if (f.shouldExtractAsStatic) extractStaticTypeAccessQualifier( f, thisCallId, locId, id, returnId ) else if (f.isLocalFunction()) { extractNewExprForLocalFunction( getLocallyVisibleFunctionLabels(f), thisCallId, locId, id, returnId ) } } } } } } } } private val jvmOverloadsFqName = FqName("kotlin.jvm.JvmOverloads") private fun extractGeneratedOverloads( f: IrFunction, parentId: Label, maybeSourceParentId: Label?, extractBody: Boolean, extractMethodAndParameterTypeAccesses: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List? ) { fun extractGeneratedOverload(paramList: List) { val overloadParameters = paramList.filterNotNull() // Note `overloadParameters` have incorrect parents and indices, since there is no // actual IrFunction describing the required synthetic overload. // We have to use the `overriddenAttributes` element of `DeclarationStackAdjuster` to // fix up references to these parameters while we're extracting // these synthetic overloads. val overloadId = tw.getLabelFor( getFunctionLabel( f, parentId, classTypeArgsIncludingOuterClasses, overloadParameters ) ) val sourceParentId = maybeSourceParentId ?: if (typeSubstitution != null) useDeclarationParentOf(f, false) else parentId if (sourceParentId == null) { logger.errorElement("Cannot get source parent ID for function", f) return } val sourceDeclId = tw.getLabelFor( getFunctionLabel(f, sourceParentId, listOf(), overloadParameters) ) val overriddenAttributes = OverriddenFunctionAttributes( id = overloadId, sourceDeclarationId = sourceDeclId, valueParameters = overloadParameters ) forceExtractFunction( f, parentId, extractBody = false, extractMethodAndParameterTypeAccesses, extractAnnotations = false, typeSubstitution, classTypeArgsIncludingOuterClasses, overriddenAttributes = overriddenAttributes ) tw.writeCompiler_generated(overloadId, CompilerGeneratedKinds.JVMOVERLOADS_METHOD.kind) val realFunctionLocId = tw.getLocation(f) if (extractBody) { DeclarationStackAdjuster(f, overriddenAttributes).use { // Create a synthetic function body that calls the corresponding $default // function: val regularArgs = paramList.map { it?.let { p -> IrGetValueImpl(-1, -1, p.symbol) } } if (f is IrConstructor) { val blockId = extractBlockBody(overloadId, realFunctionLocId) val constructorCallId = tw.getFreshIdLabel() tw.writeStmts_constructorinvocationstmt( constructorCallId, blockId, 0, overloadId ) tw.writeHasLocation(constructorCallId, realFunctionLocId) tw.writeCallableBinding( constructorCallId, getDefaultsMethodLabel(f, parentId) ) extractDefaultsCallArguments( constructorCallId, f, overloadId, constructorCallId, regularArgs, null, null ) } else { val dispatchReceiver = f.dispatchReceiverParameter?.let { IrGetValueImpl(-1, -1, it.symbol) } val extensionReceiver = f.extensionReceiverParameter?.let { IrGetValueImpl(-1, -1, it.symbol) } extractExpressionBody(overloadId, realFunctionLocId).also { returnId -> extractsDefaultsCall( f, realFunctionLocId, f.returnType, overloadId, returnId, 0, returnId, regularArgs, dispatchReceiver, extensionReceiver ) } } } } } if (!f.hasAnnotation(jvmOverloadsFqName)) { if ( f is IrConstructor && f.valueParameters.isNotEmpty() && f.valueParameters.all { it.defaultValue != null } && f.parentClassOrNull?.let { // Don't create a default constructor for an annotation class, or a class // that explicitly declares a no-arg constructor. !it.isAnnotationClass && it.declarations.none { d -> d is IrConstructor && d.valueParameters.isEmpty() } } == true ) { // Per https://kotlinlang.org/docs/classes.html#creating-instances-of-classes, a // single default overload gets created specifically // when we have all default parameters, regardless of `@JvmOverloads`. extractGeneratedOverload(f.valueParameters.map { _ -> null }) } return } val paramList: MutableList = f.valueParameters.toMutableList() for (n in (f.valueParameters.size - 1) downTo 0) { if (f.valueParameters[n].defaultValue != null) { paramList[n] = null // Remove this parameter, to be replaced by a default value extractGeneratedOverload(paramList) } } } private fun extractConstructor( id: Label, shortName: String, paramsSignature: String, parentId: Label, sourceDeclaration: Label ) { val unitType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN) tw.writeConstrs( id, shortName, "$shortName$paramsSignature", unitType.javaResult.id, parentId, sourceDeclaration ) tw.writeConstrsKotlinType(id, unitType.kotlinResult.id) } private fun extractMethod( id: Label, locId: Label, shortName: String, returnType: IrType, paramsSignature: String, parentId: Label, sourceDeclaration: Label, origin: IrDeclarationOrigin?, extractTypeAccess: Boolean ) { val returnTypeResults = useType(returnType, TypeContext.RETURN) tw.writeMethods( id, shortName, "$shortName$paramsSignature", returnTypeResults.javaResult.id, parentId, sourceDeclaration ) tw.writeMethodsKotlinType(id, returnTypeResults.kotlinResult.id) when (origin) { IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER -> tw.writeCompiler_generated( id, CompilerGeneratedKinds.GENERATED_DATA_CLASS_MEMBER.kind ) IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR -> tw.writeCompiler_generated( id, CompilerGeneratedKinds.DEFAULT_PROPERTY_ACCESSOR.kind ) IrDeclarationOrigin.ENUM_CLASS_SPECIAL_MEMBER -> tw.writeCompiler_generated( id, CompilerGeneratedKinds.ENUM_CLASS_SPECIAL_MEMBER.kind ) } if (extractTypeAccess) { extractTypeAccessRecursive(returnType, locId, id, -1) } } private fun signatureOrWarn(t: TypeResult<*>, associatedElement: IrElement?) = t.signature ?: "" .also { if (associatedElement != null) logger.warnElement( "Needed a signature for a type that doesn't have one", associatedElement ) else logger.warn("Needed a signature for a type that doesn't have one") } private fun getNullabilityAnnotationName( t: IrType, declOrigin: IrDeclarationOrigin, existingAnnotations: List, javaAnnotations: Collection? ): FqName? { if (t !is IrSimpleType) return null fun hasExistingAnnotation(name: FqName) = existingAnnotations.any { existing -> existing.type.classFqName == name } return if (declOrigin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB) { // Java declaration: restore a NotNull or Nullable annotation if the original Java // member had one but the Kotlin compiler removed it. javaAnnotations ?.mapNotNull { it.classId?.asSingleFqName() } ?.singleOrNull { NOT_NULL_ANNOTATIONS.contains(it) || NULLABLE_ANNOTATIONS.contains(it) } ?.takeUnless { hasExistingAnnotation(it) } } else { // Kotlin declaration: add a NotNull annotation to a non-nullable non-primitive type, // unless one is already present. // Usually Kotlin declarations can't have a manual `@NotNull`, but this happens at least // when delegating members are // synthesised and inherit the annotation from the delegate (which given it has // @NotNull, is likely written in Java) JvmAnnotationNames.JETBRAINS_NOT_NULL_ANNOTATION.takeUnless { t.isNullableCodeQL() || primitiveTypeMapping.getPrimitiveInfo(t) != null || hasExistingAnnotation(it) } } } private fun getNullabilityAnnotation( t: IrType, declOrigin: IrDeclarationOrigin, existingAnnotations: List, javaAnnotations: Collection? ) = getNullabilityAnnotationName(t, declOrigin, existingAnnotations, javaAnnotations)?.let { getClassByFqName(pluginContext, it)?.let { annotationClass -> annotationClass.owner.declarations.firstIsInstanceOrNull()?.let { annotationConstructor -> IrConstructorCallImpl.fromSymbolOwner( UNDEFINED_OFFSET, UNDEFINED_OFFSET, annotationConstructor.returnType, annotationConstructor.symbol, 0 ) } } } private fun forceExtractFunction( f: IrFunction, parentId: Label, extractBody: Boolean, extractMethodAndParameterTypeAccesses: Boolean, extractAnnotations: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List?, extractOrigin: Boolean = true, overriddenAttributes: OverriddenFunctionAttributes? = null ): Label { with("function", f) { DeclarationStackAdjuster(f, overriddenAttributes).use { val javaCallable = getJavaCallable(f) getFunctionTypeParameters(f).mapIndexed { idx, tp -> extractTypeParameter( tp, idx, (javaCallable as? JavaTypeParameterListOwner) ?.typeParameters ?.getOrNull(idx) ) } val id = overriddenAttributes?.id ?: // If this is a class that would ordinarily be replaced by a Java // equivalent (e.g. kotlin.Map -> java.util.Map), // don't replace here, really extract the Kotlin version: useFunction( f, parentId, classTypeArgsIncludingOuterClasses, noReplace = true ) val sourceDeclaration = overriddenAttributes?.sourceDeclarationId ?: if (typeSubstitution != null && overriddenAttributes?.id == null) { val sourceFunId = useFunction(f) if (sourceFunId == null) { logger.errorElement("Cannot get source ID for function", f) id // TODO: This is wrong; we ought to just fail in this case } else { sourceFunId } } else { id } val extReceiver = f.extensionReceiverParameter // The following parameter order is correct, because member $default methods (where // the order would be [dispatchParam], [extensionParam], normalParams) are not // extracted here val fParameters = listOfNotNull(extReceiver) + (overriddenAttributes?.valueParameters ?: f.valueParameters) val paramTypes = fParameters.mapIndexed { i, vp -> extractValueParameter( vp, id, i, typeSubstitution, sourceDeclaration, classTypeArgsIncludingOuterClasses, extractTypeAccess = extractMethodAndParameterTypeAccesses, overriddenAttributes?.sourceLoc ) } if (extReceiver != null) { val extendedType = paramTypes[0] tw.writeKtExtensionFunctions( id.cast(), extendedType.javaResult.id, extendedType.kotlinResult.id ) } val paramsSignature = paramTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { signatureOrWarn(it.javaResult, f) } val adjustedReturnType = addJavaLoweringWildcards( getAdjustedReturnType(f), false, (javaCallable as? JavaMethod)?.returnType ) val substReturnType = typeSubstitution?.let { it(adjustedReturnType, TypeContext.RETURN, pluginContext) } ?: adjustedReturnType val locId = overriddenAttributes?.sourceLoc ?: getLocation(f, classTypeArgsIncludingOuterClasses) if (f.symbol is IrConstructorSymbol) { val shortName = when { adjustedReturnType.isAnonymous -> "" typeSubstitution != null -> useType(substReturnType).javaResult.shortName else -> adjustedReturnType.classFqName?.shortName()?.asString() ?: f.name.asString() } extractConstructor( id.cast(), shortName, paramsSignature, parentId, sourceDeclaration.cast() ) } else { val shortNames = getFunctionShortName(f) val methodId = id.cast() extractMethod( methodId, locId, shortNames.nameInDB, substReturnType, paramsSignature, parentId, sourceDeclaration.cast(), if (extractOrigin) f.origin else null, extractMethodAndParameterTypeAccesses ) if (shortNames.nameInDB != shortNames.kotlinName) { tw.writeKtFunctionOriginalNames(methodId, shortNames.kotlinName) } if (f.hasInterfaceParent() && f.body != null) { addModifiers( methodId, "default" ) // The actual output class file may or may not have this modifier, // depending on the -Xjvm-default setting. } } tw.writeHasLocation(id, locId) val body = f.body if (body != null && extractBody) { if (typeSubstitution != null) logger.errorElement( "Type substitution should only be used to extract a function prototype, not the body", f ) extractBody(body, id) } extractVisibility(f, id, overriddenAttributes?.visibility ?: f.visibility) if (f.isInline) { addModifiers(id, "inline") } if (f.shouldExtractAsStatic) { addModifiers(id, "static") } if (f is IrSimpleFunction && f.overriddenSymbols.isNotEmpty()) { addModifiers(id, "override") } if (f.isSuspend) { addModifiers(id, "suspend") } if (f.symbol !is IrConstructorSymbol) { when (overriddenAttributes?.modality ?: (f as? IrSimpleFunction)?.modality) { Modality.ABSTRACT -> addModifiers(id, "abstract") Modality.FINAL -> addModifiers(id, "final") else -> Unit } } linesOfCode?.linesOfCodeInDeclaration(f, id) if (extractAnnotations) { val extraAnnotations = if (f.symbol is IrConstructorSymbol) listOf() else listOfNotNull( getNullabilityAnnotation( f.returnType, f.origin, f.annotations, getJavaCallable(f)?.annotations ) ) extractAnnotations( f, f.annotations + extraAnnotations, id, extractMethodAndParameterTypeAccesses ) } return id } } } private fun isStaticFunction(f: IrFunction): Boolean { return f.dispatchReceiverParameter == null // Has no dispatch receiver, && !f .isLocalFunction() // not a local function. Local functions are extracted as // instance methods with the local class instantiation as the // qualifier && f.symbol !is IrConstructorSymbol // not a constructor } private fun extractField( f: IrField, parentId: Label, extractAnnotationEnumTypeAccesses: Boolean ): Label { with("field", f) { DeclarationStackAdjuster(f).use { val fNameSuffix = getExtensionReceiverType(f)?.let { it.classFqName?.asString()?.replace(".", "$$") } ?: "" val extractType = if (isAnnotationClassField(f)) kClassToJavaClass(f.type) else f.type val id = useField(f) extractAnnotations(f, id, extractAnnotationEnumTypeAccesses) return extractField( id, "${f.name.asString()}$fNameSuffix", extractType, parentId, tw.getLocation(f), f.visibility, f, isExternalDeclaration(f), f.isFinal, isDirectlyExposedCompanionObjectField(f) ) } } } private fun extractField( id: Label, name: String, type: IrType, parentId: Label, locId: Label, visibility: DescriptorVisibility, errorElement: IrElement, isExternalDeclaration: Boolean, isFinal: Boolean, isStatic: Boolean ): Label { val t = useType(type) tw.writeFields(id, name, t.javaResult.id, parentId) tw.writeFieldsKotlinType(id, t.kotlinResult.id) tw.writeHasLocation(id, locId) extractVisibility(errorElement, id, visibility) if (isFinal) { addModifiers(id, "final") } if (isStatic) { addModifiers(id, "static") } if (!isExternalDeclaration) { val fieldDeclarationId = tw.getFreshIdLabel() tw.writeFielddecls(fieldDeclarationId, parentId) tw.writeFieldDeclaredIn(id, fieldDeclarationId, 0) tw.writeHasLocation(fieldDeclarationId, locId) extractTypeAccessRecursive(type, locId, fieldDeclarationId, 0) } return id } private fun extractProperty( p: IrProperty, parentId: Label, extractBackingField: Boolean, extractFunctionBodies: Boolean, extractPrivateMembers: Boolean, extractAnnotations: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List? ) { with("property", p) { fun needsInterfaceForwarderQ(f: IrFunction?) = f?.let { needsInterfaceForwarder(f) } ?: false if ( isFake(p) && !needsInterfaceForwarderQ(p.getter) && !needsInterfaceForwarderQ(p.setter) ) return DeclarationStackAdjuster(p).use { val id = useProperty(p, parentId, classTypeArgsIncludingOuterClasses) val locId = getLocation(p, classTypeArgsIncludingOuterClasses) tw.writeKtProperties(id, p.name.asString()) tw.writeHasLocation(id, locId) val bf = p.backingField val getter = p.getter val setter = p.setter if (getter == null) { if (!isExternalDeclaration(p)) { logger.warnElement("IrProperty without a getter", p) } } else if (shouldExtractDecl(getter, extractPrivateMembers)) { val getterId = extractFunction( getter, parentId, extractBody = extractFunctionBodies, extractMethodAndParameterTypeAccesses = extractFunctionBodies, extractAnnotations = extractAnnotations, typeSubstitution, classTypeArgsIncludingOuterClasses ) ?.cast() if (getterId != null) { tw.writeKtPropertyGetters(id, getterId) if (getter.origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR) { tw.writeCompiler_generated( getterId, CompilerGeneratedKinds.DELEGATED_PROPERTY_GETTER.kind ) } } } if (setter == null) { if (p.isVar && !isExternalDeclaration(p)) { logger.warnElement("isVar property without a setter", p) } } else if (shouldExtractDecl(setter, extractPrivateMembers)) { if (!p.isVar) { logger.warnElement("!isVar property with a setter", p) } val setterId = extractFunction( setter, parentId, extractBody = extractFunctionBodies, extractMethodAndParameterTypeAccesses = extractFunctionBodies, extractAnnotations = extractAnnotations, typeSubstitution, classTypeArgsIncludingOuterClasses ) ?.cast() if (setterId != null) { tw.writeKtPropertySetters(id, setterId) if (setter.origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR) { tw.writeCompiler_generated( setterId, CompilerGeneratedKinds.DELEGATED_PROPERTY_SETTER.kind ) } } } if (bf != null && extractBackingField) { val fieldParentId = useDeclarationParentOf(bf, false) if (fieldParentId != null) { val fieldId = extractField(bf, fieldParentId.cast(), extractFunctionBodies) tw.writeKtPropertyBackingFields(id, fieldId) if (p.isDelegated) { tw.writeKtPropertyDelegates(id, fieldId) } } } extractVisibility(p, id, p.visibility) // TODO: extract annotations if (p.isLateinit) { addModifiers(id, "lateinit") } } } } private fun getEnumEntryType(ee: IrEnumEntry): TypeResults? { val parent = ee.parent if (parent !is IrClass) { logger.errorElement("Enum entry with unexpected parent: " + parent.javaClass, ee) return null } else if (parent.typeParameters.isNotEmpty()) { logger.errorElement("Enum entry parent class has type parameters: " + parent.name, ee) return null } else { return useSimpleTypeClass(parent, emptyList(), false) } } private fun extractEnumEntry( ee: IrEnumEntry, parentId: Label, extractPrivateMembers: Boolean, extractFunctionBodies: Boolean ) { with("enum entry", ee) { DeclarationStackAdjuster(ee).use { val id = useEnumEntry(ee) val type = getEnumEntryType(ee) ?: return tw.writeFields(id, ee.name.asString(), type.javaResult.id, parentId) tw.writeFieldsKotlinType(id, type.kotlinResult.id) val locId = tw.getLocation(ee) tw.writeHasLocation(id, locId) tw.writeIsEnumConst(id) if (extractFunctionBodies) { val fieldDeclarationId = tw.getFreshIdLabel() tw.writeFielddecls(fieldDeclarationId, parentId) tw.writeFieldDeclaredIn(id, fieldDeclarationId, 0) tw.writeHasLocation(fieldDeclarationId, locId) extractTypeAccess(type, locId, fieldDeclarationId, 0) } ee.correspondingClass?.let { extractDeclaration( it, extractPrivateMembers, extractFunctionBodies, extractAnnotations = true ) } extractAnnotations(ee, id, extractFunctionBodies) } } } private fun extractTypeAlias(ta: IrTypeAlias) { with("type alias", ta) { if (ta.typeParameters.isNotEmpty()) { // TODO: Extract this information return } val id = useTypeAlias(ta) val locId = tw.getLocation(ta) // TODO: We don't really want to generate any Java types here; we only want the KT type: val type = useType(ta.expandedType) tw.writeKt_type_alias(id, ta.name.asString(), type.kotlinResult.id) tw.writeHasLocation(id, locId) // TODO: extract annotations } } private fun extractBody(b: IrBody, callable: Label) { with("body", b) { when (b) { is IrBlockBody -> extractBlockBody(b, callable) is IrSyntheticBody -> extractSyntheticBody(b, callable) is IrExpressionBody -> extractExpressionBody(b, callable) else -> { logger.errorElement("Unrecognised IrBody: " + b.javaClass, b) } } } } private fun extractBlockBody(callable: Label, locId: Label) = tw.getFreshIdLabel().also { tw.writeStmts_block(it, callable, 0, callable) tw.writeHasLocation(it, locId) } private fun extractBlockBody(b: IrBlockBody, callable: Label) { with("block body", b) { extractBlockBody(callable, tw.getLocation(b)).also { for ((sIdx, stmt) in b.statements.withIndex()) { extractStatement(stmt, callable, it, sIdx) } } } } private fun extractSyntheticBody(b: IrSyntheticBody, callable: Label) { with("synthetic body", b) { val kind = b.kind when { kind == IrSyntheticBodyKind.ENUM_VALUES -> tw.writeKtSyntheticBody(callable, 1) kind == IrSyntheticBodyKind.ENUM_VALUEOF -> tw.writeKtSyntheticBody(callable, 2) kind == kind_ENUM_ENTRIES -> tw.writeKtSyntheticBody(callable, 3) else -> { logger.errorElement("Unhandled synthetic body kind " + kind, b) } } } } private fun extractExpressionBody(b: IrExpressionBody, callable: Label) { with("expression body", b) { val locId = tw.getLocation(b) extractExpressionBody(callable, locId).also { returnId -> extractExpressionExpr(b.expression, callable, returnId, 0, returnId) } } } fun extractExpressionBody( callable: Label, locId: Label ): Label { val blockId = extractBlockBody(callable, locId) return tw.getFreshIdLabel().also { returnId -> tw.writeStmts_returnstmt(returnId, blockId, 0, callable) tw.writeHasLocation(returnId, locId) } } private fun getVariableLocationProvider(v: IrVariable): IrElement { val init = v.initializer if (v.startOffset < 0 && init != null) { // IR_TEMPORARY_VARIABLEs have no proper location return init } return v } private fun extractVariable( v: IrVariable, callable: Label, parent: Label, idx: Int ) { with("variable", v) { val stmtId = tw.getFreshIdLabel() val locId = tw.getLocation(getVariableLocationProvider(v)) tw.writeStmts_localvariabledeclstmt(stmtId, parent, idx, callable) tw.writeHasLocation(stmtId, locId) extractVariableExpr(v, callable, stmtId, 1, stmtId) } } private fun extractVariableExpr( v: IrVariable, callable: Label, parent: Label, idx: Int, enclosingStmt: Label, extractInitializer: Boolean = true ) { with("variable expr", v) { val varId = useVariable(v) val exprId = tw.getFreshIdLabel() val locId = tw.getLocation(getVariableLocationProvider(v)) val type = useType(v.type) tw.writeLocalvars(varId, v.name.asString(), type.javaResult.id, exprId) tw.writeLocalvarsKotlinType(varId, type.kotlinResult.id) tw.writeHasLocation(varId, locId) tw.writeExprs_localvariabledeclexpr(exprId, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(exprId, type.kotlinResult.id) extractExprContext(exprId, locId, callable, enclosingStmt) val i = v.initializer if (i != null && extractInitializer) { extractExpressionExpr(i, callable, exprId, 0, enclosingStmt) } if (!v.isVar) { addModifiers(varId, "final") } if (v.isLateinit) { addModifiers(varId, "lateinit") } } } private fun extractIfStmt( locId: Label, parent: Label, idx: Int, callable: Label ) = tw.getFreshIdLabel().also { tw.writeStmts_ifstmt(it, parent, idx, callable) tw.writeHasLocation(it, locId) } private fun extractStatement( s: IrStatement, callable: Label, parent: Label, idx: Int ) { with("statement", s) { when (s) { is IrExpression -> { extractExpressionStmt(s, callable, parent, idx) } is IrVariable -> { extractVariable(s, callable, parent, idx) } is IrClass -> { extractLocalTypeDeclStmt(s, callable, parent, idx) } is IrFunction -> { if (s.isLocalFunction()) { val compilerGeneratedKindOverride = if (s.origin == IrDeclarationOrigin.ADAPTER_FOR_CALLABLE_REFERENCE) { CompilerGeneratedKinds.DECLARING_CLASSES_OF_ADAPTER_FUNCTIONS } else { null } val classId = extractGeneratedClass( s, listOf(pluginContext.irBuiltIns.anyType), compilerGeneratedKindOverride = compilerGeneratedKindOverride ) extractLocalTypeDeclStmt(classId, s, callable, parent, idx) val ids = getLocallyVisibleFunctionLabels(s) tw.writeKtLocalFunction(ids.function) } else { logger.errorElement("Expected to find local function", s) } } is IrLocalDelegatedProperty -> { val blockId = tw.getFreshIdLabel() val locId = tw.getLocation(s) tw.writeStmts_block(blockId, parent, idx, callable) tw.writeHasLocation(blockId, locId) extractVariable(s.delegate, callable, blockId, 0) val propId = tw.getFreshIdLabel() tw.writeKtProperties(propId, s.name.asString()) tw.writeHasLocation(propId, locId) tw.writeKtPropertyDelegates(propId, useVariable(s.delegate)) // Getter: extractStatement(s.getter, callable, blockId, 1) val getterLabel = getLocallyVisibleFunctionLabels(s.getter).function tw.writeKtPropertyGetters(propId, getterLabel) val setter = s.setter if (setter != null) { extractStatement(setter, callable, blockId, 2) val setterLabel = getLocallyVisibleFunctionLabels(setter).function tw.writeKtPropertySetters(propId, setterLabel) } } else -> { logger.errorElement("Unrecognised IrStatement: " + s.javaClass, s) } } } } /** * Returns true iff `c` is a call to the function `fName` in the `kotlin.internal.ir` package. * This is used to find calls to builtin functions, which need to be handled specially as they * do not have corresponding source definitions. */ private fun isBuiltinCallInternal(c: IrCall, fName: String) = isBuiltinCall(c, fName, "kotlin.internal.ir") /** * Returns true iff `c` is a call to the function `fName` in the `kotlin` package. This is used * to find calls to builtin functions, which need to be handled specially as they do not have * corresponding source definitions. */ private fun isBuiltinCallKotlin(c: IrCall, fName: String) = isBuiltinCall(c, fName, "kotlin") /** * Returns true iff `c` is a call to the function `fName` in package `pName`. This is used to * find calls to builtin functions, which need to be handled specially as they do not have * corresponding source definitions. */ private fun isBuiltinCall(c: IrCall, fName: String, pName: String): Boolean { val verbose = false fun verboseln(s: String) { if (verbose) println(s) } verboseln("Attempting builtin match for $fName") val target = c.symbol.owner if (target.name.asString() != fName) { verboseln("No match as function name is ${target.name.asString()} not $fName") return false } val targetPkg = target.parent if (targetPkg !is IrPackageFragment) { verboseln("No match as didn't find target package") return false } val targetName = targetPkg.packageFqName.asString() if (targetName != pName) { verboseln("No match as package name is $targetName") return false } verboseln("Match") return true } private fun unaryOp( id: Label, c: IrCall, callable: Label, enclosingStmt: Label ) { val locId = tw.getLocation(c) extractExprContext(id, locId, callable, enclosingStmt) val dr = c.dispatchReceiver if (dr != null) { logger.errorElement("Unexpected dispatch receiver found", c) } if (c.valueArgumentsCount < 1) { logger.errorElement("No arguments found", c) return } extractArgument(id, c, callable, enclosingStmt, 0, "Operand null") if (c.valueArgumentsCount > 1) { logger.errorElement("Extra arguments found", c) } } private fun binOp( id: Label, c: IrCall, callable: Label, enclosingStmt: Label ) { val locId = tw.getLocation(c) extractExprContext(id, locId, callable, enclosingStmt) val dr = c.dispatchReceiver if (dr != null) { logger.errorElement("Unexpected dispatch receiver found", c) } if (c.valueArgumentsCount < 1) { logger.errorElement("No arguments found", c) return } extractArgument(id, c, callable, enclosingStmt, 0, "LHS null") if (c.valueArgumentsCount < 2) { logger.errorElement("No RHS found", c) return } extractArgument(id, c, callable, enclosingStmt, 1, "RHS null") if (c.valueArgumentsCount > 2) { logger.errorElement("Extra arguments found", c) } } private fun extractArgument( id: Label, c: IrCall, callable: Label, enclosingStmt: Label, idx: Int, msg: String ) { val op = c.getValueArgument(idx) if (op == null) { logger.errorElement(msg, c) } else { extractExpressionExpr(op, callable, id, idx, enclosingStmt) } } private fun getDeclaringTypeArguments( callTarget: IrFunction, receiverType: IrSimpleType ): List { val declaringType = callTarget.parentAsClass val receiverClass = receiverType.classifier.owner as? IrClass ?: return listOf() val ancestorTypes = ArrayList() // KFunctionX doesn't implement FunctionX on versions before 1.7.0: if ( (callTarget.name.asString() == "invoke") && (receiverClass.fqNameWhenAvailable ?.asString() ?.startsWith("kotlin.reflect.KFunction") == true) && (callTarget.parentClassOrNull ?.fqNameWhenAvailable ?.asString() ?.startsWith("kotlin.Function") == true) ) { return receiverType.arguments } // Populate ancestorTypes with the path from receiverType's class to its ancestor, // callTarget's declaring type. fun walkFrom(c: IrClass): Boolean { if (declaringType == c) return true else { c.superTypes.forEach { val ancestorClass = (it as? IrSimpleType)?.classifier?.owner as? IrClass ?: return false ancestorTypes.add(it) if (walkFrom(ancestorClass)) return true else ancestorTypes.pop() } return false } } // If a path was found, repeatedly substitute types to get the corresponding specialisation // of that ancestor. if (!walkFrom(receiverClass)) { logger.errorElement( "Failed to find a class declaring ${callTarget.name} starting at ${receiverClass.name}", callTarget ) return listOf() } else { var subbedType: IrSimpleType = receiverType ancestorTypes.forEach { val thisClass = subbedType.classifier.owner if (thisClass !is IrClass) { logger.errorElement( "Found ancestor with unexpected type ${thisClass.javaClass}", callTarget ) return listOf() } val itSubbed = it.substituteTypeArguments(thisClass.typeParameters, subbedType.arguments) if (itSubbed !is IrSimpleType) { logger.errorElement( "Substituted type has unexpected type ${itSubbed.javaClass}", callTarget ) return listOf() } subbedType = itSubbed } return subbedType.arguments } } private fun extractNewExprForLocalFunction( ids: LocallyVisibleFunctionLabels, parent: Label, locId: Label, enclosingCallable: Label, enclosingStmt: Label ) { val idNewexpr = extractNewExpr( ids.constructor, ids.type, locId, parent, -1, enclosingCallable, enclosingStmt ) extractTypeAccessRecursive( pluginContext.irBuiltIns.anyType, locId, idNewexpr, -3, enclosingCallable, enclosingStmt ) } private fun extractMethodAccessWithoutArgs( returnType: IrType, locId: Label, enclosingCallable: Label, callsiteParent: Label, childIdx: Int, enclosingStmt: Label, methodLabel: Label? ) = tw.getFreshIdLabel().also { id -> val type = useType(returnType) tw.writeExprs_methodaccess(id, type.javaResult.id, callsiteParent, childIdx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, enclosingCallable, enclosingStmt) // The caller should have warned about this before, so we don't repeat the warning here. if (methodLabel != null) tw.writeCallableBinding(id, methodLabel) } private val defaultConstructorMarkerClass by lazy { referenceExternalClass("kotlin.jvm.internal.DefaultConstructorMarker") } private val defaultConstructorMarkerType by lazy { defaultConstructorMarkerClass?.typeWith() } private fun getDefaultsMethodLastArgType(f: IrFunction) = (if (f is IrConstructor) defaultConstructorMarkerType else null) ?: pluginContext.irBuiltIns.anyType private fun getDefaultsMethodArgTypes(f: IrFunction) = // The $default method has type ([dispatchReceiver], [extensionReceiver], paramTypes..., // int, Object) // All parameter types are erased. The trailing int is a mask indicating which parameter // values are real // and which should be replaced by defaults. The final Object parameter is apparently always // null. (listOfNotNull(if (f.shouldExtractAsStatic) null else f.dispatchReceiverParameter?.type) + listOfNotNull(f.extensionReceiverParameter?.type) + f.valueParameters.map { it.type } + listOf(pluginContext.irBuiltIns.intType, getDefaultsMethodLastArgType(f))) .map { erase(it) } private fun getDefaultsMethodName(f: IrFunction) = if (f is IrConstructor) { f.returnType.let { when { it.isAnonymous -> "" else -> it.classFqName?.shortName()?.asString() ?: f.name.asString() } } } else { getFunctionShortName(f).nameInDB + "\$default" } private fun getDefaultsMethodLabel(f: IrFunction): Label? { val classTypeArgsIncludingOuterClasses = null val parentId = useDeclarationParentOf(f, false, classTypeArgsIncludingOuterClasses, true) if (parentId == null) { logger.errorElement("Couldn't get parent ID for defaults method", f) return null } return getDefaultsMethodLabel(f, parentId) } private fun getDefaultsMethodLabel( f: IrFunction, parentId: Label ): Label { val defaultsMethodName = if (f is IrConstructor) "" else getDefaultsMethodName(f) val argTypes = getDefaultsMethodArgTypes(f) val defaultMethodLabelStr = getFunctionLabel( f.parent, parentId, defaultsMethodName, argTypes, erase(f.returnType), extensionParamType = null, // if there's any, that's included already in argTypes functionTypeParameters = listOf(), classTypeArgsIncludingOuterClasses = null, overridesCollectionsMethod = false, javaSignature = null, addParameterWildcardsByDefault = false ) return tw.getLabelFor(defaultMethodLabelStr) } private fun extractsDefaultsCall( syntacticCallTarget: IrFunction, locId: Label, resultType: IrType, enclosingCallable: Label, callsiteParent: Label, childIdx: Int, enclosingStmt: Label, valueArguments: List, dispatchReceiver: IrExpression?, extensionReceiver: IrExpression? ) { val callTarget = syntacticCallTarget.target.realOverrideTarget if (isExternalDeclaration(callTarget)) { // Ensure the real target gets extracted, as we might not every directly touch it thanks // to this call being redirected to a $default method. useFunction(callTarget) } // Default parameter values are inherited by overrides; in this case the call should // dispatch against the $default method belonging to the class // that specified the default values, which will in turn dynamically dispatch back to the // relevant override. val overriddenCallTarget = (callTarget as? IrSimpleFunction)?.allOverriddenIncludingSelf()?.firstOrNull { it.overriddenSymbols.isEmpty() && it.valueParameters.any { p -> p.defaultValue != null } } ?: callTarget if (isExternalDeclaration(overriddenCallTarget)) { // Likewise, ensure the overridden target gets extracted. useFunction(overriddenCallTarget) } val defaultMethodLabel = getDefaultsMethodLabel(overriddenCallTarget) val id = extractMethodAccessWithoutArgs( resultType, locId, enclosingCallable, callsiteParent, childIdx, enclosingStmt, defaultMethodLabel ) if (overriddenCallTarget.isLocalFunction()) { extractTypeAccess( getLocallyVisibleFunctionLabels(overriddenCallTarget).type, locId, id, -1, enclosingCallable, enclosingStmt ) } else { extractStaticTypeAccessQualifierUnchecked( overriddenCallTarget, id, locId, enclosingCallable, enclosingStmt ) } extractDefaultsCallArguments( id, overriddenCallTarget, enclosingCallable, enclosingStmt, valueArguments, dispatchReceiver, extensionReceiver ) } private fun extractDefaultsCallArguments( id: Label, callTarget: IrFunction, enclosingCallable: Label, enclosingStmt: Label, valueArguments: List, dispatchReceiver: IrExpression?, extensionReceiver: IrExpression? ) { var nextIdx = 0 if (dispatchReceiver != null && !callTarget.shouldExtractAsStatic) { extractExpressionExpr(dispatchReceiver, enclosingCallable, id, nextIdx++, enclosingStmt) } if (extensionReceiver != null) { extractExpressionExpr( extensionReceiver, enclosingCallable, id, nextIdx++, enclosingStmt ) } val valueArgsWithDummies = valueArguments.zip(callTarget.valueParameters).map { (expr, param) -> expr ?: IrConstImpl.defaultValueForType(0, 0, param.type) } var realParamsMask = 0 valueArguments.forEachIndexed { index, arg -> if (arg != null) realParamsMask = realParamsMask or (1 shl index) } val extraArgs = listOf( IrConstImpl.int(0, 0, pluginContext.irBuiltIns.intType, realParamsMask), IrConstImpl.defaultValueForType(0, 0, getDefaultsMethodLastArgType(callTarget)) ) extractCallValueArguments( id, valueArgsWithDummies + extraArgs, enclosingStmt, enclosingCallable, nextIdx, extractVarargAsArray = true ) } private fun getFunctionInvokeMethod(typeArgs: List): IrFunction? { // For `kotlin.FunctionX` and `kotlin.reflect.KFunctionX` interfaces, we're making sure that // we // extract the call to the `invoke` method that does exist, // `kotlin.jvm.functions.FunctionX::invoke`. val functionalInterface = getFunctionalInterfaceTypeWithTypeArgs(typeArgs) if (functionalInterface == null) { logger.warn("Cannot find functional interface type for raw method access") return null } val functionalInterfaceClass = functionalInterface.classOrNull if (functionalInterfaceClass == null) { logger.warn("Cannot find functional interface class for raw method access") return null } val interfaceType = functionalInterfaceClass.owner val substituted = getJavaEquivalentClass(interfaceType) ?: interfaceType val function = findFunction(substituted, OperatorNameConventions.INVOKE.asString()) if (function == null) { logger.warn("Cannot find invoke function for raw method access") return null } return function } private fun isFunctionInvoke(callTarget: IrFunction, drType: IrSimpleType) = (drType.isFunctionOrKFunction() || drType.isSuspendFunctionOrKFunction()) && callTarget.name.asString() == OperatorNameConventions.INVOKE.asString() private fun getCalleeMethodId( callTarget: IrFunction, drType: IrType?, allowInstantiatedGenericMethod: Boolean ): Label? { if (callTarget.isLocalFunction()) return getLocallyVisibleFunctionLabels(callTarget).function if ( allowInstantiatedGenericMethod && drType is IrSimpleType && !isUnspecialised(drType, logger) ) { val calleeIsInvoke = isFunctionInvoke(callTarget, drType) val extractionMethod = if (calleeIsInvoke) getFunctionInvokeMethod(drType.arguments) else callTarget return extractionMethod?.let { val typeArgs = if (calleeIsInvoke && drType.arguments.size > BuiltInFunctionArity.BIG_ARITY) { // Big arity `invoke` methods have a special implementation on JVM, they are // transformed to a call to // `kotlin.jvm.functions.FunctionN::invoke(vararg args: Any?)`, so we // only need to pass the type // argument for the return type. Additionally, the arguments are extracted // inside an array literal below. listOf(drType.arguments.last()) } else { getDeclaringTypeArguments(callTarget, drType) } useFunction(extractionMethod, typeArgs) } } else { return useFunction(callTarget) } } private fun getCalleeRealOverrideTarget(f: IrFunction): IrFunction { val target = f.target.realOverrideTarget return if (overridesCollectionsMethodWithAlteredParameterTypes(f)) // Cope with the case where an inherited callee can be rewritten with substituted parameter // types // if the child class uses it to implement a collections interface // (for example, `class A { boolean contains(Object o) { ... } }; class B extends A // implements Set { ... }` // leads to generating a function `A.contains(B::T)`, with `initialSignatureFunction` // pointing to `A.contains(Object)`. (target as? IrLazyFunction)?.initialSignatureFunction ?: target else target } private fun callUsesDefaultArguments( callTarget: IrFunction, valueArguments: List ): Boolean { val varargParam = callTarget.valueParameters.withIndex().find { it.value.isVararg } // If the vararg param is the only one not specified, and it has no default value, then we // don't need to call a $default method, // as omitting it already implies passing an empty vararg array. val nullAllowedIdx = if (varargParam != null && varargParam.value.defaultValue == null) varargParam.index else -1 return valueArguments.withIndex().any { (index, it) -> it == null && index != nullAllowedIdx } } fun extractRawMethodAccess( syntacticCallTarget: IrFunction, locElement: IrElement, resultType: IrType, enclosingCallable: Label, callsiteParent: Label, childIdx: Int, enclosingStmt: Label, valueArguments: List, dispatchReceiver: IrExpression?, extensionReceiver: IrExpression?, typeArguments: List = listOf(), extractClassTypeArguments: Boolean = false, superQualifierSymbol: IrClassSymbol? = null ) { val locId = tw.getLocation(locElement) if (callUsesDefaultArguments(syntacticCallTarget, valueArguments)) { extractsDefaultsCall( syntacticCallTarget, locId, resultType, enclosingCallable, callsiteParent, childIdx, enclosingStmt, valueArguments, dispatchReceiver, extensionReceiver ) } else { extractRawMethodAccess( syntacticCallTarget, locId, resultType, enclosingCallable, callsiteParent, childIdx, enclosingStmt, valueArguments.size, { argParent, idxOffset -> extractCallValueArguments( argParent, valueArguments, enclosingStmt, enclosingCallable, idxOffset ) }, dispatchReceiver?.type, dispatchReceiver?.let { { callId -> extractExpressionExpr( dispatchReceiver, enclosingCallable, callId, -1, enclosingStmt ) } }, extensionReceiver?.let { { argParent -> extractExpressionExpr( extensionReceiver, enclosingCallable, argParent, 0, enclosingStmt ) } }, typeArguments, extractClassTypeArguments, superQualifierSymbol ) } } fun extractRawMethodAccess( syntacticCallTarget: IrFunction, locId: Label, returnType: IrType, enclosingCallable: Label, callsiteParent: Label, childIdx: Int, enclosingStmt: Label, nValueArguments: Int, extractValueArguments: (Label, Int) -> Unit, drType: IrType?, extractDispatchReceiver: ((Label) -> Unit)?, extractExtensionReceiver: ((Label) -> Unit)?, typeArguments: List = listOf(), extractClassTypeArguments: Boolean = false, superQualifierSymbol: IrClassSymbol? = null ) { val callTarget = getCalleeRealOverrideTarget(syntacticCallTarget) val methodId = getCalleeMethodId(callTarget, drType, extractClassTypeArguments) if (methodId == null) { logger.warn("No method to bind call to for raw method access") } val id = extractMethodAccessWithoutArgs( returnType, locId, enclosingCallable, callsiteParent, childIdx, enclosingStmt, methodId ) // type arguments at index -2, -3, ... extractTypeArguments(typeArguments, locId, id, enclosingCallable, enclosingStmt, -2, true) if (callTarget.isLocalFunction()) { extractNewExprForLocalFunction( getLocallyVisibleFunctionLabels(callTarget), id, locId, enclosingCallable, enclosingStmt ) } else if (callTarget.shouldExtractAsStatic) { extractStaticTypeAccessQualifier( callTarget, id, locId, enclosingCallable, enclosingStmt ) } else if (superQualifierSymbol != null) { extractSuperAccess( superQualifierSymbol.typeWith(), enclosingCallable, id, -1, enclosingStmt, locId ) } else if (extractDispatchReceiver != null) { extractDispatchReceiver(id) } val idxOffset = if (extractExtensionReceiver != null) 1 else 0 val isBigArityFunctionInvoke = drType is IrSimpleType && isFunctionInvoke(callTarget, drType) && drType.arguments.size > BuiltInFunctionArity.BIG_ARITY val argParent = if (isBigArityFunctionInvoke) { extractArrayCreationWithInitializer( id, nValueArguments + idxOffset, locId, enclosingCallable, enclosingStmt ) } else { id } if (extractExtensionReceiver != null) { extractExtensionReceiver(argParent) } extractValueArguments(argParent, idxOffset) } private fun extractStaticTypeAccessQualifierUnchecked( target: IrDeclaration, parentExpr: Label, locId: Label, enclosingCallable: Label?, enclosingStmt: Label? ) { val parent = target.parent if (parent is IrExternalPackageFragment) { // This is in a file class. val fqName = getFileClassFqName(target) if (fqName == null) { logger.error( "Can't get FqName for static type access qualifier in external package fragment ${target.javaClass}" ) } else { extractTypeAccess( useFileClassType(fqName), locId, parentExpr, -1, enclosingCallable, enclosingStmt ) } } else if (parent is IrClass) { extractTypeAccessRecursive( parent.toRawType(), locId, parentExpr, -1, enclosingCallable, enclosingStmt ) } else if (parent is IrFile) { extractTypeAccess( useFileClassType(parent), locId, parentExpr, -1, enclosingCallable, enclosingStmt ) } else { logger.warnElement( "Unexpected static type access qualifier ${parent.javaClass}", parent ) } } private fun extractStaticTypeAccessQualifier( target: IrDeclaration, parentExpr: Label, locId: Label, enclosingCallable: Label?, enclosingStmt: Label? ) { if (target.shouldExtractAsStatic) { extractStaticTypeAccessQualifierUnchecked( target, parentExpr, locId, enclosingCallable, enclosingStmt ) } } private fun isStaticAnnotatedNonCompanionMember(f: IrSimpleFunction) = f.parentClassOrNull?.isNonCompanionObject == true && (f.hasAnnotation(jvmStaticFqName) || f.correspondingPropertySymbol?.owner?.hasAnnotation(jvmStaticFqName) == true) private val IrDeclaration.shouldExtractAsStatic: Boolean get() = this is IrSimpleFunction && (isStaticFunction(this) || isStaticAnnotatedNonCompanionMember(this)) || this is IrField && this.isStatic || this is IrEnumEntry private fun extractCallValueArguments( callId: Label, call: IrFunctionAccessExpression, enclosingStmt: Label, enclosingCallable: Label, idxOffset: Int ) = extractCallValueArguments( callId, (0 until call.valueArgumentsCount).map { call.getValueArgument(it) }, enclosingStmt, enclosingCallable, idxOffset ) private fun extractCallValueArguments( callId: Label, valueArguments: List, enclosingStmt: Label, enclosingCallable: Label, idxOffset: Int, extractVarargAsArray: Boolean = false ) { var i = 0 valueArguments.forEach { arg -> if (arg != null) { if (arg is IrVararg && !extractVarargAsArray) { arg.elements.forEachIndexed { varargNo, vararg -> extractVarargElement( vararg, enclosingCallable, callId, i + idxOffset + varargNo, enclosingStmt ) } i += arg.elements.size } else { extractExpressionExpr( arg, enclosingCallable, callId, (i++) + idxOffset, enclosingStmt ) } } } } private fun findFunction(cls: IrClass, name: String): IrFunction? = cls.declarations.findSubType { it.name.asString() == name } val jvmIntrinsicsClass by lazy { referenceExternalClass("kotlin.jvm.internal.Intrinsics") } private fun findJdkIntrinsicOrWarn(name: String, warnAgainstElement: IrElement): IrFunction? { val result = jvmIntrinsicsClass?.let { findFunction(it, name) } if (result == null) { logger.errorElement("Couldn't find JVM intrinsic function $name", warnAgainstElement) } return result } private fun findTopLevelFunctionOrWarn( functionPkg: String, functionName: String, type: String, parameterTypes: Array, warnAgainstElement: IrElement ): IrFunction? { val fn = getFunctionsByFqName(pluginContext, functionPkg, functionName) .firstOrNull { fnSymbol -> val owner = fnSymbol.owner (owner.parentClassOrNull?.fqNameWhenAvailable?.asString() == type || (owner.parent is IrExternalPackageFragment && getFileClassFqName(owner)?.asString() == type)) && owner.valueParameters .map { it.type.classFqName?.asString() } .toTypedArray() contentEquals parameterTypes } ?.owner if (fn != null) { if (fn.parentClassOrNull != null) { extractExternalClassLater(fn.parentAsClass) } } else { logger.errorElement( "Couldn't find JVM intrinsic function $functionPkg $functionName in $type", warnAgainstElement ) } return fn } private fun findTopLevelPropertyOrWarn( propertyPkg: String, propertyName: String, type: String, warnAgainstElement: IrElement ): IrProperty? { val prop = getPropertiesByFqName(pluginContext, propertyPkg, propertyName) .firstOrNull { it.owner.parentClassOrNull?.fqNameWhenAvailable?.asString() == type } ?.owner if (prop != null) { if (prop.parentClassOrNull != null) { extractExternalClassLater(prop.parentAsClass) } } else { logger.errorElement( "Couldn't find JVM intrinsic property $propertyPkg $propertyName in $type", warnAgainstElement ) } return prop } val javaLangString by lazy { referenceExternalClass("java.lang.String") } val stringValueOfObjectMethod by lazy { val result = javaLangString?.declarations?.findSubType { it.name.asString() == "valueOf" && it.valueParameters.size == 1 && it.valueParameters[0].type == pluginContext.irBuiltIns.anyNType } if (result == null) { logger.error("Couldn't find declaration java.lang.String.valueOf(Object)") } result } val objectCloneMethod by lazy { val result = javaLangObject?.declarations?.findSubType { it.name.asString() == "clone" } if (result == null) { logger.error("Couldn't find declaration java.lang.Object.clone(...)") } result } val kotlinNoWhenBranchMatchedExn by lazy { referenceExternalClass("kotlin.NoWhenBranchMatchedException") } val kotlinNoWhenBranchMatchedConstructor by lazy { val result = kotlinNoWhenBranchMatchedExn?.declarations?.findSubType { it.valueParameters.isEmpty() } if (result == null) { logger.error("Couldn't find no-arg constructor for kotlin.NoWhenBranchMatchedException") } result } val javaUtilArrays by lazy { referenceExternalClass("java.util.Arrays") } private fun isFunction( target: IrFunction, pkgName: String, classNameLogged: String, classNamePredicate: (String) -> Boolean, vararg fNames: String, isNullable: Boolean? = false ) = fNames.any { isFunction(target, pkgName, classNameLogged, classNamePredicate, it, isNullable) } private fun isFunction( target: IrFunction, pkgName: String, classNameLogged: String, classNamePredicate: (String) -> Boolean, fName: String, isNullable: Boolean? = false ): Boolean { val verbose = false fun verboseln(s: String) { if (verbose) println(s) } verboseln("Attempting match for $pkgName $classNameLogged $fName") if (target.name.asString() != fName) { verboseln("No match as function name is ${target.name.asString()} not $fName") return false } val extensionReceiverParameter = target.extensionReceiverParameter val targetClass = if (extensionReceiverParameter == null) { if (isNullable == true) { verboseln( "Nullablility of type didn't match (target is not an extension method)" ) return false } target.parent } else { val st = extensionReceiverParameter.type as? IrSimpleType if (isNullable != null && st?.isNullableCodeQL() != isNullable) { verboseln("Nullablility of type didn't match") return false } st?.classifier?.owner } if (targetClass !is IrClass) { verboseln("No match as didn't find target class") return false } if (!classNamePredicate(targetClass.name.asString())) { verboseln( "No match as class name is ${targetClass.name.asString()} not $classNameLogged" ) return false } val targetPkg = targetClass.parent if (targetPkg !is IrPackageFragment) { verboseln("No match as didn't find target package") return false } val targetName = targetPkg.packageFqName.asString() if (targetName != pkgName) { verboseln("No match as package name is $targetName not $pkgName") return false } verboseln("Match") return true } private fun isFunction( target: IrFunction, pkgName: String, className: String, fName: String, isNullable: Boolean? = false ) = isFunction(target, pkgName, className, { it == className }, fName, isNullable) private fun isNumericFunction(target: IrFunction, fName: String): Boolean { return isFunction(target, "kotlin", "Int", fName) || isFunction(target, "kotlin", "Byte", fName) || isFunction(target, "kotlin", "Short", fName) || isFunction(target, "kotlin", "Long", fName) || isFunction(target, "kotlin", "Float", fName) || isFunction(target, "kotlin", "Double", fName) } private fun isNumericFunction(target: IrFunction, vararg fNames: String) = fNames.any { isNumericFunction(target, it) } private fun isArrayType(typeName: String) = when (typeName) { "Array" -> true "IntArray" -> true "ByteArray" -> true "ShortArray" -> true "LongArray" -> true "FloatArray" -> true "DoubleArray" -> true "CharArray" -> true "BooleanArray" -> true else -> false } private fun isGenericArrayType(typeName: String) = when (typeName) { "Array" -> true else -> false } private fun extractCall( c: IrCall, callable: Label, stmtExprParent: StmtExprParent ) { with("call", c) { val owner = getBoundSymbolOwner(c.symbol, c) ?: return val target = tryReplaceSyntheticFunction(owner) // The vast majority of types of call want an expr context, so make one available // lazily: val exprParent by lazy { stmtExprParent.expr(c, callable) } val parent by lazy { exprParent.parent } val idx by lazy { exprParent.idx } val enclosingStmt by lazy { exprParent.enclosingStmt } fun extractMethodAccess( syntacticCallTarget: IrFunction, extractMethodTypeArguments: Boolean = true, extractClassTypeArguments: Boolean = false ) { val typeArgs = if (extractMethodTypeArguments) (0 until c.typeArgumentsCount) .map { c.getTypeArgument(it) } .requireNoNullsOrNull() else listOf() if (typeArgs == null) { logger.warn("Missing type argument in extractMethodAccess") return } extractRawMethodAccess( syntacticCallTarget, c, c.type, callable, parent, idx, enclosingStmt, (0 until c.valueArgumentsCount).map { c.getValueArgument(it) }, c.dispatchReceiver, c.extensionReceiver, typeArgs, extractClassTypeArguments, c.superQualifierSymbol ) } fun extractSpecialEnumFunction(fnName: String) { if (c.typeArgumentsCount != 1) { logger.errorElement("Expected to find exactly one type argument", c) return } val enumType = (c.getTypeArgument(0) as? IrSimpleType)?.classifier?.owner if (enumType == null) { logger.errorElement("Couldn't find type of enum type", c) return } if (enumType is IrClass) { val func = enumType.declarations.findSubType { it.name.asString() == fnName } if (func == null) { logger.errorElement("Couldn't find function $fnName on enum type", c) return } extractMethodAccess(func, false) } else if (enumType is IrTypeParameter && enumType.isReified) { // A call to `enumValues()` is being extracted, where `T` is a reified type // parameter of an `inline` function. // We can't generate a valid expression here, because we would need to know the // type of T on the call site. // TODO: replace error expression with something that better shows this // expression is unrepresentable. val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_errorexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, tw.getLocation(c), callable, enclosingStmt) } else { logger.errorElement("Unexpected enum type rep ${enumType.javaClass}", c) } } fun binopReceiver( id: Label, receiver: IrExpression?, receiverDescription: String ) { extractExprContext(id, tw.getLocation(c), callable, enclosingStmt) if (receiver == null) { logger.errorElement("$receiverDescription not found", c) } else { extractExpressionExpr(receiver, callable, id, 0, enclosingStmt) } if (c.valueArgumentsCount < 1) { logger.errorElement("No RHS found", c) } else { if (c.valueArgumentsCount > 1) { logger.errorElement("Extra arguments found", c) } val arg = c.getValueArgument(0) if (arg == null) { logger.errorElement("RHS null", c) } else { extractExpressionExpr(arg, callable, id, 1, enclosingStmt) } } } fun unaryopReceiver( id: Label, receiver: IrExpression?, receiverDescription: String ) { extractExprContext(id, tw.getLocation(c), callable, enclosingStmt) if (receiver == null) { logger.errorElement("$receiverDescription not found", c) } else { extractExpressionExpr(receiver, callable, id, 0, enclosingStmt) } if (c.valueArgumentsCount > 0) { logger.errorElement("Extra arguments found", c) } } /** * Populate the lhs of a binary op from this call's dispatch receiver, and the rhs from * its sole argument. */ fun binopDisp(id: Label) { binopReceiver(id, c.dispatchReceiver, "Dispatch receiver") } fun binopExt(id: Label) { binopReceiver(id, c.extensionReceiver, "Extension receiver") } fun unaryopDisp(id: Label) { unaryopReceiver(id, c.dispatchReceiver, "Dispatch receiver") } fun unaryopExt(id: Label) { unaryopReceiver(id, c.extensionReceiver, "Extension receiver") } val dr = c.dispatchReceiver when { isFunction(target, "kotlin", "String", "plus", false) -> { val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_addexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) binopDisp(id) } isFunction(target, "kotlin", "String", "plus", true) -> { findJdkIntrinsicOrWarn("stringPlus", c)?.let { stringPlusFn -> extractRawMethodAccess( stringPlusFn, c, c.type, callable, parent, idx, enclosingStmt, listOf(c.extensionReceiver, c.getValueArgument(0)), null, null ) } } isNumericFunction( target, "plus", "minus", "times", "div", "rem", "and", "or", "xor", "shl", "shr", "ushr" ) -> { val type = useType(c.type) val id: Label = when (val targetName = target.name.asString()) { "plus" -> { val id = tw.getFreshIdLabel() tw.writeExprs_addexpr(id, type.javaResult.id, parent, idx) id } "minus" -> { val id = tw.getFreshIdLabel() tw.writeExprs_subexpr(id, type.javaResult.id, parent, idx) id } "times" -> { val id = tw.getFreshIdLabel() tw.writeExprs_mulexpr(id, type.javaResult.id, parent, idx) id } "div" -> { val id = tw.getFreshIdLabel() tw.writeExprs_divexpr(id, type.javaResult.id, parent, idx) id } "rem" -> { val id = tw.getFreshIdLabel() tw.writeExprs_remexpr(id, type.javaResult.id, parent, idx) id } "and" -> { val id = tw.getFreshIdLabel() tw.writeExprs_andbitexpr(id, type.javaResult.id, parent, idx) id } "or" -> { val id = tw.getFreshIdLabel() tw.writeExprs_orbitexpr(id, type.javaResult.id, parent, idx) id } "xor" -> { val id = tw.getFreshIdLabel() tw.writeExprs_xorbitexpr(id, type.javaResult.id, parent, idx) id } "shl" -> { val id = tw.getFreshIdLabel() tw.writeExprs_lshiftexpr(id, type.javaResult.id, parent, idx) id } "shr" -> { val id = tw.getFreshIdLabel() tw.writeExprs_rshiftexpr(id, type.javaResult.id, parent, idx) id } "ushr" -> { val id = tw.getFreshIdLabel() tw.writeExprs_urshiftexpr(id, type.javaResult.id, parent, idx) id } else -> { logger.errorElement("Unhandled binary target name: $targetName", c) return } } tw.writeExprsKotlinType(id, type.kotlinResult.id) if ( isFunction( target, "kotlin", "Byte or Short", { it == "Byte" || it == "Short" }, "and", "or", "xor" ) ) binopExt(id) else binopDisp(id) } // != gets desugared into not and ==. Here we resugar it. c.origin == IrStatementOrigin.EXCLEQ && isFunction(target, "kotlin", "Boolean", "not") && c.valueArgumentsCount == 0 && dr != null && dr is IrCall && isBuiltinCallInternal(dr, "EQEQ") -> { val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_valueneexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) binOp(id, dr, callable, enclosingStmt) } c.origin == IrStatementOrigin.EXCLEQEQ && isFunction(target, "kotlin", "Boolean", "not") && c.valueArgumentsCount == 0 && dr != null && dr is IrCall && isBuiltinCallInternal(dr, "EQEQEQ") -> { val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_neexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) binOp(id, dr, callable, enclosingStmt) } c.origin == IrStatementOrigin.EXCLEQ && isFunction(target, "kotlin", "Boolean", "not") && c.valueArgumentsCount == 0 && dr != null && dr is IrCall && isBuiltinCallInternal(dr, "ieee754equals") -> { val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_neexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) binOp(id, dr, callable, enclosingStmt) } isFunction(target, "kotlin", "Boolean", "not") -> { val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_lognotexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) unaryopDisp(id) } isNumericFunction(target, "inv", "unaryMinus", "unaryPlus") -> { val type = useType(c.type) val id: Label = when (val targetName = target.name.asString()) { "inv" -> { val id = tw.getFreshIdLabel() tw.writeExprs_bitnotexpr(id, type.javaResult.id, parent, idx) id } "unaryMinus" -> { val id = tw.getFreshIdLabel() tw.writeExprs_minusexpr(id, type.javaResult.id, parent, idx) id } "unaryPlus" -> { val id = tw.getFreshIdLabel() tw.writeExprs_plusexpr(id, type.javaResult.id, parent, idx) id } else -> { logger.errorElement("Unhandled unary target name: $targetName", c) return } } tw.writeExprsKotlinType(id, type.kotlinResult.id) if ( isFunction( target, "kotlin", "Byte or Short", { it == "Byte" || it == "Short" }, "inv" ) ) unaryopExt(id) else unaryopDisp(id) } // We need to handle all the builtin operators defines in BuiltInOperatorNames in // compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/IrBuiltIns.kt // as they can't be extracted as external dependencies. isBuiltinCallInternal(c, "less") -> { if (c.origin != IrStatementOrigin.LT) { logger.warnElement("Unexpected origin for LT: ${c.origin}", c) } val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_ltexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) binOp(id, c, callable, enclosingStmt) } isBuiltinCallInternal(c, "lessOrEqual") -> { if (c.origin != IrStatementOrigin.LTEQ) { logger.warnElement("Unexpected origin for LTEQ: ${c.origin}", c) } val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_leexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) binOp(id, c, callable, enclosingStmt) } isBuiltinCallInternal(c, "greater") -> { if (c.origin != IrStatementOrigin.GT) { logger.warnElement("Unexpected origin for GT: ${c.origin}", c) } val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_gtexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) binOp(id, c, callable, enclosingStmt) } isBuiltinCallInternal(c, "greaterOrEqual") -> { if (c.origin != IrStatementOrigin.GTEQ) { logger.warnElement("Unexpected origin for GTEQ: ${c.origin}", c) } val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_geexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) binOp(id, c, callable, enclosingStmt) } isBuiltinCallInternal(c, "EQEQ") -> { if (c.origin != IrStatementOrigin.EQEQ) { logger.warnElement("Unexpected origin for EQEQ: ${c.origin}", c) } val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_valueeqexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) binOp(id, c, callable, enclosingStmt) } isBuiltinCallInternal(c, "EQEQEQ") -> { if (c.origin != IrStatementOrigin.EQEQEQ) { logger.warnElement("Unexpected origin for EQEQEQ: ${c.origin}", c) } val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_eqexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) binOp(id, c, callable, enclosingStmt) } isBuiltinCallInternal(c, "ieee754equals") -> { if (c.origin != IrStatementOrigin.EQEQ) { logger.warnElement("Unexpected origin for ieee754equals: ${c.origin}", c) } val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_eqexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) binOp(id, c, callable, enclosingStmt) } isBuiltinCallInternal(c, "CHECK_NOT_NULL") -> { if (c.origin != IrStatementOrigin.EXCLEXCL) { logger.warnElement("Unexpected origin for CHECK_NOT_NULL: ${c.origin}", c) } val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_notnullexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) unaryOp(id, c, callable, enclosingStmt) } isBuiltinCallInternal(c, "THROW_CCE") -> { // TODO logger.errorElement("Unhandled builtin", c) } isBuiltinCallInternal(c, "THROW_ISE") -> { // TODO logger.errorElement("Unhandled builtin", c) } isBuiltinCallInternal(c, "noWhenBranchMatchedException") -> { kotlinNoWhenBranchMatchedConstructor?.let { val locId = tw.getLocation(c) val thrownType = useSimpleTypeClass(it.parentAsClass, listOf(), false) val stmtParent = stmtExprParent.stmt(c, callable) val throwId = tw.getFreshIdLabel() tw.writeStmts_throwstmt( throwId, stmtParent.parent, stmtParent.idx, callable ) tw.writeHasLocation(throwId, locId) val newExprId = extractNewExpr( it, null, thrownType, locId, throwId, 0, callable, throwId ) if (newExprId == null) { logger.errorElement( "No ID for newExpr in noWhenBranchMatchedException", c ) } else { extractTypeAccess(thrownType, locId, newExprId, -3, callable, throwId) } } } isBuiltinCallInternal(c, "illegalArgumentException") -> { // TODO logger.errorElement("Unhandled builtin", c) } isBuiltinCallInternal(c, "ANDAND") -> { // TODO logger.errorElement("Unhandled builtin", c) } isBuiltinCallInternal(c, "OROR") -> { // TODO logger.errorElement("Unhandled builtin", c) } isFunction(target, "kotlin", "Any", "toString", true) -> { stringValueOfObjectMethod?.let { extractRawMethodAccess( it, c, c.type, callable, parent, idx, enclosingStmt, listOf(c.extensionReceiver), null, null ) } } isBuiltinCallKotlin(c, "enumValues") -> { extractSpecialEnumFunction("values") } isBuiltinCallKotlin(c, "enumValueOf") -> { extractSpecialEnumFunction("valueOf") } isBuiltinCallKotlin(c, "arrayOfNulls") -> { val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_arraycreationexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) val locId = tw.getLocation(c) extractExprContext(id, locId, callable, enclosingStmt) if (c.typeArgumentsCount == 1) { val typeArgument = c.getTypeArgument(0) if (typeArgument == null) { logger.errorElement("Type argument missing in an arrayOfNulls call", c) } else { extractTypeAccessRecursive( typeArgument, locId, id, -1, callable, enclosingStmt, TypeContext.GENERIC_ARGUMENT ) } } else { logger.errorElement( "Expected to find exactly one type argument in an arrayOfNulls call", c ) } if (c.valueArgumentsCount == 1) { val dim = c.getValueArgument(0) if (dim != null) { extractExpressionExpr(dim, callable, id, 0, enclosingStmt) } else { logger.errorElement( "Expected to find non-null argument in an arrayOfNulls call", c ) } } else { logger.errorElement( "Expected to find only one argument in an arrayOfNulls call", c ) } } isBuiltinCallKotlin(c, "arrayOf") || isBuiltinCallKotlin(c, "doubleArrayOf") || isBuiltinCallKotlin(c, "floatArrayOf") || isBuiltinCallKotlin(c, "longArrayOf") || isBuiltinCallKotlin(c, "intArrayOf") || isBuiltinCallKotlin(c, "charArrayOf") || isBuiltinCallKotlin(c, "shortArrayOf") || isBuiltinCallKotlin(c, "byteArrayOf") || isBuiltinCallKotlin(c, "booleanArrayOf") -> { val isPrimitiveArrayCreation = !isBuiltinCallKotlin(c, "arrayOf") val elementType = if (isPrimitiveArrayCreation) { c.type.getArrayElementTypeCodeQL(pluginContext.irBuiltIns) } else { // TODO: is there any reason not to always use getArrayElementTypeCodeQL? if (c.typeArgumentsCount == 1) { c.getTypeArgument(0).also { if (it == null) { logger.errorElement( "Type argument missing in an arrayOf call", c ) } } } else { logger.errorElement( "Expected to find one type argument in arrayOf call", c ) null } } val arg = if (c.valueArgumentsCount == 1) c.getValueArgument(0) else { logger.errorElement( "Expected to find only one (vararg) argument in ${c.symbol.owner.name.asString()} call", c ) null } ?.let { if (it is IrVararg) it else { logger.errorElement( "Expected to find vararg argument in ${c.symbol.owner.name.asString()} call", c ) null } } extractArrayCreation( arg, c.type, elementType, isPrimitiveArrayCreation, c, parent, idx, callable, enclosingStmt ) } isBuiltinCall(c, "", "kotlin.jvm") -> { // Special case for KClass<*>.java, which is used in the Parcelize plugin. In // normal cases, this is already rewritten to the property referenced below: findTopLevelPropertyOrWarn( "kotlin.jvm", "java", "kotlin.jvm.JvmClassMappingKt", c ) ?.let { javaProp -> val getter = javaProp.getter if (getter == null) { logger.error( "Couldn't find getter of `kotlin.jvm.JvmClassMappingKt::java`" ) return } val ext = c.extensionReceiver if (ext == null) { logger.errorElement( "No extension receiver found for `KClass::java` call", c ) return } val argType = (ext.type as? IrSimpleType)?.arguments?.firstOrNull()?.typeOrNull val typeArguments = if (argType == null) listOf() else listOf(argType) extractRawMethodAccess( getter, c, c.type, callable, parent, idx, enclosingStmt, listOf(), null, ext, typeArguments ) } } isFunction( target, "kotlin", "(some array type)", { isArrayType(it) }, "iterator" ) -> { val parentClass = target.parent if (parentClass !is IrClass) { logger.errorElement("Iterator parent is not a class", c) return } var typeFilter = if (isGenericArrayType(parentClass.name.asString())) { "kotlin.jvm.internal.ArrayIteratorKt" } else { "kotlin.jvm.internal.ArrayIteratorsKt" } findTopLevelFunctionOrWarn( "kotlin.jvm.internal", "iterator", typeFilter, arrayOf(parentClass.kotlinFqName.asString()), c ) ?.let { iteratorFn -> val dispatchReceiver = c.dispatchReceiver if (dispatchReceiver == null) { logger.errorElement( "No dispatch receiver found for array iterator call", c ) } else { val drType = dispatchReceiver.type if (drType !is IrSimpleType) { logger.errorElement( "Dispatch receiver with unexpected type rep found for array iterator call: ${drType.javaClass}", c ) } else { val typeArgs = drType.arguments.map { when (it) { is IrTypeProjection -> it.type else -> pluginContext.irBuiltIns.anyNType } } extractRawMethodAccess( iteratorFn, c, c.type, callable, parent, idx, enclosingStmt, listOf(c.dispatchReceiver), null, null, typeArgs ) } } } } isFunction(target, "kotlin", "(some array type)", { isArrayType(it) }, "get") && c.origin == IrStatementOrigin.GET_ARRAY_ELEMENT && c.dispatchReceiver != null -> { val id = tw.getFreshIdLabel() val type = useType(c.type) tw.writeExprs_arrayaccess(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) binopDisp(id) } isFunction(target, "kotlin", "(some array type)", { isArrayType(it) }, "set") && c.origin == IrStatementOrigin.EQ && c.dispatchReceiver != null -> { val array = c.dispatchReceiver val arrayIdx = c.getValueArgument(0) val assignedValue = c.getValueArgument(1) if (array != null && arrayIdx != null && assignedValue != null) { val locId = tw.getLocation(c) extractAssignExpr(c.type, locId, parent, idx, callable, enclosingStmt) .also { assignId -> tw.getFreshIdLabel().also { arrayAccessId -> val arrayType = useType(array.type) tw.writeExprs_arrayaccess( arrayAccessId, arrayType.javaResult.id, assignId, 0 ) tw.writeExprsKotlinType( arrayAccessId, arrayType.kotlinResult.id ) extractExprContext( arrayAccessId, locId, callable, enclosingStmt ) extractExpressionExpr( array, callable, arrayAccessId, 0, enclosingStmt ) extractExpressionExpr( arrayIdx, callable, arrayAccessId, 1, enclosingStmt ) } extractExpressionExpr( assignedValue, callable, assignId, 1, enclosingStmt ) } } else { logger.errorElement("Unexpected Array.set function signature", c) } } isBuiltinCall(c, "", "kotlin.jvm.internal") -> { if (c.valueArgumentsCount != 1) { logger.errorElement( "Expected to find one argument for a kotlin.jvm.internal.() call, but found ${c.valueArgumentsCount}", c ) return } if (c.typeArgumentsCount != 2) { logger.errorElement( "Expected to find two type arguments for a kotlin.jvm.internal.() call, but found ${c.typeArgumentsCount}", c ) return } val valueArg = c.getValueArgument(0) if (valueArg == null) { logger.errorElement( "Cannot find value argument for a kotlin.jvm.internal.() call", c ) return } val typeArg = c.getTypeArgument(1) if (typeArg == null) { logger.errorElement( "Cannot find type argument for a kotlin.jvm.internal.() call", c ) return } val id = tw.getFreshIdLabel() val locId = tw.getLocation(c) val type = useType(c.type) tw.writeExprs_unsafecoerceexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, enclosingStmt) extractTypeAccessRecursive(typeArg, locId, id, 0, callable, enclosingStmt) extractExpressionExpr(valueArg, callable, id, 1, enclosingStmt) } isBuiltinCallInternal(c, "dataClassArrayMemberToString") -> { val arrayArg = c.getValueArgument(0) val realArrayClass = arrayArg?.type?.classOrNull if (realArrayClass == null) { logger.errorElement( "Argument to dataClassArrayMemberToString not a class", c ) return } val realCallee = javaUtilArrays?.declarations?.findSubType { decl -> decl.name.asString() == "toString" && decl.valueParameters.size == 1 && decl.valueParameters[0].type.classOrNull?.let { it == realArrayClass } == true } if (realCallee == null) { logger.errorElement( "Couldn't find a java.lang.Arrays.toString method matching class ${realArrayClass.owner.name}", c ) } else { extractRawMethodAccess( realCallee, c, c.type, callable, parent, idx, enclosingStmt, listOf(arrayArg), null, null ) } } isBuiltinCallInternal(c, "dataClassArrayMemberHashCode") -> { val arrayArg = c.getValueArgument(0) val realArrayClass = arrayArg?.type?.classOrNull if (realArrayClass == null) { logger.errorElement( "Argument to dataClassArrayMemberHashCode not a class", c ) return } val realCallee = javaUtilArrays?.declarations?.findSubType { decl -> decl.name.asString() == "hashCode" && decl.valueParameters.size == 1 && decl.valueParameters[0].type.classOrNull?.let { it == realArrayClass } == true } if (realCallee == null) { logger.errorElement( "Couldn't find a java.lang.Arrays.hashCode method matching class ${realArrayClass.owner.name}", c ) } else { extractRawMethodAccess( realCallee, c, c.type, callable, parent, idx, enclosingStmt, listOf(arrayArg), null, null ) } } else -> { extractMethodAccess(target, true, true) } } } } private fun extractArrayCreation( elementList: IrVararg?, resultType: IrType, elementType: IrType?, allowPrimitiveElementType: Boolean, locElement: IrElement, parent: Label, idx: Int, enclosingCallable: Label, enclosingStmt: Label ) { // If this is [someType]ArrayOf(*x), x, otherwise null val clonedArray = elementList?.let { if (it.elements.size == 1) { val onlyElement = it.elements[0] if (onlyElement is IrSpreadElement) onlyElement.expression else null } else null } if (clonedArray != null) { // This is an array clone: extract is as a call to java.lang.Object.clone objectCloneMethod?.let { extractRawMethodAccess( it, locElement, resultType, enclosingCallable, parent, idx, enclosingStmt, listOf(), clonedArray, null ) } } else { // This is array creation: extract it as a call to new ArrayType[] { ... } val id = tw.getFreshIdLabel() val type = useType(resultType) tw.writeExprs_arraycreationexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) val locId = tw.getLocation(locElement) extractExprContext(id, locId, enclosingCallable, enclosingStmt) if (elementType != null) { val typeContext = if (allowPrimitiveElementType) TypeContext.OTHER else TypeContext.GENERIC_ARGUMENT extractTypeAccessRecursive( elementType, locId, id, -1, enclosingCallable, enclosingStmt, typeContext ) } if (elementList != null) { val initId = tw.getFreshIdLabel() tw.writeExprs_arrayinit(initId, type.javaResult.id, id, -2) tw.writeExprsKotlinType(initId, type.kotlinResult.id) extractExprContext(initId, locId, enclosingCallable, enclosingStmt) elementList.elements.forEachIndexed { i, arg -> extractVarargElement(arg, enclosingCallable, initId, i, enclosingStmt) } extractConstantInteger( elementList.elements.size, locId, id, 0, enclosingCallable, enclosingStmt ) } } } private fun extractNewExpr( methodId: Label, constructedType: TypeResults, locId: Label, parent: Label, idx: Int, callable: Label, enclosingStmt: Label ): Label { val id = tw.getFreshIdLabel() tw.writeExprs_newexpr(id, constructedType.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, constructedType.kotlinResult.id) extractExprContext(id, locId, callable, enclosingStmt) tw.writeCallableBinding(id, methodId) return id } private fun extractNewExpr( calledConstructor: IrFunction, constructorTypeArgs: List?, constructedType: TypeResults, locId: Label, parent: Label, idx: Int, callable: Label, enclosingStmt: Label ): Label? { val funId = useFunction(calledConstructor, constructorTypeArgs) if (funId == null) { logger.error("Cannot get ID for newExpr function") return null } return extractNewExpr(funId, constructedType, locId, parent, idx, callable, enclosingStmt) } private fun needsObinitFunction(c: IrClass) = c.primaryConstructor == null && c.constructors.count() > 1 private fun getObinitLabel(c: IrClass, parentId: Label): String = getFunctionLabel( c, parentId, "", listOf(), pluginContext.irBuiltIns.unitType, null, functionTypeParameters = listOf(), classTypeArgsIncludingOuterClasses = listOf(), overridesCollectionsMethod = false, javaSignature = null, addParameterWildcardsByDefault = false ) private fun extractConstructorCall( e: IrFunctionAccessExpression, parent: Label, idx: Int, callable: Label, enclosingStmt: Label ) { val eType = e.type if (eType !is IrSimpleType) { logger.errorElement("Constructor call has non-simple type ${eType.javaClass}", e) return } val type = useType(eType) val isAnonymous = eType.isAnonymous val locId = tw.getLocation(e) val valueArgs = (0 until e.valueArgumentsCount).map { e.getValueArgument(it) } val id = if ( e !is IrEnumConstructorCall && callUsesDefaultArguments(e.symbol.owner, valueArgs) ) { val defaultsMethodId = getDefaultsMethodLabel(e.symbol.owner) if (defaultsMethodId == null) { logger.errorElement("Cannot get defaults method ID", e) return } extractNewExpr( defaultsMethodId.cast(), type, locId, parent, idx, callable, enclosingStmt ) .also { extractDefaultsCallArguments( it, e.symbol.owner, callable, enclosingStmt, valueArgs, null, null ) } } else { val newExprId = extractNewExpr( e.symbol.owner, eType.arguments, type, locId, parent, idx, callable, enclosingStmt ) if (newExprId == null) { logger.errorElement("Cannot get newExpr ID", e) return } val realCallTarget = e.symbol.owner.realOverrideTarget // Generated constructor calls to kotlin.Enum have no arguments in IR, but the // constructor takes two parameters. if ( e is IrEnumConstructorCall && realCallTarget is IrConstructor && realCallTarget.parentClassOrNull?.fqNameWhenAvailable?.asString() == "kotlin.Enum" && realCallTarget.valueParameters.size == 2 && realCallTarget.valueParameters[0].type == pluginContext.irBuiltIns.stringType && realCallTarget.valueParameters[1].type == pluginContext.irBuiltIns.intType ) { val id0 = extractNull( pluginContext.irBuiltIns.stringType, locId, newExprId, 0, callable, enclosingStmt ) tw.writeCompiler_generated( id0, CompilerGeneratedKinds.ENUM_CONSTRUCTOR_ARGUMENT.kind ) val id1 = extractConstantInteger(0, locId, newExprId, 1, callable, enclosingStmt) tw.writeCompiler_generated( id1, CompilerGeneratedKinds.ENUM_CONSTRUCTOR_ARGUMENT.kind ) } else { extractCallValueArguments(newExprId, e, enclosingStmt, callable, 0) } newExprId } if (isAnonymous) { tw.writeIsAnonymClass(type.javaResult.id.cast(), id) } val dr = e.dispatchReceiver if (dr != null) { extractExpressionExpr(dr, callable, id, -2, enclosingStmt) } val typeAccessType = if (isAnonymous) { val c = eType.classifier.owner if (c !is IrClass) { logger.warnElement("Anonymous type not a class (${c.javaClass})", e) } if ((c as? IrClass)?.superTypes?.size == 1) { useType(c.superTypes.first()) } else { useType(pluginContext.irBuiltIns.anyType) } } else { type } if (e is IrConstructorCall) { extractConstructorTypeAccess( eType, typeAccessType, e.symbol, locId, id, -3, callable, enclosingStmt ) } else if (e is IrEnumConstructorCall) { val enumClass = e.symbol.owner.parent as? IrClass if (enumClass == null) { logger.warnElement("Couldn't find declaring class of enum constructor call", e) return } val args = (0 until e.typeArgumentsCount).map { e.getTypeArgument(it) }.requireNoNullsOrNull() if (args == null) { logger.warnElement("Found null type argument in enum constructor call", e) return } val enumType = enumClass.typeWith(args) extractConstructorTypeAccess( enumType, useType(enumType), e.symbol, locId, id, -3, callable, enclosingStmt ) } else { logger.errorElement("Unexpected constructor call type: ${e.javaClass}", e) } } abstract inner class StmtExprParent { abstract fun stmt(e: IrExpression, callable: Label): StmtParent abstract fun expr(e: IrExpression, callable: Label): ExprParent } inner class StmtParent(val parent: Label, val idx: Int) : StmtExprParent() { override fun stmt(e: IrExpression, callable: Label) = this override fun expr(e: IrExpression, callable: Label) = extractExpressionStmt(tw.getLocation(e), parent, idx, callable).let { id -> ExprParent(id, 0, id) } } inner class ExprParent( val parent: Label, val idx: Int, val enclosingStmt: Label ) : StmtExprParent() { override fun stmt(e: IrExpression, callable: Label): StmtParent { val id = tw.getFreshIdLabel() val type = useType(e.type) val locId = tw.getLocation(e) tw.writeExprs_stmtexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, enclosingStmt) return StmtParent(id, 0) } override fun expr(e: IrExpression, callable: Label): ExprParent { return this } } private fun getStatementOriginOperator(origin: IrStatementOrigin?) = when (origin) { IrStatementOrigin.PLUSEQ -> "plus" IrStatementOrigin.MINUSEQ -> "minus" IrStatementOrigin.MULTEQ -> "times" IrStatementOrigin.DIVEQ -> "div" IrStatementOrigin.PERCEQ -> "rem" else -> null } private fun getUpdateInPlaceRHS( origin: IrStatementOrigin?, isExpectedLhs: (IrExpression?) -> Boolean, updateRhs: IrExpression ): IrExpression? { // Check for a desugared in-place update operator, such as "v += e": return getStatementOriginOperator(origin)?.let { if (updateRhs is IrCall && isNumericFunction(updateRhs.symbol.owner, it)) { // Check for an expression like x = get(x).op(e): val opReceiver = updateRhs.dispatchReceiver if (isExpectedLhs(opReceiver)) { updateRhs.getValueArgument(0) } else null } else null } } private fun writeUpdateInPlaceExpr( origin: IrStatementOrigin ): (( tw: TrapWriter, id: Label, type: Label, exprParent: Label, index: Int ) -> Unit)? { when (origin) { IrStatementOrigin.PLUSEQ -> return { tw: TrapWriter, id: Label, type: Label, exprParent: Label, index: Int -> tw.writeExprs_assignaddexpr(id.cast(), type, exprParent, index) } IrStatementOrigin.MINUSEQ -> return { tw: TrapWriter, id: Label, type: Label, exprParent: Label, index: Int -> tw.writeExprs_assignsubexpr(id.cast(), type, exprParent, index) } IrStatementOrigin.MULTEQ -> return { tw: TrapWriter, id: Label, type: Label, exprParent: Label, index: Int -> tw.writeExprs_assignmulexpr(id.cast(), type, exprParent, index) } IrStatementOrigin.DIVEQ -> return { tw: TrapWriter, id: Label, type: Label, exprParent: Label, index: Int -> tw.writeExprs_assigndivexpr(id.cast(), type, exprParent, index) } IrStatementOrigin.PERCEQ -> return { tw: TrapWriter, id: Label, type: Label, exprParent: Label, index: Int -> tw.writeExprs_assignremexpr(id.cast(), type, exprParent, index) } else -> return null } } /** * This method tries to extract a block as an enhanced for loop. It returns true if it succeeds, * and false otherwise. */ private fun tryExtractForLoop( e: IrContainerExpression, callable: Label, parent: StmtExprParent ): Boolean { /* * We're expecting the pattern * { * val iterator = [expr].iterator() * while (iterator.hasNext()) { * val [loopVar] = iterator.next() * [block] * } * } */ if (e.origin != IrStatementOrigin.FOR_LOOP || e.statements.size != 2) { return false } val iteratorVariable = e.statements[0] as? IrVariable val innerWhile = e.statements[1] as? IrWhileLoop if ( iteratorVariable == null || iteratorVariable.origin != IrDeclarationOrigin.FOR_LOOP_ITERATOR || innerWhile == null || innerWhile.origin != IrStatementOrigin.FOR_LOOP_INNER_WHILE ) { return false } val initializer = iteratorVariable.initializer as? IrCall if ( initializer == null || initializer.origin != IrStatementOrigin.FOR_LOOP_ITERATOR || initializer.symbol.owner.name.asString() != "iterator" ) { return false } val expr = initializer.dispatchReceiver val cond = innerWhile.condition as? IrCall val body = innerWhile.body as? IrBlock if ( expr == null || cond == null || cond.origin != IrStatementOrigin.FOR_LOOP_HAS_NEXT || (cond.dispatchReceiver as? IrGetValue)?.symbol?.owner != iteratorVariable || body == null || body.origin != IrStatementOrigin.FOR_LOOP_INNER_WHILE || body.statements.size < 2 ) { return false } val loopVar = body.statements[0] as? IrVariable val nextCall = loopVar?.initializer as? IrCall if ( loopVar == null || !(loopVar.origin == IrDeclarationOrigin.FOR_LOOP_VARIABLE || loopVar.origin == IrDeclarationOrigin.IR_TEMPORARY_VARIABLE) || nextCall == null || nextCall.origin != IrStatementOrigin.FOR_LOOP_NEXT || (nextCall.dispatchReceiver as? IrGetValue)?.symbol?.owner != iteratorVariable ) { return false } val id = extractLoop(innerWhile, null, parent, callable) { p, idx -> tw.getFreshIdLabel().also { tw.writeStmts_enhancedforstmt(it, p, idx, callable) } } extractVariableExpr(loopVar, callable, id, 0, id, extractInitializer = false) extractExpressionExpr(expr, callable, id, 1, id) val block = body.statements[1] as? IrBlock if (body.statements.size == 2 && block != null) { // Extract the body that was given to us by the compiler extractExpressionStmt(block, callable, id, 2) } else { // Extract a block with all but the first (loop variable declaration) statement extractBlock(body, body.statements.takeLast(body.statements.size - 1), id, 2, callable) } return true } /** * This tried to extract a block as an array update. It returns true if it succeeds, and false * otherwise. */ private fun tryExtractArrayUpdate( e: IrContainerExpression, callable: Label, parent: StmtExprParent ): Boolean { /* * We're expecting the pattern * { * val array = e1 * val idx = e2 * array.set(idx, array.get(idx).op(e3)) * } * * If we find it, we'll extract e1[e2] op= e3 (op is +, -, ...) */ if (e.statements.size != 3) return false (e.statements[0] as? IrVariable)?.let { arrayVarDecl -> arrayVarDecl.initializer?.let { arrayVarInitializer -> (e.statements[1] as? IrVariable)?.let { indexVarDecl -> indexVarDecl.initializer?.let { indexVarInitializer -> (e.statements[2] as? IrCall)?.let { arraySetCall -> if ( isFunction( arraySetCall.symbol.owner, "kotlin", "(some array type)", { isArrayType(it) }, "set" ) ) { val updateRhs0 = arraySetCall.getValueArgument(1) if (updateRhs0 == null) { logger.errorElement("Update RHS not found", e) return false } getUpdateInPlaceRHS( e .origin, // Using e.origin not arraySetCall.origin here // distinguishes a compiler-generated block // from a user manually code that looks the // same. { oldValue -> oldValue is IrCall && isFunction( oldValue.symbol.owner, "kotlin", "(some array type)", { typeName -> isArrayType(typeName) }, "get" ) && (oldValue.dispatchReceiver as? IrGetValue)?.let { receiverVal -> receiverVal.symbol.owner == arrayVarDecl.symbol.owner } ?: false }, updateRhs0 ) ?.let { updateRhs -> val origin = e.origin if (origin == null) { logger.errorElement("No origin found", e) return false } val writeUpdateInPlaceExprFun = writeUpdateInPlaceExpr(origin) if (writeUpdateInPlaceExprFun == null) { logger.errorElement("Unexpected origin", e) return false } // Create an assignment skeleton _ op= _ val exprParent = parent.expr(e, callable) val assignId = tw.getFreshIdLabel() val type = useType(arrayVarInitializer.type) val locId = tw.getLocation(e) tw.writeExprsKotlinType(assignId, type.kotlinResult.id) extractExprContext( assignId, locId, callable, exprParent.enclosingStmt ) writeUpdateInPlaceExprFun( tw, assignId, type.javaResult.id, exprParent.parent, exprParent.idx ) // Extract e1[e2] val lhsId = tw.getFreshIdLabel() val elementType = useType(updateRhs.type) tw.writeExprs_arrayaccess( lhsId, elementType.javaResult.id, assignId, 0 ) tw.writeExprsKotlinType(lhsId, elementType.kotlinResult.id) extractExprContext( lhsId, locId, callable, exprParent.enclosingStmt ) extractExpressionExpr( arrayVarInitializer, callable, lhsId, 0, exprParent.enclosingStmt ) extractExpressionExpr( indexVarInitializer, callable, lhsId, 1, exprParent.enclosingStmt ) // Extract e3 extractExpressionExpr( updateRhs, callable, assignId, 1, exprParent.enclosingStmt ) return true } } } } } } } return false } private fun extractExpressionStmt( locId: Label, parent: Label, idx: Int, callable: Label ) = tw.getFreshIdLabel().also { tw.writeStmts_exprstmt(it, parent, idx, callable) tw.writeHasLocation(it, locId) } private fun extractExpressionStmt( e: IrExpression, callable: Label, parent: Label, idx: Int ) { extractExpression(e, callable, StmtParent(parent, idx)) } fun extractExpressionExpr( e: IrExpression, callable: Label, parent: Label, idx: Int, enclosingStmt: Label ) { extractExpression(e, callable, ExprParent(parent, idx, enclosingStmt)) } private fun extractExprContext( id: Label, locId: Label, callable: Label?, enclosingStmt: Label? ) { tw.writeHasLocation(id, locId) callable?.let { tw.writeCallableEnclosingExpr(id, it) } enclosingStmt?.let { tw.writeStatementEnclosingExpr(id, it) } } private fun extractEqualsExpression( locId: Label, parent: Label, idx: Int, callable: Label, enclosingStmt: Label ) = tw.getFreshIdLabel().also { val type = useType(pluginContext.irBuiltIns.booleanType) tw.writeExprs_eqexpr(it, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(it, type.kotlinResult.id) extractExprContext(it, locId, callable, enclosingStmt) } private fun extractAndbitExpression( type: IrType, locId: Label, parent: Label, idx: Int, callable: Label, enclosingStmt: Label ) = tw.getFreshIdLabel().also { val typeResults = useType(type) tw.writeExprs_andbitexpr(it, typeResults.javaResult.id, parent, idx) tw.writeExprsKotlinType(it, typeResults.kotlinResult.id) extractExprContext(it, locId, callable, enclosingStmt) } private fun extractConstantInteger( v: Number, locId: Label, parent: Label, idx: Int, callable: Label?, enclosingStmt: Label?, overrideId: Label? = null ) = exprIdOrFresh(overrideId).also { val type = useType(pluginContext.irBuiltIns.intType) tw.writeExprs_integerliteral(it, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(it, type.kotlinResult.id) tw.writeNamestrings(v.toString(), v.toString(), it) extractExprContext(it, locId, callable, enclosingStmt) } private fun extractNull( t: IrType, locId: Label, parent: Label, idx: Int, callable: Label?, enclosingStmt: Label?, overrideId: Label? = null ) = exprIdOrFresh(overrideId).also { val type = useType(t) // Match Java by using a special for nulls, rather than Kotlin's view of this which is // kotlin.Nothing?, the type that can only contain null. val nullTypeName = "" val javaNullType = tw.getLabelFor( "@\"type;$nullTypeName\"", { tw.writePrimitives(it, nullTypeName) } ) tw.writeExprs_nullliteral(it, javaNullType, parent, idx) tw.writeExprsKotlinType(it, type.kotlinResult.id) extractExprContext(it, locId, callable, enclosingStmt) } private fun extractAssignExpr( type: IrType, locId: Label, parent: Label, idx: Int, callable: Label, enclosingStmt: Label ) = tw.getFreshIdLabel().also { val typeResults = useType(type) tw.writeExprs_assignexpr(it, typeResults.javaResult.id, parent, idx) tw.writeExprsKotlinType(it, typeResults.kotlinResult.id) extractExprContext(it, locId, callable, enclosingStmt) } private fun extractExpression( e: IrExpression, callable: Label, parent: StmtExprParent ) { with("expression", e) { when (e) { is IrDelegatingConstructorCall -> { val stmtParent = parent.stmt(e, callable) val irCallable = declarationStack.peek().first val delegatingClass = e.symbol.owner.parent val currentClass = irCallable.parent if (delegatingClass !is IrClass) { logger.warnElement( "Delegating class isn't a class: " + delegatingClass.javaClass, e ) } if (currentClass !is IrClass) { logger.warnElement( "Current class isn't a class: " + currentClass.javaClass, e ) } val id: Label if (delegatingClass != currentClass) { id = tw.getFreshIdLabel() tw.writeStmts_superconstructorinvocationstmt( id, stmtParent.parent, stmtParent.idx, callable ) } else { id = tw.getFreshIdLabel() tw.writeStmts_constructorinvocationstmt( id, stmtParent.parent, stmtParent.idx, callable ) } val locId = tw.getLocation(e) val methodId = useFunction(e.symbol.owner) if (methodId == null) { logger.errorElement("Cannot get ID for delegating constructor", e) } else { tw.writeCallableBinding(id.cast(), methodId) } tw.writeHasLocation(id, locId) extractCallValueArguments(id, e, id, callable, 0) val dr = e.dispatchReceiver if (dr != null) { extractExpressionExpr(dr, callable, id, -1, id) } // todo: type arguments at index -2, -3, ... } is IrThrow -> { val stmtParent = parent.stmt(e, callable) val id = tw.getFreshIdLabel() val locId = tw.getLocation(e) tw.writeStmts_throwstmt(id, stmtParent.parent, stmtParent.idx, callable) tw.writeHasLocation(id, locId) extractExpressionExpr(e.value, callable, id, 0, id) } is IrBreak -> { val stmtParent = parent.stmt(e, callable) val id = tw.getFreshIdLabel() tw.writeStmts_breakstmt(id, stmtParent.parent, stmtParent.idx, callable) extractBreakContinue(e, id) } is IrContinue -> { val stmtParent = parent.stmt(e, callable) val id = tw.getFreshIdLabel() tw.writeStmts_continuestmt(id, stmtParent.parent, stmtParent.idx, callable) extractBreakContinue(e, id) } is IrReturn -> { val stmtParent = parent.stmt(e, callable) val id = tw.getFreshIdLabel() val locId = tw.getLocation(e) tw.writeStmts_returnstmt(id, stmtParent.parent, stmtParent.idx, callable) tw.writeHasLocation(id, locId) extractExpressionExpr(e.value, callable, id, 0, id) } is IrTry -> { val stmtParent = parent.stmt(e, callable) val id = tw.getFreshIdLabel() val locId = tw.getLocation(e) tw.writeStmts_trystmt(id, stmtParent.parent, stmtParent.idx, callable) tw.writeHasLocation(id, locId) extractExpressionStmt(e.tryResult, callable, id, -1) val finallyStmt = e.finallyExpression if (finallyStmt != null) { extractExpressionStmt(finallyStmt, callable, id, -2) } for ((catchIdx, catchClause) in e.catches.withIndex()) { val catchId = tw.getFreshIdLabel() tw.writeStmts_catchclause(catchId, id, catchIdx, callable) val catchLocId = tw.getLocation(catchClause) tw.writeHasLocation(catchId, catchLocId) extractTypeAccessRecursive( catchClause.catchParameter.type, tw.getLocation(catchClause.catchParameter), catchId, -1, callable, catchId ) extractVariableExpr( catchClause.catchParameter, callable, catchId, 0, catchId ) extractExpressionStmt(catchClause.result, callable, catchId, 1) } } is IrContainerExpression -> { if ( !tryExtractArrayUpdate(e, callable, parent) && !tryExtractForLoop(e, callable, parent) ) { extractBlock(e, e.statements, parent, callable) } } is IrWhileLoop -> { extractLoopWithCondition(e, parent, callable) } is IrDoWhileLoop -> { extractLoopWithCondition(e, parent, callable) } is IrInstanceInitializerCall -> { val irConstructor = declarationStack.peek().first as? IrConstructor if (irConstructor == null) { logger.errorElement("IrInstanceInitializerCall outside constructor", e) return } if (needsObinitFunction(irConstructor.parentAsClass)) { val exprParent = parent.expr(e, callable) val id = tw.getFreshIdLabel() val type = useType(pluginContext.irBuiltIns.unitType) val locId = tw.getLocation(e) val parentClass = irConstructor.parentAsClass val parentId = useDeclarationParentOf(irConstructor, false, null, true) if (parentId == null) { logger.errorElement("Cannot get parent ID for obinit", e) return } val methodLabel = getObinitLabel(parentClass, parentId) val methodId = tw.getLabelFor(methodLabel) tw.writeExprs_methodaccess( id, type.javaResult.id, exprParent.parent, exprParent.idx ) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, exprParent.enclosingStmt) tw.writeCallableBinding(id, methodId) } else { val stmtParent = parent.stmt(e, callable) extractInstanceInitializerBlock(stmtParent, irConstructor) } } is IrConstructorCall -> { val exprParent = parent.expr(e, callable) extractConstructorCall( e, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt ) } is IrEnumConstructorCall -> { val exprParent = parent.expr(e, callable) extractConstructorCall( e, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt ) } is IrCall -> { extractCall(e, callable, parent) } is IrStringConcatenation -> { val exprParent = parent.expr(e, callable) val id = tw.getFreshIdLabel() val type = useType(e.type) val locId = tw.getLocation(e) tw.writeExprs_stringtemplateexpr( id, type.javaResult.id, exprParent.parent, exprParent.idx ) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, exprParent.enclosingStmt) e.arguments.forEachIndexed { i, a -> extractExpressionExpr(a, callable, id, i, exprParent.enclosingStmt) } } is CodeQLIrConst<*> -> { val exprParent = parent.expr(e, callable) extractConstant( e, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt ) } is IrGetValue -> { val exprParent = parent.expr(e, callable) val owner = e.symbol.owner if ( owner is IrValueParameter && owner.index == -1 && !owner.isExtensionReceiver() ) { extractThisAccess(e, owner.parent, exprParent, callable) } else { val isAnnotationClassParameter = ((owner as? IrValueParameter)?.parent as? IrConstructor) ?.parentClassOrNull ?.kind == ClassKind.ANNOTATION_CLASS val extractType = if (isAnnotationClassParameter) kClassToJavaClass(e.type) else e.type extractVariableAccess( useValueDeclaration(owner), extractType, tw.getLocation(e), exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt ) } } is IrGetField -> { val exprParent = parent.expr(e, callable) val owner = tryReplaceAndroidSyntheticField(e.symbol.owner) val locId = tw.getLocation(e) val fieldType = if (isAnnotationClassField(owner)) kClassToJavaClass(e.type) else e.type extractVariableAccess( useField(owner), fieldType, locId, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt ) .also { id -> val receiver = e.receiver if (receiver != null) { extractExpressionExpr( receiver, callable, id, -1, exprParent.enclosingStmt ) } else if (owner.isStatic) { extractStaticTypeAccessQualifier( owner, id, locId, callable, exprParent.enclosingStmt ) } } } is IrGetEnumValue -> { val exprParent = parent.expr(e, callable) extractEnumValue( e, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt ) } is IrSetValue, is IrSetField -> { val exprParent = parent.expr(e, callable) val id = tw.getFreshIdLabel() val type = useType(e.type) val rhsValue = when (e) { is IrSetValue -> e.value is IrSetField -> e.value else -> { logger.errorElement("Unhandled IrSet* element.", e) return } } // The set operation's location as actually that of its LHS. Hence, the // assignment spans the // set op plus its RHS, while the varAccess takes its location from `e`. val locId = tw.getLocation(e.startOffset, rhsValue.endOffset) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, exprParent.enclosingStmt) val lhsId = tw.getFreshIdLabel() val lhsLocId = tw.getLocation(e) extractExprContext(lhsId, lhsLocId, callable, exprParent.enclosingStmt) when (e) { is IrSetValue -> { // Check for a desugared in-place update operator, such as "v += e": val inPlaceUpdateRhs = getUpdateInPlaceRHS( e.origin, { it is IrGetValue && it.symbol.owner == e.symbol.owner }, rhsValue ) if (inPlaceUpdateRhs != null) { val origin = e.origin if (origin == null) { logger.errorElement("No origin for set-value", e) return } else { val writeUpdateInPlaceExprFun = writeUpdateInPlaceExpr(origin) if (writeUpdateInPlaceExprFun == null) { logger.errorElement("Unexpected origin for set-value", e) return } writeUpdateInPlaceExprFun( tw, id, type.javaResult.id, exprParent.parent, exprParent.idx ) } } else { tw.writeExprs_assignexpr( id, type.javaResult.id, exprParent.parent, exprParent.idx ) } val lhsType = useType(e.symbol.owner.type) tw.writeExprs_varaccess(lhsId, lhsType.javaResult.id, id, 0) tw.writeExprsKotlinType(lhsId, lhsType.kotlinResult.id) val vId = useValueDeclaration(e.symbol.owner) if (vId != null) { tw.writeVariableBinding(lhsId, vId) } extractExpressionExpr( inPlaceUpdateRhs ?: rhsValue, callable, id, 1, exprParent.enclosingStmt ) } is IrSetField -> { tw.writeExprs_assignexpr( id, type.javaResult.id, exprParent.parent, exprParent.idx ) val realField = tryReplaceAndroidSyntheticField(e.symbol.owner) val lhsType = useType(realField.type) tw.writeExprs_varaccess(lhsId, lhsType.javaResult.id, id, 0) tw.writeExprsKotlinType(lhsId, lhsType.kotlinResult.id) val vId = useField(realField) tw.writeVariableBinding(lhsId, vId) extractExpressionExpr( e.value, callable, id, 1, exprParent.enclosingStmt ) val receiver = e.receiver if (receiver != null) { extractExpressionExpr( receiver, callable, lhsId, -1, exprParent.enclosingStmt ) } else if (realField.isStatic) { extractStaticTypeAccessQualifier( realField, lhsId, lhsLocId, callable, exprParent.enclosingStmt ) } } else -> { logger.errorElement("Unhandled IrSet* element.", e) } } } is IrWhen -> { val isAndAnd = e.origin == IrStatementOrigin.ANDAND val isOrOr = e.origin == IrStatementOrigin.OROR if ( (isAndAnd || isOrOr) && e.branches.size == 2 && (e.branches[1].condition as? CodeQLIrConst<*>)?.value == true && (e.branches[if (e.origin == IrStatementOrigin.ANDAND) 1 else 0].result as? CodeQLIrConst<*>) ?.value == isOrOr ) { // resugar binary logical operators: val exprParent = parent.expr(e, callable) val type = useType(e.type) val id = if (e.origin == IrStatementOrigin.ANDAND) { val id = tw.getFreshIdLabel() tw.writeExprs_andlogicalexpr( id, type.javaResult.id, exprParent.parent, exprParent.idx ) id } else { val id = tw.getFreshIdLabel() tw.writeExprs_orlogicalexpr( id, type.javaResult.id, exprParent.parent, exprParent.idx ) id } val locId = tw.getLocation(e) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, exprParent.enclosingStmt) extractExpressionExpr( e.branches[0].condition, callable, id, 0, exprParent.enclosingStmt ) var rhsIdx = if (e.origin == IrStatementOrigin.ANDAND) 0 else 1 extractExpressionExpr( e.branches[rhsIdx].result, callable, id, 1, exprParent.enclosingStmt ) return } val exprParent = parent.expr(e, callable) val id = tw.getFreshIdLabel() val type = useType(e.type) val locId = tw.getLocation(e) tw.writeExprs_whenexpr( id, type.javaResult.id, exprParent.parent, exprParent.idx ) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, exprParent.enclosingStmt) if (e.origin == IrStatementOrigin.IF) { tw.writeWhen_if(id) } e.branches.forEachIndexed { i, b -> val bId = tw.getFreshIdLabel() val bLocId = tw.getLocation(b) tw.writeStmts_whenbranch(bId, id, i, callable) tw.writeHasLocation(bId, bLocId) extractExpressionExpr(b.condition, callable, bId, 0, bId) extractExpressionStmt(b.result, callable, bId, 1) if (b is IrElseBranch) { tw.writeWhen_branch_else(bId) } } } is IrGetClass -> { val exprParent = parent.expr(e, callable) val id = tw.getFreshIdLabel() val locId = tw.getLocation(e) val type = useType(e.type) tw.writeExprs_getclassexpr( id, type.javaResult.id, exprParent.parent, exprParent.idx ) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, exprParent.enclosingStmt) extractExpressionExpr(e.argument, callable, id, 0, exprParent.enclosingStmt) } is IrTypeOperatorCall -> { val exprParent = parent.expr(e, callable) extractTypeOperatorCall( e, callable, exprParent.parent, exprParent.idx, exprParent.enclosingStmt ) } is IrVararg -> { // There are lowered IR cases when the vararg expression is not within a call, // such as // val temp0 = [*expr]. // This AST element can also occur as a collection literal in an annotation // class, such as // annotation class Ann(val strings: Array = []) val exprParent = parent.expr(e, callable) extractArrayCreation( e, e.type, e.varargElementType, true, e, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt ) } is IrGetObjectValue -> { // For `object MyObject { ... }`, the .class has an // automatically-generated `public static final MyObject INSTANCE` // field that we are accessing here. val exprParent = parent.expr(e, callable) val c = getBoundSymbolOwner(e.symbol, e) ?: return val instance = if (c.isCompanion) useCompanionObjectClassInstance(c) else useObjectClassInstance(c) if (instance != null) { val id = tw.getFreshIdLabel() val type = useType(e.type) val locId = tw.getLocation(e) tw.writeExprs_varaccess( id, type.javaResult.id, exprParent.parent, exprParent.idx ) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, exprParent.enclosingStmt) tw.writeVariableBinding(id, instance.id) } } is IrFunctionReference -> { extractFunctionReference(e, parent, callable) } is IrFunctionExpression -> { /* * Extract generated class: * ``` * class C : Any, kotlin.FunctionI { * constructor() { super(); } * fun invoke(a0:T0, a1:T1, ... aI: TI): R { ... } * } * ``` * or in case of big arity lambdas * ``` * class C : Any, kotlin.FunctionN { * constructor() { super(); } * fun invoke(a0:T0, a1:T1, ... aI: TI): R { ... } * fun invoke(vararg args: Any?): R { * return invoke(args[0] as T0, args[1] as T1, ..., args[I] as TI) * } * } * ``` **/ val ids = getLocallyVisibleFunctionLabels(e.function) val locId = tw.getLocation(e) val ext = e.function.extensionReceiverParameter val parameters = if (ext != null) { listOf(ext) + e.function.valueParameters } else { e.function.valueParameters } var types = parameters.map { it.type } types += e.function.returnType val isBigArity = types.size > BuiltInFunctionArity.BIG_ARITY if (isBigArity) { implementFunctionNInvoke(e.function, ids, locId, parameters) } else { addModifiers(ids.function, "override") } val exprParent = parent.expr(e, callable) val idLambdaExpr = tw.getFreshIdLabel() tw.writeExprs_lambdaexpr( idLambdaExpr, ids.type.javaResult.id, exprParent.parent, exprParent.idx ) tw.writeExprsKotlinType(idLambdaExpr, ids.type.kotlinResult.id) extractExprContext(idLambdaExpr, locId, callable, exprParent.enclosingStmt) tw.writeCallableBinding(idLambdaExpr, ids.constructor) // todo: fix hard coded block body of lambda tw.writeLambdaKind(idLambdaExpr, 1) val fnInterfaceType = getFunctionalInterfaceType(types) if (fnInterfaceType == null) { logger.warnElement( "Cannot find functional interface type for function expression", e ) } else { val id = extractGeneratedClass( e .function, // We're adding this function as a member, and // changing its name to `invoke` to implement // `kotlin.FunctionX<,,,>.invoke(,,)` listOf(pluginContext.irBuiltIns.anyType, fnInterfaceType) ) extractTypeAccessRecursive( fnInterfaceType, locId, idLambdaExpr, -3, callable, exprParent.enclosingStmt ) tw.writeIsAnonymClass(id, idLambdaExpr) } } is IrClassReference -> { val exprParent = parent.expr(e, callable) extractClassReference( e, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt ) } is IrPropertyReference -> { extractPropertyReference( "property reference", e, e.getter, e.setter, e.field, parent, callable ) } is IrLocalDelegatedPropertyReference -> { extractPropertyReference( "local delegated property reference", e, e.getter, e.setter, null, parent, callable ) } else -> { logger.errorElement("Unrecognised IrExpression: " + e.javaClass, e) } } return } } private fun extractBlock( e: IrContainerExpression, statements: List, parent: StmtExprParent, callable: Label ) { val stmtParent = parent.stmt(e, callable) extractBlock(e, statements, stmtParent.parent, stmtParent.idx, callable) } private fun extractBlock( e: IrElement, statements: List, parent: Label, idx: Int, callable: Label ) { val id = tw.getFreshIdLabel() val locId = tw.getLocation(e) tw.writeStmts_block(id, parent, idx, callable) tw.writeHasLocation(id, locId) statements.forEachIndexed { i, s -> extractStatement(s, callable, id, i) } } private inline fun getBoundSymbolOwner( symbol: IrBindableSymbol, e: IrExpression ): B? { if (symbol.isBound) { return symbol.owner } logger.errorElement("Unbound symbol found, skipping extraction of expression", e) return null } private fun extractSuperAccess( irType: IrType, callable: Label, parent: Label, idx: Int, enclosingStmt: Label, locId: Label ) = tw.getFreshIdLabel().also { val type = useType(irType) tw.writeExprs_superaccess(it, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(it, type.kotlinResult.id) extractExprContext(it, locId, callable, enclosingStmt) extractTypeAccessRecursive(irType, locId, it, 0) } private fun extractThisAccess( type: TypeResults, callable: Label, parent: Label, idx: Int, enclosingStmt: Label, locId: Label ) = tw.getFreshIdLabel().also { tw.writeExprs_thisaccess(it, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(it, type.kotlinResult.id) extractExprContext(it, locId, callable, enclosingStmt) } private fun extractThisAccess( irType: IrType, callable: Label, parent: Label, idx: Int, enclosingStmt: Label, locId: Label ) = extractThisAccess(useType(irType), callable, parent, idx, enclosingStmt, locId) private fun extractThisAccess( e: IrGetValue, thisParamParent: IrDeclarationParent, exprParent: ExprParent, callable: Label ) { val containingDeclaration = declarationStack.peek().first val locId = tw.getLocation(e) if ( containingDeclaration.shouldExtractAsStatic && containingDeclaration.parentClassOrNull?.isNonCompanionObject == true ) { // Use of `this` in a non-companion object member that will be lowered to a static // function -- replace with a reference // to the corresponding static object instance. val instanceField = useObjectClassInstance(containingDeclaration.parentAsClass) extractVariableAccess( instanceField.id, e.type, locId, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt ) .also { varAccessId -> extractStaticTypeAccessQualifier( containingDeclaration, varAccessId, locId, callable, exprParent.enclosingStmt ) } } else { if (thisParamParent is IrFunction) { val overriddenAttributes = declarationStack.findOverriddenAttributes(thisParamParent) val replaceWithParamIdx = overriddenAttributes?.valueParameters?.indexOf(e.symbol.owner) if (replaceWithParamIdx != null && replaceWithParamIdx != -1) { // Use of 'this' in a function where the dispatch receiver is passed like an // ordinary parameter, // such as a `$default` static function that substitutes in default arguments as // needed. val paramDeclarerId = overriddenAttributes.id ?: useDeclarationParent(thisParamParent, false) val replacementParamId = tw.getLabelFor( getValueParameterLabel(paramDeclarerId, replaceWithParamIdx) ) extractVariableAccess( replacementParamId, e.type, locId, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt ) return } } val id = extractThisAccess( e.type, callable, exprParent.parent, exprParent.idx, exprParent.enclosingStmt, locId ) fun extractTypeAccess(parent: IrClass) { extractTypeAccessRecursive( parent.typeWith(listOf()), locId, id, 0, callable, exprParent.enclosingStmt ) } val owner = e.symbol.owner when (val ownerParent = owner.parent) { is IrFunction -> { if ( ownerParent.dispatchReceiverParameter == owner && ownerParent.extensionReceiverParameter != null ) { val ownerParent2 = ownerParent.parent if (ownerParent2 is IrClass) { extractTypeAccess(ownerParent2) } else { logger.errorElement("Unhandled qualifier for this", e) } } } is IrClass -> { if (ownerParent.thisReceiver == owner) { extractTypeAccess(ownerParent) } } else -> { logger.errorElement( "Unexpected owner parent for this access: " + ownerParent.javaClass, e ) } } } } private fun extractVariableAccess( variable: Label?, type: TypeResults, locId: Label, parent: Label, idx: Int, callable: Label, enclosingStmt: Label ) = tw.getFreshIdLabel().also { tw.writeExprs_varaccess(it, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(it, type.kotlinResult.id) extractExprContext(it, locId, callable, enclosingStmt) if (variable != null) { tw.writeVariableBinding(it, variable) } } private fun extractVariableAccess( variable: Label?, irType: IrType, locId: Label, parent: Label, idx: Int, callable: Label, enclosingStmt: Label ) = extractVariableAccess( variable, useType(irType), locId, parent, idx, callable, enclosingStmt ) private fun extractLoop( loop: IrLoop, bodyIdx: Int?, stmtExprParent: StmtExprParent, callable: Label, getId: (Label, Int) -> Label ): Label { val stmtParent = stmtExprParent.stmt(loop, callable) val locId = tw.getLocation(loop) val idx: Int val parent: Label val label = loop.label if (label != null) { val labeledStmt = tw.getFreshIdLabel() tw.writeStmts_labeledstmt(labeledStmt, stmtParent.parent, stmtParent.idx, callable) tw.writeHasLocation(labeledStmt, locId) tw.writeNamestrings(label, "", labeledStmt) idx = 0 parent = labeledStmt } else { idx = stmtParent.idx parent = stmtParent.parent } val id = getId(parent, idx) tw.writeHasLocation(id, locId) val body = loop.body if (body != null && bodyIdx != null) { extractExpressionStmt(body, callable, id, bodyIdx) } return id } private fun extractLoopWithCondition( loop: IrLoop, stmtExprParent: StmtExprParent, callable: Label ) { val id = extractLoop(loop, 1, stmtExprParent, callable) { parent, idx -> if (loop is IrWhileLoop) { tw.getFreshIdLabel().also { tw.writeStmts_whilestmt(it, parent, idx, callable) } } else { tw.getFreshIdLabel().also { tw.writeStmts_dostmt(it, parent, idx, callable) } } } extractExpressionExpr(loop.condition, callable, id, 0, id) } private fun exprIdOrFresh(id: Label?) = id?.cast() ?: tw.getFreshIdLabel() private fun extractClassReference( e: IrClassReference, parent: Label, idx: Int, enclosingCallable: Label?, enclosingStmt: Label?, overrideId: Label? = null, typeAccessOverrideId: Label? = null, useJavaLangClassType: Boolean = false ) = exprIdOrFresh(overrideId).also { id -> val locId = tw.getLocation(e) val jlcType = if (useJavaLangClassType) this.javaLangClass?.let { it.typeWith() } else null val type = useType(jlcType ?: e.type) tw.writeExprs_typeliteral(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, enclosingCallable, enclosingStmt) extractTypeAccessRecursive( e.classType, locId, id, 0, enclosingCallable, enclosingStmt, overrideId = typeAccessOverrideId ) } private fun extractEnumValue( e: IrGetEnumValue, parent: Label, idx: Int, enclosingCallable: Label?, enclosingStmt: Label?, extractTypeAccess: Boolean = true, overrideId: Label? = null ) = exprIdOrFresh(overrideId).also { id -> val type = useType(e.type) val locId = tw.getLocation(e) tw.writeExprs_varaccess(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, enclosingCallable, enclosingStmt) getBoundSymbolOwner(e.symbol, e)?.let { owner -> val vId = useEnumEntry(owner) tw.writeVariableBinding(id, vId) if (extractTypeAccess) extractStaticTypeAccessQualifier( owner, id, locId, enclosingCallable, enclosingStmt ) } } private fun escapeCharForQuotedLiteral(c: Char) = when (c) { '\r' -> "\\r" '\n' -> "\\n" '\t' -> "\\t" '\\' -> "\\\\" '"' -> "\\\"" else -> c.toString() } // Render a string literal as it might occur in Kotlin source. Note this is a reasonable guess; // the real source // could use other escape sequences to describe the same String. Importantly, this is the same // guess the Java // extractor makes regarding string literals occurring within annotations, which we need to // coincide with to ensure // database consistency. private fun toQuotedLiteral(s: String) = s.toCharArray().joinToString(separator = "", prefix = "\"", postfix = "\"") { c -> escapeCharForQuotedLiteral(c) } private fun extractConstant( e: CodeQLIrConst<*>, parent: Label, idx: Int, enclosingCallable: Label?, enclosingStmt: Label?, overrideId: Label? = null ): Label? { val v = e.value return when { v is Number && (v is Int || v is Short || v is Byte) -> { extractConstantInteger( v, tw.getLocation(e), parent, idx, enclosingCallable, enclosingStmt, overrideId = overrideId ) } v is Long -> { exprIdOrFresh(overrideId).also { id -> val type = useType(e.type) val locId = tw.getLocation(e) tw.writeExprs_longliteral(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, enclosingCallable, enclosingStmt) tw.writeNamestrings(v.toString(), v.toString(), id) } } v is Float -> { exprIdOrFresh(overrideId).also { id -> val type = useType(e.type) val locId = tw.getLocation(e) tw.writeExprs_floatingpointliteral(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, enclosingCallable, enclosingStmt) tw.writeNamestrings(v.toString(), v.toString(), id) } } v is Double -> { exprIdOrFresh(overrideId).also { id -> val type = useType(e.type) val locId = tw.getLocation(e) tw.writeExprs_doubleliteral(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, enclosingCallable, enclosingStmt) tw.writeNamestrings(v.toString(), v.toString(), id) } } v is Boolean -> { exprIdOrFresh(overrideId).also { id -> val type = useType(e.type) val locId = tw.getLocation(e) tw.writeExprs_booleanliteral(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, enclosingCallable, enclosingStmt) tw.writeNamestrings(v.toString(), v.toString(), id) } } v is Char -> { exprIdOrFresh(overrideId).also { id -> val type = useType(e.type) val locId = tw.getLocation(e) tw.writeExprs_characterliteral(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, enclosingCallable, enclosingStmt) tw.writeNamestrings(v.toString(), v.toString(), id) } } v is String -> { exprIdOrFresh(overrideId).also { id -> val type = useType(e.type) val locId = tw.getLocation(e) tw.writeExprs_stringliteral(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, enclosingCallable, enclosingStmt) tw.writeNamestrings(toQuotedLiteral(v.toString()), v.toString(), id) } } v == null -> { extractNull( e.type, tw.getLocation(e), parent, idx, enclosingCallable, enclosingStmt, overrideId = overrideId ) } else -> { null.also { logger.errorElement("Unrecognised IrConst: " + v.javaClass, e) } } } } private fun IrValueParameter.isExtensionReceiver(): Boolean { val parentFun = parent as? IrFunction ?: return false return parentFun.extensionReceiverParameter == this } private open inner class GeneratedClassHelper( protected val locId: Label, protected val ids: GeneratedClassLabels ) { protected val classId = ids.type.javaResult.id.cast() /** * Extract a parameter to field assignment, such as `this.field = paramName` below: * ``` * constructor(paramName: type) { * this.field = paramName * } * ``` */ fun extractParameterToFieldAssignmentInConstructor( paramName: String, paramType: IrType, fieldId: Label, paramIdx: Int, stmtIdx: Int ) { val paramId = tw.getFreshIdLabel() extractValueParameter( paramId, paramType, paramName, locId, ids.constructor, paramIdx, paramId, syntheticParameterNames = false, isVararg = false, isNoinline = false, isCrossinline = false ) extractExpressionStmt(locId, ids.constructorBlock, stmtIdx, ids.constructor).also { assignmentStmtId -> extractAssignExpr( paramType, locId, assignmentStmtId, 0, ids.constructor, assignmentStmtId ) .also { assignmentId -> extractVariableAccess( fieldId, paramType, locId, assignmentId, 0, ids.constructor, assignmentStmtId ) .also { lhsId -> extractThisAccess( ids.type, ids.constructor, lhsId, -1, assignmentStmtId, locId ) } extractVariableAccess( paramId, paramType, locId, assignmentId, 1, ids.constructor, assignmentStmtId ) } } } } data class ReceiverInfo( val receiver: IrExpression, val type: IrType, val field: Label, val indexOffset: Int ) private fun makeReceiverInfo(receiver: IrExpression?, indexOffset: Int): ReceiverInfo? { if (receiver == null) { return null } val type = receiver.type val field: Label = tw.getFreshIdLabel() return ReceiverInfo(receiver, type, field, indexOffset) } /** * This is used when extracting callable references, i.e. `::someCallable` or * `::someReceiver::someCallable`. */ private open inner class CallableReferenceHelper( protected val callableReferenceExpr: IrCallableReference, locId: Label, ids: GeneratedClassLabels ) : GeneratedClassHelper(locId, ids) { // Only one of the receivers can be non-null, but we defensively handle the case when both // are null anyway private val dispatchReceiverInfo = makeReceiverInfo(callableReferenceExpr.dispatchReceiver, 0) private val extensionReceiverInfo = makeReceiverInfo( callableReferenceExpr.extensionReceiver, if (dispatchReceiverInfo == null) 0 else 1 ) fun extractReceiverField() { val firstAssignmentStmtIdx = 1 if (dispatchReceiverInfo != null) { extractField( dispatchReceiverInfo.field, "", dispatchReceiverInfo.type, classId, locId, DescriptorVisibilities.PRIVATE, callableReferenceExpr, isExternalDeclaration = false, isFinal = true, isStatic = false ) extractParameterToFieldAssignmentInConstructor( "", dispatchReceiverInfo.type, dispatchReceiverInfo.field, 0 + dispatchReceiverInfo.indexOffset, firstAssignmentStmtIdx + dispatchReceiverInfo.indexOffset ) } if (extensionReceiverInfo != null) { extractField( extensionReceiverInfo.field, "", extensionReceiverInfo.type, classId, locId, DescriptorVisibilities.PRIVATE, callableReferenceExpr, isExternalDeclaration = false, isFinal = true, isStatic = false ) extractParameterToFieldAssignmentInConstructor( "", extensionReceiverInfo.type, extensionReceiverInfo.field, 0 + extensionReceiverInfo.indexOffset, firstAssignmentStmtIdx + extensionReceiverInfo.indexOffset ) } } protected fun writeVariableAccessInFunctionBody( pType: TypeResults, idx: Int, variable: Label, parent: Label, callable: Label, stmt: Label ): Label { val pId = tw.getFreshIdLabel() tw.writeExprs_varaccess(pId, pType.javaResult.id, parent, idx) tw.writeExprsKotlinType(pId, pType.kotlinResult.id) tw.writeVariableBinding(pId, variable) extractExprContext(pId, locId, callable, stmt) return pId } private fun writeFieldAccessInFunctionBody( pType: IrType, idx: Int, variable: Label, parent: Label, callable: Label, stmt: Label ) { val accessId = writeVariableAccessInFunctionBody( useType(pType), idx, variable, parent, callable, stmt ) writeThisAccess(accessId, callable, stmt) } protected fun writeThisAccess( parent: Label, callable: Label, stmt: Label ) { extractThisAccess(ids.type, callable, parent, -1, stmt, locId) } fun extractFieldWriteOfReflectionTarget( labels: FunctionLabels, // labels of the containing function target: IrFieldSymbol, // the target field being accessed) ) { val fieldType = useType(target.owner.type) extractExpressionStmt(locId, labels.blockId, 0, labels.methodId).also { exprStmtId -> extractAssignExpr( target.owner.type, locId, exprStmtId, 0, labels.methodId, exprStmtId ) .also { assignExprId -> extractFieldAccess(fieldType, assignExprId, exprStmtId, labels, target) val p = labels.parameters.first() writeVariableAccessInFunctionBody( p.second, 1, p.first, assignExprId, labels.methodId, exprStmtId ) } } } fun extractFieldReturnOfReflectionTarget( labels: FunctionLabels, // labels of the containing function target: IrFieldSymbol, // the target field being accessed ) { val retId = tw.getFreshIdLabel() tw.writeStmts_returnstmt(retId, labels.blockId, 0, labels.methodId) tw.writeHasLocation(retId, locId) val fieldType = useType(target.owner.type) extractFieldAccess(fieldType, retId, retId, labels, target) } private fun extractFieldAccess( fieldType: TypeResults, parent: Label, stmt: Label, labels: FunctionLabels, target: IrFieldSymbol ) { val accessId = tw.getFreshIdLabel() tw.writeExprs_varaccess(accessId, fieldType.javaResult.id, parent, 0) tw.writeExprsKotlinType(accessId, fieldType.kotlinResult.id) extractExprContext(accessId, locId, labels.methodId, stmt) val fieldId = useField(target.owner) tw.writeVariableBinding(accessId, fieldId) if (dispatchReceiverInfo != null) { writeFieldAccessInFunctionBody( dispatchReceiverInfo.type, -1, dispatchReceiverInfo.field, accessId, labels.methodId, stmt ) } } /** * Extracts a call to `target` inside the function identified by `labels`. Special * parameters (`dispatch` and `extension`) are also handled. * * Examples are: * ``` * this..fn(this., param1, param2, param3, ...) * param1.fn(this., param2, ...) * param1.fn(param2, param3, ...) * fn(this., param1, param2, ...) * fn(param1, param2, ...) * new MyType(param1, param2, ...) * ``` * * The parameters with default argument values cover special cases: * - dispatchReceiverIdx is usually -1, except if a constructor is referenced * - big arity function references need to call `invoke` with arguments received in an * object array: `fn(param1[0] as T0, param1[1] as T1, ...)` */ fun extractCallToReflectionTarget( labels: FunctionLabels, // labels of the containing function target: IrFunctionSymbol, // the target function/constructor being called returnType: IrType, // the return type of the called function. Note that // `target.owner.returnType` and `returnType` doesn't match for generic // functions expressionTypeArgs: List, // type arguments of the extracted expression classTypeArgsIncludingOuterClasses: List< IrTypeArgument >?, // type arguments of the class containing the callable reference dispatchReceiverIdx: Int = -1, // dispatch receiver index: -1 in case of functions, -2 for constructors bigArityParameterTypes: List? = null // parameter types used for the cast expressions in a big arity `invoke` // invocation. null if not a big arity invocation. ) { // Return statement of generated function: val retId = tw.getFreshIdLabel() tw.writeStmts_returnstmt(retId, labels.blockId, 0, labels.methodId) tw.writeHasLocation(retId, locId) // Call to target function: val callType = useType(returnType) val callId: Label = if (target is IrConstructorSymbol) { val callId = tw.getFreshIdLabel() tw.writeExprs_newexpr(callId, callType.javaResult.id, retId, 0) tw.writeExprsKotlinType(callId, callType.kotlinResult.id) extractConstructorTypeAccess( returnType, callType, target, locId, callId, -3, labels.methodId, retId ) callId } else { val callId = tw.getFreshIdLabel() tw.writeExprs_methodaccess(callId, callType.javaResult.id, retId, 0) tw.writeExprsKotlinType(callId, callType.kotlinResult.id) extractTypeArguments( expressionTypeArgs, locId, callId, labels.methodId, retId, -2, true ) callId } extractExprContext(callId, locId, labels.methodId, retId) val callableId = useFunction( target.owner.realOverrideTarget, classTypeArgsIncludingOuterClasses ) if (callableId == null) { logger.error("Cannot get ID for reflection target") } else { tw.writeCallableBinding(callId.cast(), callableId) } val useFirstArgAsDispatch: Boolean if (dispatchReceiverInfo != null) { writeFieldAccessInFunctionBody( dispatchReceiverInfo.type, dispatchReceiverIdx, dispatchReceiverInfo.field, callId, labels.methodId, retId ) useFirstArgAsDispatch = false } else { if (target.owner.isLocalFunction()) { val ids = getLocallyVisibleFunctionLabels(target.owner) extractNewExprForLocalFunction(ids, callId, locId, labels.methodId, retId) useFirstArgAsDispatch = false } else { useFirstArgAsDispatch = target.owner.dispatchReceiverParameter != null if (isStaticFunction(target.owner)) { extractStaticTypeAccessQualifier( target.owner, callId, locId, labels.methodId, retId ) } } } val extensionIdxOffset: Int if (extensionReceiverInfo != null) { writeFieldAccessInFunctionBody( extensionReceiverInfo.type, 0, extensionReceiverInfo.field, callId, labels.methodId, retId ) extensionIdxOffset = 1 } else { extensionIdxOffset = 0 } if (bigArityParameterTypes != null) { // In case we're extracting a big arity function reference: addArgumentsToInvocationInInvokeNBody( bigArityParameterTypes, labels, retId, callId, locId, extensionIdxOffset, useFirstArgAsDispatch, dispatchReceiverIdx ) } else { val dispatchIdxOffset = if (useFirstArgAsDispatch) 1 else 0 for ((pIdx, p) in labels.parameters.withIndex()) { val childIdx = if (pIdx == 0 && useFirstArgAsDispatch) { dispatchReceiverIdx } else { pIdx + extensionIdxOffset - dispatchIdxOffset } writeVariableAccessInFunctionBody( p.second, childIdx, p.first, callId, labels.methodId, retId ) } } } fun extractConstructorArguments( callable: Label, idCtorRef: Label, enclosingStmt: Label ) { if (dispatchReceiverInfo != null) { extractExpressionExpr( dispatchReceiverInfo.receiver, callable, idCtorRef, 0 + dispatchReceiverInfo.indexOffset, enclosingStmt ) } if (extensionReceiverInfo != null) { extractExpressionExpr( extensionReceiverInfo.receiver, callable, idCtorRef, 0 + extensionReceiverInfo.indexOffset, enclosingStmt ) } } } private inner class PropertyReferenceHelper( callableReferenceExpr: IrCallableReference, locId: Label, ids: GeneratedClassLabels ) : CallableReferenceHelper(callableReferenceExpr, locId, ids) { fun extractPropertyReferenceInvoke( getId: Label, getterParameterTypes: List, getterReturnType: IrType ) { // Extracting this method is not (strictly) needed for interface member implementation. // `[Mutable]PropertyReferenceX` already implements it, but its signature doesn't match // the // generic one, because it's a raw method implementation. Also, by adding the `invoke` // explicitly, // we have better data flow analysis support. val invokeLabels = addFunctionManual( tw.getFreshIdLabel(), OperatorNameConventions.INVOKE.asString(), getterParameterTypes, getterReturnType, classId, locId ) // return this.get(a0, a1, ...) val retId = tw.getFreshIdLabel() tw.writeStmts_returnstmt(retId, invokeLabels.blockId, 0, invokeLabels.methodId) tw.writeHasLocation(retId, locId) // Call to target function: val callType = useType(getterReturnType) val callId = tw.getFreshIdLabel() tw.writeExprs_methodaccess(callId, callType.javaResult.id, retId, 0) tw.writeExprsKotlinType(callId, callType.kotlinResult.id) extractExprContext(callId, locId, invokeLabels.methodId, retId) tw.writeCallableBinding(callId, getId) this.writeThisAccess(callId, invokeLabels.methodId, retId) for ((pIdx, p) in invokeLabels.parameters.withIndex()) { this.writeVariableAccessInFunctionBody( p.second, pIdx, p.first, callId, invokeLabels.methodId, retId ) } } } private val propertyRefType by lazy { referenceExternalClass("kotlin.jvm.internal.PropertyReference")?.typeWith() } private fun extractPropertyReference( exprKind: String, propertyReferenceExpr: IrCallableReference, getter: IrSimpleFunctionSymbol?, setter: IrSimpleFunctionSymbol?, backingField: IrFieldSymbol?, parent: StmtExprParent, callable: Label ) { with(exprKind, propertyReferenceExpr) { /* * Extract generated class: * ``` * class C : kotlin.jvm.internal.PropertyReference, kotlin.reflect.KMutableProperty0 { * private dispatchReceiver: TD * constructor(dispatchReceiver: TD) { * super() * this.dispatchReceiver = dispatchReceiver * } * * override fun get(): R { return this.dispatchReceiver.FN1() } * * override fun set(a0: R): Unit { return this.dispatchReceiver.FN2(a0) } * * override fun invoke(): R { return this.get() } * } * ``` * * Variations: * - KProperty vs KMutableProperty * - KProperty0<> vs KProperty1<,> * - no receiver vs dispatchReceiver vs extensionReceiver **/ val kPropertyType = propertyReferenceExpr.type if (kPropertyType !is IrSimpleType) { logger.errorElement( "Unexpected: property reference with non simple type. ${kPropertyType.classFqName?.asString()}", propertyReferenceExpr ) return } val kPropertyClass = kPropertyType.classOrNull if (kPropertyClass == null) { logger.errorElement( "Cannot find class for kPropertyType. ${kPropertyType.classFqName?.asString()}", propertyReferenceExpr ) return } val parameterTypes: List? = kPropertyType.arguments .map { if (it is IrType) { it } else { logger.errorElement( "Unexpected: Non-IrType (${it.javaClass}) property reference parameter.", propertyReferenceExpr ) null } } .requireNoNullsOrNull() if (parameterTypes == null) { logger.errorElement( "Unexpected: One or more non-IrType property reference parameters.", propertyReferenceExpr ) return } val locId = tw.getLocation(propertyReferenceExpr) val javaResult = TypeResult(tw.getFreshIdLabel(), "", "") val kotlinResult = TypeResult(tw.getFreshIdLabel(), "", "") tw.writeKt_notnull_types(kotlinResult.id, javaResult.id) val ids = GeneratedClassLabels( TypeResults(javaResult, kotlinResult), constructor = tw.getFreshIdLabel(), constructorBlock = tw.getFreshIdLabel() ) val declarationParent = peekDeclStackAsDeclarationParent(propertyReferenceExpr) ?: return // The base class could be `Any`. `PropertyReference` is used to keep symmetry with // function references. val baseClass = propertyRefType ?: pluginContext.irBuiltIns.anyType val classId = extractGeneratedClass( ids, listOf(baseClass, kPropertyType), locId, propertyReferenceExpr, declarationParent ) val helper = PropertyReferenceHelper(propertyReferenceExpr, locId, ids) helper.extractReceiverField() val classTypeArguments = (propertyReferenceExpr.dispatchReceiver?.type as? IrSimpleType)?.arguments ?: if ( (getter?.owner?.dispatchReceiverParameter ?: setter?.owner?.dispatchReceiverParameter) != null ) { (kPropertyType.arguments.first() as? IrSimpleType)?.arguments } else { null } val expressionTypeArguments = (0 until propertyReferenceExpr.typeArgumentsCount).mapNotNull { propertyReferenceExpr.getTypeArgument(it) } val idPropertyRef = tw.getFreshIdLabel() val getterParameterTypes = parameterTypes.dropLast(1) val getterReturnType = parameterTypes.last() if (getter != null) { val getterCallableId = useFunction(getter.owner.realOverrideTarget, classTypeArguments) if (getterCallableId == null) { logger.errorElement("Cannot get ID for getter", propertyReferenceExpr) } else { val getLabels = addFunctionManual( tw.getFreshIdLabel(), OperatorNameConventions.GET.asString(), getterParameterTypes, getterReturnType, classId, locId ) helper.extractCallToReflectionTarget( getLabels, getter, getterReturnType, expressionTypeArguments, classTypeArguments ) tw.writePropertyRefGetBinding(idPropertyRef, getterCallableId) helper.extractPropertyReferenceInvoke( getLabels.methodId, getterParameterTypes, getterReturnType ) } } else { // Property without a getter. if (backingField == null) { logger.errorElement( "Expected to find getter or backing field for property reference.", propertyReferenceExpr ) return } val getLabels = addFunctionManual( tw.getFreshIdLabel(), OperatorNameConventions.GET.asString(), getterParameterTypes, getterReturnType, classId, locId ) val fieldId = useField(backingField.owner) helper.extractFieldReturnOfReflectionTarget(getLabels, backingField) tw.writePropertyRefFieldBinding(idPropertyRef, fieldId) helper.extractPropertyReferenceInvoke( getLabels.methodId, getterParameterTypes, getterReturnType ) } if (setter != null) { val setterCallableId = useFunction(setter.owner.realOverrideTarget, classTypeArguments) if (setterCallableId == null) { logger.errorElement("Cannot get ID for setter", propertyReferenceExpr) } else { val setLabels = addFunctionManual( tw.getFreshIdLabel(), OperatorNameConventions.SET.asString(), parameterTypes, pluginContext.irBuiltIns.unitType, classId, locId ) helper.extractCallToReflectionTarget( setLabels, setter, pluginContext.irBuiltIns.unitType, expressionTypeArguments, classTypeArguments ) tw.writePropertyRefSetBinding(idPropertyRef, setterCallableId) } } else { if (backingField != null && !backingField.owner.isFinal) { val setLabels = addFunctionManual( tw.getFreshIdLabel(), OperatorNameConventions.SET.asString(), parameterTypes, pluginContext.irBuiltIns.unitType, classId, locId ) val fieldId = useField(backingField.owner) helper.extractFieldWriteOfReflectionTarget(setLabels, backingField) tw.writePropertyRefFieldBinding(idPropertyRef, fieldId) } } // Add constructor (property ref) call: val exprParent = parent.expr(propertyReferenceExpr, callable) tw.writeExprs_propertyref( idPropertyRef, ids.type.javaResult.id, exprParent.parent, exprParent.idx ) tw.writeExprsKotlinType(idPropertyRef, ids.type.kotlinResult.id) extractExprContext(idPropertyRef, locId, callable, exprParent.enclosingStmt) tw.writeCallableBinding(idPropertyRef, ids.constructor) extractTypeAccessRecursive( kPropertyType, locId, idPropertyRef, -3, callable, exprParent.enclosingStmt ) helper.extractConstructorArguments(callable, idPropertyRef, exprParent.enclosingStmt) tw.writeIsAnonymClass(classId, idPropertyRef) } } private val functionRefType by lazy { referenceExternalClass("kotlin.jvm.internal.FunctionReference")?.typeWith() } private fun extractFunctionReference( functionReferenceExpr: IrFunctionReference, parent: StmtExprParent, callable: Label ) { with("function reference", functionReferenceExpr) { val target = if (functionReferenceExpr.origin == IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE) // For an adaptation (e.g. to adjust the number or type of arguments or results), // the symbol field points at the adapter while `.reflectionTarget` points at the // source-level target. functionReferenceExpr.symbol else // TODO: Consider whether we could always target the symbol functionReferenceExpr.reflectionTarget ?: run { logger.warnElement( "Expected to find reflection target for function reference. Using underlying symbol instead.", functionReferenceExpr ) functionReferenceExpr.symbol } /* * Extract generated class: * ``` * class C : kotlin.jvm.internal.FunctionReference, kotlin.FunctionI { * private dispatchReceiver: TD * private extensionReceiver: TE * constructor(dispatchReceiver: TD, extensionReceiver: TE) { * super() * this.dispatchReceiver = dispatchReceiver * this.extensionReceiver = extensionReceiver * } * fun invoke(a0:T0, a1:T1, ... aI: TI): R { return this.dispatchReceiver.FN(a0,a1,...,aI) } OR * fun invoke( a1:T1, ... aI: TI): R { return this.dispatchReceiver.FN(this.dispatchReceiver,a1,...,aI) } OR * fun invoke(a0:T0, a1:T1, ... aI: TI): R { return Ctor(a0,a1,...,aI) } * } * ``` * or in case of big arity lambdas ???? * ``` * class C : kotlin.jvm.internal.FunctionReference, kotlin.FunctionN { * private receiver: TD * constructor(receiver: TD) { super(); this.receiver = receiver; } * fun invoke(vararg args: Any?): R { * return this.receiver.FN(args[0] as T0, args[1] as T1, ..., args[I] as TI) * } * } * ``` **/ if ( functionReferenceExpr.dispatchReceiver != null && functionReferenceExpr.extensionReceiver != null ) { logger.errorElement( "Unexpected: dispatchReceiver and extensionReceiver are both non-null", functionReferenceExpr ) return } if ( target.owner.dispatchReceiverParameter != null && target.owner.extensionReceiverParameter != null ) { logger.errorElement( "Unexpected: dispatch and extension parameters are both non-null", functionReferenceExpr ) return } val type = functionReferenceExpr.type if (type !is IrSimpleType) { logger.errorElement( "Unexpected: function reference with non simple type. ${type.classFqName?.asString()}", functionReferenceExpr ) return } val parameterTypes: List? = type.arguments .map { if (it is IrType) { it } else { logger.errorElement( "Unexpected: Non-IrType (${it.javaClass}) function reference parameter.", functionReferenceExpr ) null } } .requireNoNullsOrNull() if (parameterTypes == null) { logger.errorElement( "Unexpected: One or more non-IrType function reference parameters.", functionReferenceExpr ) return } val dispatchReceiverIdx: Int val expressionTypeArguments: List val classTypeArguments: List? if (target is IrConstructorSymbol) { // In case a constructor is referenced, the return type of the `KFunctionX<,,,>` is // the type if the constructed type. classTypeArguments = (type.arguments.last() as? IrSimpleType)?.arguments expressionTypeArguments = listOf(parameterTypes.last()) dispatchReceiverIdx = -2 } else { classTypeArguments = (functionReferenceExpr.dispatchReceiver?.type as? IrSimpleType)?.arguments ?: if (target.owner.dispatchReceiverParameter != null) { (type.arguments.first() as? IrSimpleType)?.arguments } else { null } expressionTypeArguments = (0 until functionReferenceExpr.typeArgumentsCount).mapNotNull { functionReferenceExpr.getTypeArgument(it) } dispatchReceiverIdx = -1 } val locId = tw.getLocation(functionReferenceExpr) val javaResult = TypeResult(tw.getFreshIdLabel(), "", "") val kotlinResult = TypeResult(tw.getFreshIdLabel(), "", "") tw.writeKt_notnull_types(kotlinResult.id, javaResult.id) val ids = LocallyVisibleFunctionLabels( TypeResults(javaResult, kotlinResult), constructor = tw.getFreshIdLabel(), function = tw.getFreshIdLabel(), constructorBlock = tw.getFreshIdLabel() ) // Add constructor (member ref) call: val exprParent = parent.expr(functionReferenceExpr, callable) val idMemberRef = tw.getFreshIdLabel() tw.writeExprs_memberref( idMemberRef, ids.type.javaResult.id, exprParent.parent, exprParent.idx ) tw.writeExprsKotlinType(idMemberRef, ids.type.kotlinResult.id) extractExprContext(idMemberRef, locId, callable, exprParent.enclosingStmt) tw.writeCallableBinding(idMemberRef, ids.constructor) val targetCallableId = useFunction(target.owner.realOverrideTarget, classTypeArguments) if (targetCallableId == null) { logger.errorElement( "Cannot get ID for function reference callable", functionReferenceExpr ) } else { tw.writeMemberRefBinding(idMemberRef, targetCallableId) } val helper = CallableReferenceHelper(functionReferenceExpr, locId, ids) val fnInterfaceType = getFunctionalInterfaceTypeWithTypeArgs(type.arguments) if (fnInterfaceType == null) { logger.warnElement( "Cannot find functional interface type for function reference", functionReferenceExpr ) } else { val declarationParent = peekDeclStackAsDeclarationParent(functionReferenceExpr) ?: return // `FunctionReference` base class is required, because that's implementing // `KFunction`. val baseClass = functionRefType ?: pluginContext.irBuiltIns.anyType val classId = extractGeneratedClass( ids, listOf(baseClass, fnInterfaceType), locId, functionReferenceExpr, declarationParent, null, { it.valueParameters.size == 1 } ) { // The argument to FunctionReference's constructor is the function arity. extractConstantInteger( type.arguments.size - 1, locId, it, 0, ids.constructor, it ) } helper.extractReceiverField() val isBigArity = type.arguments.size > BuiltInFunctionArity.BIG_ARITY val funLabels = if (isBigArity) { addFunctionNInvoke(ids.function, parameterTypes.last(), classId, locId) } else { addFunctionInvoke( ids.function, parameterTypes.dropLast(1), parameterTypes.last(), classId, locId ) } helper.extractCallToReflectionTarget( funLabels, target, parameterTypes.last(), expressionTypeArguments, classTypeArguments, dispatchReceiverIdx, if (isBigArity) parameterTypes.dropLast(1) else null ) val typeAccessArguments = if (isBigArity) listOf(parameterTypes.last()) else parameterTypes if (target is IrConstructorSymbol) { val returnType = typeAccessArguments.last() val typeAccessId = extractTypeAccess( useType(fnInterfaceType, TypeContext.OTHER), locId, idMemberRef, -3, callable, exprParent.enclosingStmt ) typeAccessArguments.dropLast(1).forEachIndexed { argIdx, arg -> extractTypeAccessRecursive( arg, locId, typeAccessId, argIdx, callable, exprParent.enclosingStmt, TypeContext.GENERIC_ARGUMENT ) } extractConstructorTypeAccess( returnType, useType(returnType), target, locId, typeAccessId, typeAccessArguments.count() - 1, callable, exprParent.enclosingStmt ) } else { extractTypeAccessRecursive( fnInterfaceType, locId, idMemberRef, -3, callable, exprParent.enclosingStmt ) } helper.extractConstructorArguments(callable, idMemberRef, exprParent.enclosingStmt) tw.writeIsAnonymClass(classId, idMemberRef) } } } private fun getFunctionalInterfaceType(functionNTypeArguments: List) = getFunctionalInterfaceTypeWithTypeArgs( functionNTypeArguments.map { makeTypeProjection(it, Variance.INVARIANT) } ) private fun getFunctionalInterfaceTypeWithTypeArgs( functionNTypeArguments: List ) = if (functionNTypeArguments.size > BuiltInFunctionArity.BIG_ARITY) referenceExternalClass("kotlin.jvm.functions.FunctionN") ?.symbol ?.typeWithArguments(listOf(functionNTypeArguments.last())) else functionN(pluginContext)(functionNTypeArguments.size - 1) .symbol .typeWithArguments(functionNTypeArguments) private data class FunctionLabels( val methodId: Label, val blockId: Label, val parameters: List, TypeResults>> ) /** * Adds a function `invoke(a: Any[])` with the specified return type to the class identified by * `parentId`. */ private fun addFunctionNInvoke( methodId: Label, returnType: IrType, parentId: Label, locId: Label ): FunctionLabels { return addFunctionInvoke( methodId, listOf(pluginContext.irBuiltIns.arrayClass.typeWith(pluginContext.irBuiltIns.anyNType)), returnType, parentId, locId ) } /** * Adds a function named `invoke` with the specified parameter types and return type to the * class identified by `parentId`. */ private fun addFunctionInvoke( methodId: Label, parameterTypes: List, returnType: IrType, parentId: Label, locId: Label ): FunctionLabels { return addFunctionManual( methodId, OperatorNameConventions.INVOKE.asString(), parameterTypes, returnType, parentId, locId ) } /** * Extracts a function with the given name, parameter types, return type, containing type, and * location. */ private fun addFunctionManual( methodId: Label, name: String, parameterTypes: List, returnType: IrType, parentId: Label, locId: Label ): FunctionLabels { val parameters = parameterTypes.mapIndexed { idx, p -> val paramId = tw.getFreshIdLabel() val paramType = extractValueParameter( paramId, p, "a$idx", locId, methodId, idx, paramId, syntheticParameterNames = false, isVararg = false, isNoinline = false, isCrossinline = false ) Pair(paramId, paramType) } val paramsSignature = parameters.joinToString(separator = ",", prefix = "(", postfix = ")") { signatureOrWarn(it.second.javaResult, declarationStack.tryPeek()?.first) } val rt = useType(returnType, TypeContext.RETURN) tw.writeMethods( methodId, name, "$name$paramsSignature", rt.javaResult.id, parentId, methodId ) tw.writeMethodsKotlinType(methodId, rt.kotlinResult.id) tw.writeHasLocation(methodId, locId) addModifiers(methodId, "public") addModifiers(methodId, "override") return FunctionLabels(methodId, extractBlockBody(methodId, locId), parameters) } /* * This function generates an implementation for `fun kotlin.FunctionN.invoke(vararg args: Any?): R` * * The following body is added: * ``` * fun invoke(vararg a0: Any?): R { * return invoke(a0[0] as T0, a0[1] as T1, ..., a0[I] as TI) * } * ``` * */ private fun implementFunctionNInvoke( lambda: IrFunction, ids: LocallyVisibleFunctionLabels, locId: Label, parameters: List ) { val funLabels = addFunctionNInvoke( tw.getFreshIdLabel(), lambda.returnType, ids.type.javaResult.id.cast(), locId ) // Return val retId = tw.getFreshIdLabel() tw.writeStmts_returnstmt(retId, funLabels.blockId, 0, funLabels.methodId) tw.writeHasLocation(retId, locId) // Call to original `invoke`: val callId = tw.getFreshIdLabel() val callType = useType(lambda.returnType) tw.writeExprs_methodaccess(callId, callType.javaResult.id, retId, 0) tw.writeExprsKotlinType(callId, callType.kotlinResult.id) extractExprContext(callId, locId, funLabels.methodId, retId) val calledMethodId = useFunction(lambda) if (calledMethodId == null) { logger.errorElement("Cannot get ID for called lambda", lambda) } else { tw.writeCallableBinding(callId, calledMethodId) } // this access extractThisAccess(ids.type, funLabels.methodId, callId, -1, retId, locId) addArgumentsToInvocationInInvokeNBody( parameters.map { it.type }, funLabels, retId, callId, locId ) } /** * Adds the arguments to the method call inside `invoke(a0: Any[])`. Each argument is an array * access with a cast: * ``` * fun invoke(a0: Any[]) : T { * return fn(a0[0] as T0, a0[1] as T1, ...) * } * ``` */ private fun addArgumentsToInvocationInInvokeNBody( parameterTypes: List, // list of parameter types funLabels: FunctionLabels, // already generated labels for the function definition enclosingStmtId: Label, // label for the enclosing statement (return) exprParentId: Label, // label for the expression parent (call) locId: Label, // label for the location of all generated items firstArgumentOffset: Int = 0, // 0 or 1, the index used for the first argument. 1 in case an extension parameter is // already accessed at index 0 useFirstArgAsDispatch: Boolean = false, // true if the first argument should be used as the dispatch receiver dispatchReceiverIdx: Int = -1 // index of the dispatch receiver. -1 in case of functions, -2 in case of // constructors ) { val argsParamType = pluginContext.irBuiltIns.arrayClass.typeWith(pluginContext.irBuiltIns.anyNType) val argsType = useType(argsParamType) val anyNType = useType(pluginContext.irBuiltIns.anyNType) val dispatchIdxOffset = if (useFirstArgAsDispatch) 1 else 0 for ((pIdx, pType) in parameterTypes.withIndex()) { // `a0[i] as Ti` is generated below for each parameter val childIdx = if (pIdx == 0 && useFirstArgAsDispatch) { dispatchReceiverIdx } else { pIdx + firstArgumentOffset - dispatchIdxOffset } // cast: `(Ti)a0[i]` val castId = tw.getFreshIdLabel() val type = useType(pType) tw.writeExprs_castexpr(castId, type.javaResult.id, exprParentId, childIdx) tw.writeExprsKotlinType(castId, type.kotlinResult.id) extractExprContext(castId, locId, funLabels.methodId, enclosingStmtId) // type access `Ti` extractTypeAccessRecursive(pType, locId, castId, 0, funLabels.methodId, enclosingStmtId) // element access: `a0[i]` val arrayAccessId = tw.getFreshIdLabel() tw.writeExprs_arrayaccess(arrayAccessId, anyNType.javaResult.id, castId, 1) tw.writeExprsKotlinType(arrayAccessId, anyNType.kotlinResult.id) extractExprContext(arrayAccessId, locId, funLabels.methodId, enclosingStmtId) // parameter access: `a0` val argsAccessId = tw.getFreshIdLabel() tw.writeExprs_varaccess(argsAccessId, argsType.javaResult.id, arrayAccessId, 0) tw.writeExprsKotlinType(argsAccessId, argsType.kotlinResult.id) extractExprContext(argsAccessId, locId, funLabels.methodId, enclosingStmtId) tw.writeVariableBinding(argsAccessId, funLabels.parameters.first().first) // index access: `i` extractConstantInteger( pIdx, locId, arrayAccessId, 1, funLabels.methodId, enclosingStmtId ) } } private fun extractVarargElement( e: IrVarargElement, callable: Label, parent: Label, idx: Int, enclosingStmt: Label ) { with("vararg element", e) { val argExpr = when (e) { is IrExpression -> e is IrSpreadElement -> e.expression else -> { logger.errorElement("Unrecognised IrVarargElement: " + e.javaClass, e) null } } argExpr?.let { extractExpressionExpr(it, callable, parent, idx, enclosingStmt) } } } /** * Extracts a type access expression and its generic arguments for a constructor call. It only * extracts type arguments relating to the constructed type, not the constructor itself, which * makes a difference in case of nested generics. */ private fun extractConstructorTypeAccess( irType: IrType, type: TypeResults, target: IrFunctionSymbol, locId: Label, parent: Label, idx: Int, enclosingCallable: Label, enclosingStmt: Label ) { val typeAccessId = extractTypeAccess(type, locId, parent, idx, enclosingCallable, enclosingStmt) if (irType is IrSimpleType) { extractTypeArguments( irType.arguments .take(target.owner.parentAsClass.typeParameters.size) .filterIsInstance(), locId, typeAccessId, enclosingCallable, enclosingStmt ) } } /** * Extracts a single wildcard type access expression with no enclosing callable and statement. */ private fun extractWildcardTypeAccess( type: TypeResultsWithoutSignatures, location: Label, parent: Label, idx: Int ): Label { val id = tw.getFreshIdLabel() tw.writeExprs_wildcardtypeaccess(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) tw.writeHasLocation(id, location) return id } /** Extracts a single type access expression with no enclosing callable and statement. */ private fun extractTypeAccess( type: TypeResults, location: Label, parent: Label, idx: Int, overrideId: Label? = null ): Label { // TODO: elementForLocation allows us to give some sort of // location, but a proper location for the type access will // require upstream changes val id = exprIdOrFresh(overrideId) tw.writeExprs_unannotatedtypeaccess(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) tw.writeHasLocation(id, location) return id } /** Extracts a single type access expression with enclosing callable and statement. */ private fun extractTypeAccess( type: TypeResults, location: Label, parent: Label, idx: Int, enclosingCallable: Label?, enclosingStmt: Label?, overrideId: Label? = null ): Label { val id = extractTypeAccess(type, location, parent, idx, overrideId = overrideId) if (enclosingCallable != null) { tw.writeCallableEnclosingExpr(id, enclosingCallable) } if (enclosingStmt != null) { tw.writeStatementEnclosingExpr(id, enclosingStmt) } return id } /** * Extracts a type argument type access, introducing a wildcard type access if appropriate, or * directly calling `extractTypeAccessRecursive` if the argument is invariant. No enclosing * callable and statement is extracted, this is useful for type access extraction in field * declarations. */ private fun extractWildcardTypeAccessRecursive( t: IrTypeArgument, location: Label, parent: Label, idx: Int ) { val typeLabels by lazy { TypeResultsWithoutSignatures( getTypeArgumentLabel(t), TypeResultWithoutSignature(fakeKotlinType(), Unit, "TODO") ) } when (t) { is IrStarProjection -> extractWildcardTypeAccess(typeLabels, location, parent, idx) is IrTypeProjection -> when (t.variance) { Variance.INVARIANT -> extractTypeAccessRecursive( t.type, location, parent, idx, TypeContext.GENERIC_ARGUMENT ) else -> { val wildcardLabel = extractWildcardTypeAccess(typeLabels, location, parent, idx) // Mimic a Java extractor oddity, that it uses the child index to indicate // what kind of wildcard this is val boundChildIdx = if (t.variance == Variance.OUT_VARIANCE) 0 else 1 extractTypeAccessRecursive( t.type, location, wildcardLabel, boundChildIdx, TypeContext.GENERIC_ARGUMENT ) } } } } /** * Extracts a type access expression and its child type access expressions in case of a generic * type. Nested generics are also handled. No enclosing callable and statement is extracted, * this is useful for type access extraction in field declarations. */ private fun extractTypeAccessRecursive( t: IrType, location: Label, parent: Label, idx: Int, typeContext: TypeContext = TypeContext.OTHER ): Label { val typeAccessId = extractTypeAccess(useType(t, typeContext), location, parent, idx) if (t is IrSimpleType) { // From 1.9, the list might change when we call erase, // so we make a copy that it is safe to iterate over. val argumentsCopy = t.arguments.toList() argumentsCopy.forEachIndexed { argIdx, arg -> extractWildcardTypeAccessRecursive(arg, location, typeAccessId, argIdx) } } return typeAccessId } /** * Extracts a type access expression and its child type access expressions in case of a generic * type. Nested generics are also handled. */ private fun extractTypeAccessRecursive( t: IrType, location: Label, parent: Label, idx: Int, enclosingCallable: Label?, enclosingStmt: Label?, typeContext: TypeContext = TypeContext.OTHER, overrideId: Label? = null ): Label { // TODO: `useType` substitutes types to their java equivalent, and sometimes that also means // changing the number of type arguments. The below logic doesn't take this into account. // For example `KFunction2` becomes `KFunction` with three child // type access expressions: `Int`, `Double`, `String`. val typeAccessId = extractTypeAccess( useType(t, typeContext), location, parent, idx, enclosingCallable, enclosingStmt, overrideId = overrideId ) if (t is IrSimpleType) { if (t.arguments.isNotEmpty() && overrideId != null) { logger.error( "Unexpected parameterized type with an overridden expression ID; children will be assigned fresh IDs" ) } extractTypeArguments( t.arguments.filterIsInstance(), location, typeAccessId, enclosingCallable, enclosingStmt ) } return typeAccessId } /** * Extracts a list of types as type access expressions. Nested generics are also handled. Used * for extracting nested type access expressions, and type arguments of constructor or function * calls. */ private fun extractTypeArguments( typeArgs: List, location: Label, parentExpr: Label, enclosingCallable: Label?, enclosingStmt: Label?, startIndex: Int = 0, reverse: Boolean = false ) { typeArgs.forEachIndexed { argIdx, arg -> val mul = if (reverse) -1 else 1 extractTypeAccessRecursive( arg, location, parentExpr, argIdx * mul + startIndex, enclosingCallable, enclosingStmt, TypeContext.GENERIC_ARGUMENT ) } } /** * Extracts type arguments of a member access expression as type access expressions. Nested * generics are also handled. Used for extracting nested type access expressions, and type * arguments of constructor or function calls. */ private fun extractTypeArguments( c: IrMemberAccessExpression, parentExpr: Label, enclosingCallable: Label, enclosingStmt: Label, startIndex: Int = 0, reverse: Boolean = false ) { val typeArguments = (0 until c.typeArgumentsCount).map { c.getTypeArgument(it) }.requireNoNullsOrNull() if (typeArguments == null) { logger.errorElement("Found a null type argument for a member access expression", c) } else { extractTypeArguments( typeArguments, tw.getLocation(c), parentExpr, enclosingCallable, enclosingStmt, startIndex, reverse ) } } private fun extractArrayCreationWithInitializer( parent: Label, arraySize: Int, locId: Label, enclosingCallable: Label, enclosingStmt: Label ): Label { val arrayCreationId = tw.getFreshIdLabel() val arrayType = pluginContext.irBuiltIns.arrayClass.typeWith(pluginContext.irBuiltIns.anyNType) val at = useType(arrayType) tw.writeExprs_arraycreationexpr(arrayCreationId, at.javaResult.id, parent, 0) tw.writeExprsKotlinType(arrayCreationId, at.kotlinResult.id) extractExprContext(arrayCreationId, locId, enclosingCallable, enclosingStmt) extractTypeAccessRecursive( pluginContext.irBuiltIns.anyNType, locId, arrayCreationId, -1, enclosingCallable, enclosingStmt ) val initId = tw.getFreshIdLabel() tw.writeExprs_arrayinit(initId, at.javaResult.id, arrayCreationId, -2) tw.writeExprsKotlinType(initId, at.kotlinResult.id) extractExprContext(initId, locId, enclosingCallable, enclosingStmt) extractConstantInteger( arraySize, locId, arrayCreationId, 0, enclosingCallable, enclosingStmt ) return initId } private fun extractTypeOperatorCall( e: IrTypeOperatorCall, callable: Label, parent: Label, idx: Int, enclosingStmt: Label ) { with("type operator call", e) { when (e.operator) { IrTypeOperator.CAST -> { val id = tw.getFreshIdLabel() val locId = tw.getLocation(e) val type = useType(e.type) tw.writeExprs_castexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, enclosingStmt) extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt) extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt) } IrTypeOperator.IMPLICIT_CAST -> { val id = tw.getFreshIdLabel() val locId = tw.getLocation(e) val type = useType(e.type) tw.writeExprs_implicitcastexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, enclosingStmt) extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt) extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt) } IrTypeOperator.IMPLICIT_NOTNULL -> { val id = tw.getFreshIdLabel() val locId = tw.getLocation(e) val type = useType(e.type) tw.writeExprs_implicitnotnullexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, enclosingStmt) extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt) extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt) } IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> { val id = tw.getFreshIdLabel() val locId = tw.getLocation(e) val type = useType(e.type) tw.writeExprs_implicitcoerciontounitexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, enclosingStmt) extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt) extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt) } IrTypeOperator.SAFE_CAST -> { val id = tw.getFreshIdLabel() val locId = tw.getLocation(e) val type = useType(e.type) tw.writeExprs_safecastexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, enclosingStmt) extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt) extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt) } IrTypeOperator.INSTANCEOF -> { val id = tw.getFreshIdLabel() val locId = tw.getLocation(e) val type = useType(e.type) tw.writeExprs_instanceofexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, enclosingStmt) extractExpressionExpr(e.argument, callable, id, 0, enclosingStmt) extractTypeAccessRecursive(e.typeOperand, locId, id, 1, callable, enclosingStmt) } IrTypeOperator.NOT_INSTANCEOF -> { val id = tw.getFreshIdLabel() val locId = tw.getLocation(e) val type = useType(e.type) tw.writeExprs_notinstanceofexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, enclosingStmt) extractExpressionExpr(e.argument, callable, id, 0, enclosingStmt) extractTypeAccessRecursive(e.typeOperand, locId, id, 1, callable, enclosingStmt) } IrTypeOperator.SAM_CONVERSION -> { /* The following Kotlin code ``` fun interface IntPredicate { fun accept(i: Int): Boolean } val x = IntPredicate { it % 2 == 0 } ``` is extracted as ``` interface IntPredicate { Boolean accept(Integer i); } class extends Object implements IntPredicate { Function1 ; public (Function1 ) { this. = ; } public override Boolean accept(Integer i) { return .invoke(i); } } IntPredicate x = (IntPredicate)new (...); ``` */ val st = e.argument.type as? IrSimpleType if (st == null) { logger.errorElement("Expected to find a simple type in SAM conversion.", e) return } fun IrSimpleType.isKProperty() = classFqName?.asString()?.startsWith("kotlin.reflect.KProperty") == true if ( !st.isFunctionOrKFunction() && !st.isSuspendFunctionOrKFunction() && !st.isKProperty() ) { logger.errorElement( "Expected to find expression with function type in SAM conversion.", e ) return } // Either Function1, ... Function22 or FunctionN type, but not Function23 or // above. val functionType = getFunctionalInterfaceTypeWithTypeArgs(st.arguments) if (functionType == null) { logger.errorElement("Cannot find functional interface.", e) return } val invokeMethod = functionType.classOrNull?.owner?.declarations?.findSubType { it.name.asString() == OperatorNameConventions.INVOKE.asString() } if (invokeMethod == null) { logger.errorElement( "Couldn't find `invoke` method on functional interface.", e ) return } val typeOwner = e.typeOperand.classifierOrFail.owner if (typeOwner !is IrClass) { logger.errorElement( "Expected to find SAM conversion to IrClass. Found '${typeOwner.javaClass}' instead. Can't implement SAM interface.", e ) return } val samMember = typeOwner.declarations.findSubType { it is IrOverridableMember && it.modality == Modality.ABSTRACT } if (samMember == null) { logger.errorElement( "Couldn't find SAM member in type '${typeOwner.kotlinFqName.asString()}'. Can't implement SAM interface.", e ) return } val javaResult = TypeResult(tw.getFreshIdLabel(), "", "") val kotlinResult = TypeResult(tw.getFreshIdLabel(), "", "") tw.writeKt_notnull_types(kotlinResult.id, javaResult.id) val ids = LocallyVisibleFunctionLabels( TypeResults(javaResult, kotlinResult), constructor = tw.getFreshIdLabel(), constructorBlock = tw.getFreshIdLabel(), function = tw.getFreshIdLabel() ) val locId = tw.getLocation(e) val helper = GeneratedClassHelper(locId, ids) val declarationParent = peekDeclStackAsDeclarationParent(e) ?: return val classId = extractGeneratedClass( ids, listOf(pluginContext.irBuiltIns.anyType, e.typeOperand), locId, e, declarationParent ) // add field val fieldId = tw.getFreshIdLabel() extractField( fieldId, "", functionType, classId, locId, DescriptorVisibilities.PRIVATE, e, isExternalDeclaration = false, isFinal = true, isStatic = false ) // adjust constructor helper.extractParameterToFieldAssignmentInConstructor( "", functionType, fieldId, 0, 1 ) // add implementation function val classTypeArgs = (e.type as? IrSimpleType)?.arguments val typeSub = classTypeArgs?.let { makeGenericSubstitutionFunction(typeOwner, it) } fun trySub(t: IrType, context: TypeContext) = if (typeSub == null) t else typeSub(t, context, pluginContext) // Force extraction of this function even if this is a fake override -- // This happens in the case where a functional interface inherits its only // abstract member, // which usually we wouldn't extract, but in this case we're effectively using // it as a template // for the real function we're extracting that will implement this interface, // and it serves fine // for that purpose. By contrast if we looked through the fake to the underlying // abstract method // we would need to compose generic type substitutions -- for example, if we're // implementing // T UnaryOperator.apply(T t) here, we would need to compose substitutions so // we can implement // the real underlying R Function.apply(T t). forceExtractFunction( samMember, classId, extractBody = false, extractMethodAndParameterTypeAccesses = true, extractAnnotations = false, typeSub, classTypeArgs, overriddenAttributes = OverriddenFunctionAttributes( id = ids.function, sourceLoc = tw.getLocation(e), modality = Modality.FINAL ) ) addModifiers(ids.function, "override") if (st.isSuspendFunctionOrKFunction()) { addModifiers(ids.function, "suspend") } // body val blockId = extractBlockBody(ids.function, locId) // return stmt val returnId = tw.getFreshIdLabel() tw.writeStmts_returnstmt(returnId, blockId, 0, ids.function) tw.writeHasLocation(returnId, locId) // .invoke(vp0, cp1, vp2, vp3, ...) or // .invoke(new Object[x]{vp0, vp1, vp2, ...}) // Call to original `invoke`: val callId = tw.getFreshIdLabel() val callType = useType(trySub(samMember.returnType, TypeContext.RETURN)) tw.writeExprs_methodaccess(callId, callType.javaResult.id, returnId, 0) tw.writeExprsKotlinType(callId, callType.kotlinResult.id) extractExprContext(callId, locId, ids.function, returnId) val calledMethodId = useFunction(invokeMethod, functionType.arguments) if (calledMethodId == null) { logger.errorElement("Cannot get ID for called method", invokeMethod) } else { tw.writeCallableBinding(callId, calledMethodId) } // access val lhsId = tw.getFreshIdLabel() val lhsType = useType(functionType) tw.writeExprs_varaccess(lhsId, lhsType.javaResult.id, callId, -1) tw.writeExprsKotlinType(lhsId, lhsType.kotlinResult.id) extractExprContext(lhsId, locId, ids.function, returnId) tw.writeVariableBinding(lhsId, fieldId) val parameters = mutableListOf() val extParam = samMember.extensionReceiverParameter if (extParam != null) { parameters.add(extParam) } parameters.addAll(samMember.valueParameters) fun extractArgument( p: IrValueParameter, idx: Int, parent: Label ) { val argsAccessId = tw.getFreshIdLabel() val paramType = useType(trySub(p.type, TypeContext.OTHER)) tw.writeExprs_varaccess(argsAccessId, paramType.javaResult.id, parent, idx) tw.writeExprsKotlinType(argsAccessId, paramType.kotlinResult.id) extractExprContext(argsAccessId, locId, ids.function, returnId) tw.writeVariableBinding(argsAccessId, useValueParameter(p, ids.function)) } val isBigArity = st.arguments.size > BuiltInFunctionArity.BIG_ARITY val argParent = if (isBigArity) { // .invoke(new Object[x]{vp0, vp1, vp2, ...}) extractArrayCreationWithInitializer( callId, parameters.size, locId, ids.function, returnId ) } else { // .invoke(vp0, cp1, vp2, vp3, ...) or callId } for ((parameterIdx, vp) in parameters.withIndex()) { extractArgument(vp, parameterIdx, argParent) } val id = tw.getFreshIdLabel() val type = useType(e.typeOperand) tw.writeExprs_castexpr(id, type.javaResult.id, parent, idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) extractExprContext(id, locId, callable, enclosingStmt) extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt) val idNewexpr = extractNewExpr( ids.constructor, ids.type, locId, id, 1, callable, enclosingStmt ) tw.writeIsAnonymClass( ids.type.javaResult.id.cast(), idNewexpr ) extractTypeAccessRecursive( e.typeOperand, locId, idNewexpr, -3, callable, enclosingStmt ) extractExpressionExpr(e.argument, callable, idNewexpr, 0, enclosingStmt) } else -> { logger.errorElement( "Unrecognised IrTypeOperatorCall for ${e.operator}: " + e.render(), e ) } } } } private fun extractBreakContinue(e: IrBreakContinue, id: Label) { with("break/continue", e) { val locId = tw.getLocation(e) tw.writeHasLocation(id, locId) val label = e.label if (label != null) { tw.writeNamestrings(label, "", id) } } } private val IrType.isAnonymous: Boolean get() = ((this as? IrSimpleType)?.classifier?.owner as? IrClass)?.isAnonymousObject ?: false private fun addVisibilityModifierToLocalOrAnonymousClass(id: Label) { addModifiers(id, "private") } /** Extracts the class around a local function, a lambda, or a function reference. */ private fun extractGeneratedClass( ids: GeneratedClassLabels, superTypes: List, locId: Label, elementToReportOn: IrElement, declarationParent: IrDeclarationParent, compilerGeneratedKindOverride: CompilerGeneratedKinds? = null, superConstructorSelector: (IrFunction) -> Boolean = { it.valueParameters.isEmpty() }, extractSuperconstructorArgs: (Label) -> Unit = {}, ): Label { // Write class val id = ids.type.javaResult.id.cast() val pkgId = extractPackage("") tw.writeClasses_or_interfaces(id, "", pkgId, id) tw.writeCompiler_generated( id, (compilerGeneratedKindOverride ?: CompilerGeneratedKinds.CALLABLE_CLASS).kind ) tw.writeHasLocation(id, locId) // Extract constructor val unitType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN) tw.writeConstrs(ids.constructor, "", "", unitType.javaResult.id, id, ids.constructor) tw.writeConstrsKotlinType(ids.constructor, unitType.kotlinResult.id) tw.writeHasLocation(ids.constructor, locId) addModifiers(ids.constructor, "public") // Constructor body val constructorBlockId = ids.constructorBlock tw.writeStmts_block(constructorBlockId, ids.constructor, 0, ids.constructor) tw.writeHasLocation(constructorBlockId, locId) // Super call val baseClass = superTypes.first().classOrNull if (baseClass == null) { logger.warnElement("Cannot find base class", elementToReportOn) } else { val baseConstructor = baseClass.owner.declarations.findSubType { it.symbol is IrConstructorSymbol && superConstructorSelector(it) } if (baseConstructor == null) { logger.warnElement("Cannot find base constructor", elementToReportOn) } else { val baseConstructorId = useFunction(baseConstructor) if (baseConstructorId == null) { logger.errorElement("Cannot find base constructor ID", elementToReportOn) } else { val superCallId = tw.getFreshIdLabel() tw.writeStmts_superconstructorinvocationstmt( superCallId, constructorBlockId, 0, ids.constructor ) tw.writeHasLocation(superCallId, locId) tw.writeCallableBinding(superCallId.cast(), baseConstructorId) extractSuperconstructorArgs(superCallId) } } } addModifiers(id, "final") addVisibilityModifierToLocalOrAnonymousClass(id) extractClassSupertypes( superTypes, listOf(), id, isInterface = false, inReceiverContext = true ) extractEnclosingClass(declarationParent, id, null, locId, listOf()) return id } /** * Extracts the class around a local function or a lambda. The superclass must have a no-arg * constructor. */ private fun extractGeneratedClass( localFunction: IrFunction, superTypes: List, compilerGeneratedKindOverride: CompilerGeneratedKinds? = null ): Label { with("generated class", localFunction) { val ids = getLocallyVisibleFunctionLabels(localFunction) val id = extractGeneratedClass( ids, superTypes, tw.getLocation(localFunction), localFunction, localFunction.parent, compilerGeneratedKindOverride = compilerGeneratedKindOverride ) // Extract local function as a member extractFunction( localFunction, id, extractBody = true, extractMethodAndParameterTypeAccesses = true, extractAnnotations = false, null, listOf() ) return id } } private inner class DeclarationStackAdjuster( val declaration: IrDeclaration, val overriddenAttributes: OverriddenFunctionAttributes? = null ) : Closeable { init { declarationStack.push(declaration, overriddenAttributes) } override fun close() { declarationStack.pop() } } class DeclarationStack { private val stack: Stack> = Stack() fun push(item: IrDeclaration, overriddenAttributes: OverriddenFunctionAttributes?) = stack.push(Pair(item, overriddenAttributes)) fun pop() = stack.pop() fun isEmpty() = stack.isEmpty() fun peek() = stack.peek() fun tryPeek() = if (stack.isEmpty()) null else stack.peek() fun findOverriddenAttributes(f: IrFunction) = stack.lastOrNull { it.first == f }?.second } data class OverriddenFunctionAttributes( val id: Label? = null, val sourceDeclarationId: Label? = null, val sourceLoc: Label? = null, val valueParameters: List? = null, val typeParameters: List? = null, val isStatic: Boolean? = null, val visibility: DescriptorVisibility? = null, val modality: Modality? = null, ) private fun peekDeclStackAsDeclarationParent( elementToReportOn: IrElement ): IrDeclarationParent? { val trapWriter = tw if (declarationStack.isEmpty() && trapWriter is SourceFileTrapWriter) { // If the current declaration is used as a parent, we might end up with an empty stack. // In this case, the source file is the parent. return trapWriter.irFile } val dp = declarationStack.peek().first as? IrDeclarationParent if (dp == null) logger.errorElement("Couldn't find current declaration parent", elementToReportOn) return dp } private enum class CompilerGeneratedKinds(val kind: Int) { DECLARING_CLASSES_OF_ADAPTER_FUNCTIONS(1), GENERATED_DATA_CLASS_MEMBER(2), DEFAULT_PROPERTY_ACCESSOR(3), CLASS_INITIALISATION_METHOD(4), ENUM_CLASS_SPECIAL_MEMBER(5), DELEGATED_PROPERTY_GETTER(6), DELEGATED_PROPERTY_SETTER(7), JVMSTATIC_PROXY_METHOD(8), JVMOVERLOADS_METHOD(9), DEFAULT_ARGUMENTS_METHOD(10), INTERFACE_FORWARDER(11), ENUM_CONSTRUCTOR_ARGUMENT(12), CALLABLE_CLASS(13), } }