11package com.github.codeql
22
33import com.github.codeql.utils.versions.functionN
4+ import com.github.codeql.utils.lowerBound
5+ import com.github.codeql.utils.substituteTypeAndArguments
6+ import com.github.codeql.utils.upperBound
47import com.semmle.extractor.java.OdasaOutput
58import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
69import org.jetbrains.kotlin.builtins.functions.BuiltInFunctionArity
@@ -12,10 +15,12 @@ import org.jetbrains.kotlin.ir.declarations.*
1215import org.jetbrains.kotlin.ir.expressions.*
1316import org.jetbrains.kotlin.ir.interpreter.toIrConst
1417import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
18+ import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
1519import org.jetbrains.kotlin.ir.types.*
1620import org.jetbrains.kotlin.ir.util.*
1721import org.jetbrains.kotlin.name.FqName
1822import org.jetbrains.kotlin.util.OperatorNameConventions
23+ import org.jetbrains.kotlin.types.Variance
1924
2025open class KotlinFileExtractor (
2126 override val logger : FileLogger ,
@@ -49,7 +54,7 @@ open class KotlinFileExtractor(
4954 is IrClass -> return getClassLabel(element, listOf ()).classLabel
5055 is IrTypeParameter -> return getTypeParameterLabel(element)
5156 is IrFunction -> return getFunctionLabel(element)
52- is IrValueParameter -> return getValueParameterLabel(element)
57+ is IrValueParameter -> return getValueParameterLabel(element, null )
5358 is IrProperty -> return getPropertyLabel(element)
5459 is IrField -> return getFieldLabel(element)
5560 is IrEnumEntry -> return getEnumEntryLabel(element)
@@ -87,7 +92,7 @@ open class KotlinFileExtractor(
8792 return id
8893 }
8994
90- fun extractClassInstance (c : IrClass , typeArgs : List <IrTypeArgument >): Label <out DbClassorinterface > {
95+ fun extractClassInstance (c : IrClass , typeArgs : List <IrTypeArgument >, extractFunctionPrototypes : Boolean ): Label <out DbClassorinterface > {
9196 if (typeArgs.isEmpty()) {
9297 logger.warn(Severity .ErrorSevere , " Instance without type arguments: " + c.name.asString())
9398 }
@@ -128,6 +133,18 @@ open class KotlinFileExtractor(
128133 val locId = tw.getLocation(c)
129134 tw.writeHasLocation(id, locId)
130135
136+ if (extractFunctionPrototypes) {
137+ val typeParamSubstitution = c.typeParameters.map({ it.symbol }).zip(typeArgs).toMap()
138+
139+ c.declarations.map {
140+ when (it) {
141+ is IrFunction -> extractFunction(it, id, false , typeParamSubstitution)
142+ is IrProperty -> extractProperty(it, id, false , typeParamSubstitution)
143+ else -> {}
144+ }
145+ }
146+ }
147+
131148 return id
132149 }
133150
@@ -259,12 +276,15 @@ open class KotlinFileExtractor(
259276 return FieldResult (instanceId, instanceName)
260277 }
261278
262- private fun extractValueParameter (vp : IrValueParameter , parent : Label <out DbCallable >, idx : Int ): TypeResults {
263- return extractValueParameter(useValueParameter(vp), vp.type, vp.name.asString(), tw.getLocation(vp), parent, idx)
279+ private fun extractValueParameter (vp : IrValueParameter , parent : Label <out DbCallable >, idx : Int , typeSubstitutionMap : Map < IrTypeParameterSymbol , IrTypeArgument > ? ): TypeResults {
280+ return extractValueParameter(useValueParameter(vp, parent ), vp.type, vp.name.asString(), tw.getLocation(vp), parent, idx, typeSubstitutionMap )
264281 }
265282
266- private fun extractValueParameter (id : Label <out DbParam >, t : IrType , name : String , locId : Label <DbLocation >, parent : Label <out DbCallable >, idx : Int ): TypeResults {
267- val type = useType(t)
283+ private fun extractValueParameter (id : Label <out DbParam >, t : IrType , name : String , locId : Label <DbLocation >, parent : Label <out DbCallable >, idx : Int , typeSubstitutionMap : Map <IrTypeParameterSymbol , IrTypeArgument >? ): TypeResults {
284+ val substitutedType = t.substituteTypeAndArguments(typeSubstitutionMap) {
285+ it.lowerBound(pluginContext)
286+ }
287+ val type = useType(substitutedType)
268288 tw.writeParams(id, type.javaResult.id, type.kotlinResult.id, idx, parent, id)
269289 tw.writeHasLocation(id, locId)
270290 tw.writeParamName(id, name)
@@ -338,7 +358,7 @@ open class KotlinFileExtractor(
338358 }
339359 }
340360
341- fun extractFunction (f : IrFunction , parentId : Label <out DbReftype >): Label <out DbCallable > {
361+ fun extractFunction (f : IrFunction , parentId : Label <out DbReftype >, extractBody : Boolean = true, typeSubstitutionMap : Map < IrTypeParameterSymbol , IrTypeArgument > ? = null ): Label <out DbCallable > {
342362 currentFunction = f
343363
344364 f.typeParameters.map { extractTypeParameter(it) }
@@ -349,19 +369,21 @@ open class KotlinFileExtractor(
349369 if (f.isLocalFunction())
350370 getLocalFunctionLabels(f).function
351371 else
352- useFunction<DbCallable >(f)
372+ // TODO: figure out whether to standardise on naming top-level functions for the file-class
373+ // or (as temporarily done here) for their containing package.
374+ useFunction<DbCallable >(f, if (f.parent is IrFile ) useDeclarationParent(f.parent) else parentId)
353375
354376 val extReceiver = f.extensionReceiverParameter
355377 val idxOffset = if (extReceiver != null ) 1 else 0
356378 val paramTypes = f.valueParameters.mapIndexed { i, vp ->
357- extractValueParameter(vp, id, i + idxOffset)
379+ extractValueParameter(vp, id, i + idxOffset, typeSubstitutionMap )
358380 }
359381 val allParamTypes = if (extReceiver != null ) {
360382 val extendedType = useType(extReceiver.type)
361383 @Suppress(" UNCHECKED_CAST" )
362384 tw.writeKtExtensionFunctions(id as Label <DbMethod >, extendedType.javaResult.id, extendedType.kotlinResult.id)
363385
364- val t = extractValueParameter(extReceiver, id, 0 )
386+ val t = extractValueParameter(extReceiver, id, 0 , null )
365387 val l = mutableListOf (t)
366388 l.addAll(paramTypes)
367389 l
@@ -371,13 +393,21 @@ open class KotlinFileExtractor(
371393
372394 val paramsSignature = allParamTypes.joinToString(separator = " ," , prefix = " (" , postfix = " )" ) { it.javaResult.signature!! }
373395
396+ val substReturnType = f.returnType.substituteTypeAndArguments(typeSubstitutionMap) {
397+ it.upperBound(pluginContext)
398+ }
399+
374400 if (f.symbol is IrConstructorSymbol ) {
375- val returnType = useType(erase(f.returnType), TypeContext .RETURN )
376- val shortName = if (f.returnType.isAnonymous) " " else f.returnType.classFqName?.shortName()?.asString() ? : f.name.asString()
401+ val unitType = useType(pluginContext.irBuiltIns.unitType, TypeContext .RETURN )
402+ val shortName = when {
403+ f.returnType.isAnonymous -> " "
404+ typeSubstitutionMap != null -> useType(substReturnType).javaResult.shortName
405+ else -> f.returnType.classFqName?.shortName()?.asString() ? : f.name.asString()
406+ }
377407 @Suppress(" UNCHECKED_CAST" )
378- tw.writeConstrs(id as Label <DbConstructor >, shortName, " $shortName$paramsSignature " , returnType .javaResult.id, returnType .kotlinResult.id, parentId, id)
408+ tw.writeConstrs(id as Label <DbConstructor >, shortName, " $shortName$paramsSignature " , unitType .javaResult.id, unitType .kotlinResult.id, parentId, id)
379409 } else {
380- val returnType = useType(f.returnType , TypeContext .RETURN )
410+ val returnType = useType(substReturnType , TypeContext .RETURN )
381411 val shortName = getFunctionShortName(f)
382412 @Suppress(" UNCHECKED_CAST" )
383413 tw.writeMethods(id as Label <DbMethod >, shortName, " $shortName$paramsSignature " , returnType.javaResult.id, returnType.kotlinResult.id, parentId, id)
@@ -386,7 +416,9 @@ open class KotlinFileExtractor(
386416
387417 tw.writeHasLocation(id, locId)
388418 val body = f.body
389- if (body != null ) {
419+ if (body != null && extractBody) {
420+ // Type substitution should only be used to extract a prototype, not the body as well:
421+ assert (typeSubstitutionMap == null )
390422 extractBody(body, id)
391423 }
392424
@@ -403,8 +435,8 @@ open class KotlinFileExtractor(
403435 return id
404436 }
405437
406- fun extractProperty (p : IrProperty , parentId : Label <out DbReftype >) {
407- val id = useProperty(p)
438+ fun extractProperty (p : IrProperty , parentId : Label <out DbReftype >, extractBackingField : Boolean = true, typeSubstitutionMap : Map < IrTypeParameterSymbol , IrTypeArgument > ? = null ) {
439+ val id = useProperty(p, parentId )
408440 val locId = tw.getLocation(p)
409441 tw.writeKtProperties(id, p.name.asString())
410442 tw.writeHasLocation(id, locId)
@@ -415,7 +447,7 @@ open class KotlinFileExtractor(
415447
416448 if (getter != null ) {
417449 @Suppress(" UNCHECKED_CAST" )
418- val getterId = extractFunction(getter, parentId) as Label <out DbMethod >
450+ val getterId = extractFunction(getter, parentId, extractBackingField, typeSubstitutionMap ) as Label <out DbMethod >
419451 tw.writeKtPropertyGetters(id, getterId)
420452 } else {
421453 if (p.modality != Modality .FINAL || ! isExternalDeclaration(p)) {
@@ -428,15 +460,15 @@ open class KotlinFileExtractor(
428460 logger.warnElement(Severity .ErrorSevere , " !isVar property with a setter" , p)
429461 }
430462 @Suppress(" UNCHECKED_CAST" )
431- val setterId = extractFunction(setter, parentId) as Label <out DbMethod >
463+ val setterId = extractFunction(setter, parentId, extractBackingField, typeSubstitutionMap ) as Label <out DbMethod >
432464 tw.writeKtPropertySetters(id, setterId)
433465 } else {
434466 if (p.isVar && ! isExternalDeclaration(p)) {
435467 logger.warnElement(Severity .ErrorSevere , " isVar property without a setter" , p)
436468 }
437469 }
438470
439- if (bf != null ) {
471+ if (bf != null && extractBackingField ) {
440472 val fieldId = extractField(bf, parentId)
441473 tw.writeKtPropertyBackingFields(id, fieldId)
442474 }
@@ -692,17 +724,16 @@ open class KotlinFileExtractor(
692724 isFunction(" kotlin" , " Double" , fName)
693725 }
694726
695- fun extractMethodAccess (callTarget : IrFunction , extractTypeArguments : Boolean = true) {
727+ fun extractMethodAccess (callTarget : IrFunction , extractMethodTypeArguments : Boolean = true, extractClassTypeArguments : Boolean = false) {
696728 val id = tw.getFreshIdLabel<DbMethodaccess >()
697729 val type = useType(c.type)
698730 val locId = tw.getLocation(c)
699-
700731 tw.writeExprs_methodaccess(id, type.javaResult.id, type.kotlinResult.id, parent, idx)
701732 tw.writeHasLocation(id, locId)
702733 tw.writeCallableEnclosingExpr(id, callable)
703734 tw.writeStatementEnclosingExpr(id, enclosingStmt)
704735
705- if (extractTypeArguments ) {
736+ if (extractMethodTypeArguments ) {
706737 // type arguments at index -2, -3, ...
707738 extractTypeArguments(c, id, callable, enclosingStmt, - 2 , true )
708739 }
@@ -730,10 +761,28 @@ open class KotlinFileExtractor(
730761 tw.writeStatementEnclosingExpr(typeAccessId, enclosingStmt)
731762
732763 } else {
733- val methodId = useFunction<DbMethod >(callTarget)
764+ val dr = c.dispatchReceiver
765+
766+ // Returns true if type is C<T1, T2, ...> where C is declared `class <T1, T2, ...> C { ... }`
767+ fun isUnspecialised (type : IrSimpleType ) =
768+ type.classifier.owner is IrClass &&
769+ (type.classifier.owner as IrClass ).typeParameters.zip(type.arguments).all { paramAndArg ->
770+ (paramAndArg.second as ? IrTypeProjection )?.let {
771+ // Type arg refers to the class' own type parameter?
772+ it.variance == Variance .INVARIANT &&
773+ it.type.classifierOrNull?.owner == = paramAndArg.first
774+ } ? : false
775+ }
776+
777+ val drType = dr?.type
778+ val methodId =
779+ if (drType != null && extractClassTypeArguments && drType is IrSimpleType && ! isUnspecialised(drType))
780+ useFunction<DbCallable >(callTarget, drType.arguments)
781+ else
782+ useFunction<DbCallable >(callTarget)
783+
734784 tw.writeCallableBinding(id, methodId)
735785
736- val dr = c.dispatchReceiver
737786 if (dr != null ) {
738787 extractExpressionExpr(dr, callable, id, - 1 , enclosingStmt)
739788 }
@@ -1055,7 +1104,7 @@ open class KotlinFileExtractor(
10551104 }
10561105 }
10571106 else -> {
1058- extractMethodAccess(c.symbol.owner)
1107+ extractMethodAccess(c.symbol.owner, true , true )
10591108 }
10601109 }
10611110 }
@@ -1725,7 +1774,7 @@ open class KotlinFileExtractor(
17251774
17261775 val argsParamId = tw.getFreshIdLabel<DbParam >()
17271776 val argsParamType = pluginContext.irBuiltIns.arrayClass.typeWith(pluginContext.irBuiltIns.anyNType)
1728- val paramType = extractValueParameter(argsParamId, argsParamType, " args" , locId, methodId, 0 )
1777+ val paramType = extractValueParameter(argsParamId, argsParamType, " args" , locId, methodId, 0 , null )
17291778
17301779 val paramsSignature = " (${paramType.javaResult.signature!! } )"
17311780
0 commit comments