Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bb3049a

Browse files
smowtonigfoo
authored andcommitted
Extract generic method prototypes
These feature substituted types according to their declaring generic specialisation, with wildcards that reach top-level being converted to their upper or lower bound depending on usage context. This commit also includes an incidental fix such that constructors declare their return-type as unit, consistent with the Java extractor.
1 parent b38f47f commit bb3049a

8 files changed

Lines changed: 387 additions & 58 deletions

File tree

java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.github.codeql
22

33
import 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
47
import com.semmle.extractor.java.OdasaOutput
58
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
69
import org.jetbrains.kotlin.builtins.functions.BuiltInFunctionArity
@@ -12,10 +15,12 @@ import org.jetbrains.kotlin.ir.declarations.*
1215
import org.jetbrains.kotlin.ir.expressions.*
1316
import org.jetbrains.kotlin.ir.interpreter.toIrConst
1417
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
18+
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
1519
import org.jetbrains.kotlin.ir.types.*
1620
import org.jetbrains.kotlin.ir.util.*
1721
import org.jetbrains.kotlin.name.FqName
1822
import org.jetbrains.kotlin.util.OperatorNameConventions
23+
import org.jetbrains.kotlin.types.Variance
1924

2025
open 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

Comments
 (0)