Skip to content

Commit 01aaa6c

Browse files
authored
Merge pull request #9123 from smowton/smowton/fix/type-variable-in-scope-consistency
Kotlin: fix cases where type variables were used out of scope
2 parents d18e03c + c9232c0 commit 01aaa6c

File tree

10 files changed

+411
-372
lines changed

10 files changed

+411
-372
lines changed

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

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -173,19 +173,7 @@ open class KotlinFileExtractor(
173173

174174
fun extractTypeParameter(tp: IrTypeParameter, apparentIndex: Int): Label<out DbTypevariable>? {
175175
with("type parameter", tp) {
176-
val parentId: Label<out DbClassorinterfaceorcallable>? = when (val parent = tp.parent) {
177-
is IrFunction -> useFunction(parent)
178-
is IrClass -> useClassSource(parent)
179-
else -> {
180-
logger.errorElement("Unexpected type parameter parent", tp)
181-
null
182-
}
183-
}
184-
185-
if (parentId == null) {
186-
return null
187-
}
188-
176+
val parentId = getTypeParameterParentLabel(tp) ?: return null
189177
val id = tw.getLabelFor<DbTypevariable>(getTypeParameterLabel(tp))
190178

191179
// Note apparentIndex does not necessarily equal `tp.index`, because at least constructor type parameters
@@ -334,17 +322,7 @@ open class KotlinFileExtractor(
334322
val typeParamSubstitution =
335323
when (argsIncludingOuterClasses) {
336324
null -> { x: IrType, _: TypeContext, _: IrPluginContext -> x.toRawType() }
337-
else -> {
338-
makeTypeGenericSubstitutionMap(c, argsIncludingOuterClasses).let {
339-
{ x: IrType, useContext: TypeContext, pluginContext: IrPluginContext ->
340-
x.substituteTypeAndArguments(
341-
it,
342-
useContext,
343-
pluginContext
344-
)
345-
}
346-
}
347-
}
325+
else -> makeGenericSubstitutionFunction(c, argsIncludingOuterClasses)
348326
}
349327

350328
c.declarations.map {
@@ -511,12 +489,12 @@ open class KotlinFileExtractor(
511489
return FieldResult(instanceId, instanceName)
512490
}
513491

514-
private fun extractValueParameter(vp: IrValueParameter, parent: Label<out DbCallable>, idx: Int, typeSubstitution: TypeSubstitution?, parentSourceDeclaration: Label<out DbCallable>, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?, extractTypeAccess: Boolean): TypeResults {
492+
private fun extractValueParameter(vp: IrValueParameter, parent: Label<out DbCallable>, idx: Int, typeSubstitution: TypeSubstitution?, parentSourceDeclaration: Label<out DbCallable>, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?, extractTypeAccess: Boolean, locOverride: Label<DbLocation>? = null): TypeResults {
515493
with("value parameter", vp) {
516-
val location = getLocation(vp, classTypeArgsIncludingOuterClasses)
494+
val location = locOverride ?: getLocation(vp, classTypeArgsIncludingOuterClasses)
517495
val id = useValueParameter(vp, parent)
518496
if (extractTypeAccess) {
519-
extractTypeAccessRecursive(vp.type, location, id, -1)
497+
extractTypeAccessRecursive(typeSubstitution?.let { it(vp.type, TypeContext.OTHER, pluginContext) } ?: vp.type, location, id, -1)
520498
}
521499
return extractValueParameter(id, vp.type, vp.name.asString(), location, parent, idx, typeSubstitution, useValueParameter(vp, parentSourceDeclaration), vp.isVararg)
522500
}
@@ -676,7 +654,7 @@ open class KotlinFileExtractor(
676654
}
677655
}
678656

679-
fun extractFunction(f: IrFunction, parentId: Label<out DbReftype>, extractBody: Boolean, extractMethodAndParameterTypeAccesses: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?, idOverride: Label<DbMethod>? = null): Label<out DbCallable>? {
657+
fun extractFunction(f: IrFunction, parentId: Label<out DbReftype>, extractBody: Boolean, extractMethodAndParameterTypeAccesses: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?, idOverride: Label<DbMethod>? = null, locOverride: Label<DbLocation>? = null): Label<out DbCallable>? {
680658
if (isFake(f)) return null
681659

682660
with("function", f) {
@@ -694,21 +672,21 @@ open class KotlinFileExtractor(
694672
useFunction<DbCallable>(f, parentId, classTypeArgsIncludingOuterClasses, noReplace = true)
695673

696674
val sourceDeclaration =
697-
if (typeSubstitution != null)
675+
if (typeSubstitution != null && idOverride == null)
698676
useFunction(f)
699677
else
700678
id
701679

702680
val extReceiver = f.extensionReceiverParameter
703681
val idxOffset = if (extReceiver != null) 1 else 0
704682
val paramTypes = f.valueParameters.mapIndexed { i, vp ->
705-
extractValueParameter(vp, id, i + idxOffset, typeSubstitution, sourceDeclaration, classTypeArgsIncludingOuterClasses, extractTypeAccess = extractMethodAndParameterTypeAccesses)
683+
extractValueParameter(vp, id, i + idxOffset, typeSubstitution, sourceDeclaration, classTypeArgsIncludingOuterClasses, extractTypeAccess = extractMethodAndParameterTypeAccesses, locOverride)
706684
}
707685
val allParamTypes = if (extReceiver != null) {
708686
val extendedType = useType(extReceiver.type)
709687
tw.writeKtExtensionFunctions(id.cast<DbMethod>(), extendedType.javaResult.id, extendedType.kotlinResult.id)
710688

711-
val t = extractValueParameter(extReceiver, id, 0, null, sourceDeclaration, classTypeArgsIncludingOuterClasses, extractTypeAccess = extractMethodAndParameterTypeAccesses)
689+
val t = extractValueParameter(extReceiver, id, 0, null, sourceDeclaration, classTypeArgsIncludingOuterClasses, extractTypeAccess = extractMethodAndParameterTypeAccesses, locOverride)
712690
listOf(t) + paramTypes
713691
} else {
714692
paramTypes
@@ -718,7 +696,7 @@ open class KotlinFileExtractor(
718696

719697
val substReturnType = typeSubstitution?.let { it(f.returnType, TypeContext.RETURN, pluginContext) } ?: f.returnType
720698

721-
val locId = getLocation(f, classTypeArgsIncludingOuterClasses)
699+
val locId = locOverride ?: getLocation(f, classTypeArgsIncludingOuterClasses)
722700

723701
if (f.symbol is IrConstructorSymbol) {
724702
val unitType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN)
@@ -738,7 +716,7 @@ open class KotlinFileExtractor(
738716
tw.writeMethodsKotlinType(methodId, returnType.kotlinResult.id)
739717

740718
if (extractMethodAndParameterTypeAccesses) {
741-
extractTypeAccessRecursive(f.returnType, locId, id, -1)
719+
extractTypeAccessRecursive(substReturnType, locId, id, -1)
742720
}
743721

744722
if (shortName.nameInDB != shortName.kotlinName) {
@@ -4014,7 +3992,12 @@ open class KotlinFileExtractor(
40143992
helper.extractParameterToFieldAssignmentInConstructor("<fn>", functionType, fieldId, 0, 1)
40153993

40163994
// add implementation function
4017-
extractFunction(samMember, classId, extractBody = false, extractMethodAndParameterTypeAccesses = true, null, null, ids.function)
3995+
val classTypeArgs = (e.type as? IrSimpleType)?.arguments
3996+
val typeSub = classTypeArgs?.let { makeGenericSubstitutionFunction(typeOwner, it) }
3997+
3998+
fun trySub(t: IrType, context: TypeContext) = if (typeSub == null) t else typeSub(t, context, pluginContext)
3999+
4000+
extractFunction(samMember, classId, extractBody = false, extractMethodAndParameterTypeAccesses = true, typeSub, classTypeArgs, idOverride = ids.function, locOverride = tw.getLocation(e))
40184001

40194002
//body
40204003
val blockId = tw.getFreshIdLabel<DbBlock>()
@@ -4037,7 +4020,7 @@ open class KotlinFileExtractor(
40374020

40384021
// Call to original `invoke`:
40394022
val callId = tw.getFreshIdLabel<DbMethodaccess>()
4040-
val callType = useType(samMember.returnType)
4023+
val callType = useType(trySub(samMember.returnType, TypeContext.RETURN))
40414024
tw.writeExprs_methodaccess(callId, callType.javaResult.id, returnId, 0)
40424025
tw.writeExprsKotlinType(callId, callType.kotlinResult.id)
40434026
extractCommonExpr(callId)
@@ -4061,7 +4044,7 @@ open class KotlinFileExtractor(
40614044

40624045
fun extractArgument(p: IrValueParameter, idx: Int, parent: Label<out DbExprparent>) {
40634046
val argsAccessId = tw.getFreshIdLabel<DbVaraccess>()
4064-
val paramType = useType(p.type)
4047+
val paramType = useType(trySub(p.type, TypeContext.OTHER))
40654048
tw.writeExprs_varaccess(argsAccessId, paramType.javaResult.id, parent, idx)
40664049
tw.writeExprsKotlinType(argsAccessId, paramType.kotlinResult.id)
40674050
extractCommonExpr(argsAccessId)

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

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,17 @@ open class KotlinUsesExtractor(
215215
fun makeTypeGenericSubstitutionMap(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>) =
216216
getTypeParametersInScope(c).map({ it.symbol }).zip(argsIncludingOuterClasses.map { it.withQuestionMark(true) }).toMap()
217217

218+
fun makeGenericSubstitutionFunction(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>) =
219+
makeTypeGenericSubstitutionMap(c, argsIncludingOuterClasses).let {
220+
{ x: IrType, useContext: TypeContext, pluginContext: IrPluginContext ->
221+
x.substituteTypeAndArguments(
222+
it,
223+
useContext,
224+
pluginContext
225+
)
226+
}
227+
}
228+
218229
// The Kotlin compiler internal representation of Outer<A, B>.Inner<C, D>.InnerInner<E, F>.someFunction<G, H>.LocalClass<I, J> is LocalClass<I, J, G, H, E, F, C, D, A, B>. This function returns [A, B, C, D, E, F, G, H, I, J].
219230
fun orderTypeArgsLeftToRight(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?): List<IrTypeArgument>? {
220231
if(argsIncludingOuterClasses.isNullOrEmpty())
@@ -242,10 +253,12 @@ open class KotlinUsesExtractor(
242253
val extractClass = substituteClass ?: c
243254

244255
// `KFunction1<T1,T2>` is substituted by `KFunction<T>`. The last type argument is the return type.
256+
// Similarly Function23 and above get replaced by kotlin.jvm.functions.FunctionN with only one type arg, the result type.
245257
// References to SomeGeneric<T1, T2, ...> where SomeGeneric is declared SomeGeneric<T1, T2, ...> are extracted
246258
// as if they were references to the unbound type SomeGeneric.
247259
val extractedTypeArgs = when {
248-
c.symbol.isKFunction() && typeArgs != null && typeArgs.isNotEmpty() -> listOf(typeArgs.last())
260+
extractClass.symbol.isKFunction() && typeArgs != null && typeArgs.isNotEmpty() -> listOf(typeArgs.last())
261+
extractClass.fqNameWhenAvailable == FqName("kotlin.jvm.functions.FunctionN") && typeArgs != null && typeArgs.isNotEmpty() -> listOf(typeArgs.last())
249262
typeArgs != null && isUnspecialised(c, typeArgs) -> listOf()
250263
else -> typeArgs
251264
}
@@ -1083,8 +1096,21 @@ open class KotlinUsesExtractor(
10831096
return classTypeResult.id
10841097
}
10851098

1099+
fun getTypeParameterParentLabel(param: IrTypeParameter) =
1100+
param.parent.let {
1101+
when (it) {
1102+
is IrClass -> useClassSource(it)
1103+
is IrFunction -> useFunction(it, noReplace = true)
1104+
else -> { logger.error("Unexpected type parameter parent $it"); null }
1105+
}
1106+
}
1107+
10861108
fun getTypeParameterLabel(param: IrTypeParameter): String {
1087-
val parentLabel = useDeclarationParent(param.parent, false)
1109+
// Use this instead of `useDeclarationParent` so we can use useFunction with noReplace = true,
1110+
// ensuring that e.g. a method-scoped type variable declared on kotlin.String.transform <R> gets
1111+
// a different name to the corresponding java.lang.String.transform <R>, even though useFunction
1112+
// will usually replace references to one function with the other.
1113+
val parentLabel = getTypeParameterParentLabel(param)
10881114
return "@\"typevar;{$parentLabel};${param.name}\""
10891115
}
10901116

java/ql/consistency-queries/typeParametersInScope.ql

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import java
22

3+
class InstantiatedType extends ParameterizedType {
4+
InstantiatedType() { typeArgs(_, _, this) }
5+
}
6+
37
Type getAMentionedType(RefType type) {
48
result = type
59
or
610
result = getAMentionedType(type).(Array).getElementType()
711
or
8-
result = getAMentionedType(type).(ParameterizedType).getATypeArgument()
12+
result = getAMentionedType(type).(InstantiatedType).getATypeArgument()
913
or
1014
result = getAMentionedType(type).(NestedType).getEnclosingType()
1115
}
@@ -24,17 +28,26 @@ Type getATypeUsedInClass(RefType type) {
2428
result = getAMentionedType(getATypeUsedInClass(type))
2529
}
2630

27-
TypeVariable getATypeVariableInScope(RefType type) {
28-
result = type.getACallable().(GenericCallable).getATypeParameter()
29-
or
30-
result = type.(GenericType).getATypeParameter()
31+
Element getEnclosingElementStar(RefType e) {
32+
result = e
3133
or
32-
result = getAMentionedType(type.(ParameterizedType).getATypeArgument())
33-
or
34-
result = getATypeVariableInScope(type.getEnclosingType())
34+
result.contains(e)
35+
}
36+
37+
TypeVariable getATypeVariableInScope(RefType type) {
38+
exists(Element e | e = getEnclosingElementStar(type) |
39+
result = e.(RefType).getACallable().(GenericCallable).getATypeParameter()
40+
or
41+
result = e.(GenericType).getATypeParameter()
42+
or
43+
result = e.(GenericCallable).getATypeParameter()
44+
or
45+
result = getAMentionedType(e.(InstantiatedType).getATypeArgument())
46+
)
3547
}
3648

3749
from ClassOrInterface typeUser, TypeVariable outOfScope
3850
where
39-
outOfScope = getAMentionedType(typeUser) and not outOfScope = getATypeVariableInScope(typeUser)
40-
select "Type " + typeUser + " uses out-of-scope type variable " + outOfScope
51+
outOfScope = getATypeUsedInClass(typeUser) and not outOfScope = getATypeVariableInScope(typeUser)
52+
select "Type " + typeUser + " uses out-of-scope type variable " + outOfScope +
53+
". Note the Java extractor is known to sometimes do this; the Kotlin extractor should not."

java/ql/test/kotlin/library-tests/dataflow/func/util.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
class Processor {
2-
fun <R> process(f: () -> R) : R {
2+
fun <R1> process(f: () -> R1) : R1 {
33
return f()
44
}
55

6-
fun <T, R> process(f: (T) -> R, arg: T) : R {
6+
fun <T, R2> process(f: (T) -> R2, arg: T) : R2 {
77
return f(arg)
88
}
99

10-
fun <T0, T1, R> process(f: (T0, T1) -> R, arg0: T0, arg1: T1) : R {
10+
fun <T0, T1, R3> process(f: (T0, T1) -> R3, arg0: T0, arg1: T1) : R3 {
1111
return f(arg0, arg1)
1212
}
1313

14-
fun <T, R> process(
15-
f: (T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T) -> R,
16-
a: T, b: T) : R {
14+
fun <T, R4> process(
15+
f: (T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T) -> R4,
16+
a: T, b: T) : R4 {
1717
return f(a,b,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a)
1818
}
1919

20-
fun <T, R> processExt(f: T.(T) -> R, ext: T, arg: T) : R {
20+
fun <T, R5> processExt(f: T.(T) -> R5, ext: T, arg: T) : R5 {
2121
return ext.f(arg)
2222
}
2323
}

0 commit comments

Comments
 (0)