Skip to content

Commit bef4011

Browse files
committed
Kotlin: fix type variable erasure inside default function values
Previously because extractClassInstance didn't use the declaration stack, we wouldn't notice that it was legal to refer to its type variable in the context of extracting a specialised method <-> method source-decl edge. This led to erasing the types of the source-decl, so that e.g. Map.put(...) would have signature (Object, Object) not (K, V) as it should.
1 parent 0d98eba commit bef4011

File tree

5 files changed

+94
-13
lines changed

5 files changed

+94
-13
lines changed

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,23 @@ open class KotlinFileExtractor(
254254
}
255255
}
256256

257+
fun extractClassInstance(classLabel: Label<out DbClassorinterface>, c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?, shouldExtractOutline: Boolean, shouldExtractDetails: Boolean) {
258+
DeclarationStackAdjuster(c).use {
259+
if (shouldExtractOutline) {
260+
extractClassWithoutMembers(c, argsIncludingOuterClasses)
261+
}
262+
263+
if (shouldExtractDetails) {
264+
val supertypeMode = if (argsIncludingOuterClasses == null) ExtractSupertypesMode.Raw else ExtractSupertypesMode.Specialised(argsIncludingOuterClasses)
265+
extractClassSupertypes(c, classLabel, supertypeMode, true)
266+
extractNonPrivateMemberPrototypes(c, argsIncludingOuterClasses, classLabel)
267+
}
268+
}
269+
}
270+
257271
// `argsIncludingOuterClasses` can be null to describe a raw generic type.
258272
// For non-generic types it will be zero-length list.
259-
fun extractClassInstance(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?): Label<out DbClassorinterface> {
273+
private fun extractClassWithoutMembers(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?): Label<out DbClassorinterface> {
260274
with("class instance", c) {
261275
if (argsIncludingOuterClasses?.isEmpty() == true) {
262276
logger.error("Instance without type arguments: " + c.name.asString())

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

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -411,16 +411,9 @@ open class KotlinUsesExtractor(
411411
if (replacedArgsIncludingOuterClasses == null || replacedArgsIncludingOuterClasses.isNotEmpty()) {
412412
// If this is a generic type instantiation or a raw type then it has no
413413
// source entity, so we need to extract it here
414-
val extractorWithCSource by lazy { this.withFileOfClass(replacedClass) }
415-
416-
if (!instanceSeenBefore) {
417-
extractorWithCSource.extractClassInstance(replacedClass, replacedArgsIncludingOuterClasses)
418-
}
419-
420-
if (inReceiverContext && tw.lm.genericSpecialisationsExtracted.add(classLabelResult.classLabel)) {
421-
val supertypeMode = if (replacedArgsIncludingOuterClasses == null) ExtractSupertypesMode.Raw else ExtractSupertypesMode.Specialised(replacedArgsIncludingOuterClasses)
422-
extractorWithCSource.extractClassSupertypes(replacedClass, classLabel, supertypeMode, true)
423-
extractorWithCSource.extractNonPrivateMemberPrototypes(replacedClass, replacedArgsIncludingOuterClasses, classLabel)
414+
val shouldExtractClassDetails = inReceiverContext && tw.lm.genericSpecialisationsExtracted.add(classLabelResult.classLabel)
415+
if (!instanceSeenBefore || shouldExtractClassDetails) {
416+
this.withFileOfClass(replacedClass).extractClassInstance(classLabel, replacedClass, replacedArgsIncludingOuterClasses, !instanceSeenBefore, shouldExtractClassDetails)
424417
}
425418
}
426419

java/ql/test/kotlin/library-tests/parameter-defaults/PrintAst.expected

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,3 +1165,59 @@ test.kt:
11651165
# 161| -1: [VarAccess] p0
11661166
# 161| 0: [VarAccess] p1
11671167
# 161| 1: [VarAccess] p2
1168+
# 165| 15: [Class,GenericType,ParameterizedType] TestGenericUsedWithinDefaultValue
1169+
#-----| -2: (Generic Parameters)
1170+
# 165| 0: [TypeVariable] T
1171+
# 165| 1: [Constructor] TestGenericUsedWithinDefaultValue
1172+
# 165| 5: [BlockStmt] { ... }
1173+
# 165| 0: [SuperConstructorInvocationStmt] super(...)
1174+
# 165| 1: [BlockStmt] { ... }
1175+
# 171| 2: [Method] f
1176+
# 171| 3: [TypeAccess] Unit
1177+
#-----| 4: (Parameters)
1178+
# 171| 0: [Parameter] x
1179+
# 171| 0: [TypeAccess] int
1180+
# 171| 1: [Parameter] y
1181+
# 171| 0: [TypeAccess] String
1182+
# 171| 5: [BlockStmt] { ... }
1183+
# 171| 3: [Method] f$default
1184+
# 171| 3: [TypeAccess] Unit
1185+
#-----| 4: (Parameters)
1186+
# 171| 0: [Parameter] p0
1187+
# 171| 0: [TypeAccess] TestGenericUsedWithinDefaultValue<>
1188+
# 171| 1: [Parameter] p1
1189+
# 171| 0: [TypeAccess] int
1190+
# 171| 2: [Parameter] p2
1191+
# 171| 0: [TypeAccess] String
1192+
# 171| 3: [Parameter] p3
1193+
# 171| 0: [TypeAccess] int
1194+
# 171| 4: [Parameter] p4
1195+
# 171| 0: [TypeAccess] Object
1196+
# 171| 5: [BlockStmt] { ... }
1197+
# 171| 0: [IfStmt] if (...)
1198+
# 171| 0: [EQExpr] ... == ...
1199+
# 171| 0: [AndBitwiseExpr] ... & ...
1200+
# 171| 0: [IntegerLiteral] 2
1201+
# 171| 1: [VarAccess] p3
1202+
# 171| 1: [IntegerLiteral] 0
1203+
# 171| 1: [ExprStmt] <Expr>;
1204+
# 171| 0: [AssignExpr] ...=...
1205+
# 171| 0: [VarAccess] p2
1206+
# 171| 1: [MethodAccess] ident(...)
1207+
# 171| -1: [ClassInstanceExpr] new TestGenericUsedWithinDefaultValue<String>(...)
1208+
# 171| -3: [TypeAccess] TestGenericUsedWithinDefaultValue<String>
1209+
# 171| 0: [TypeAccess] String
1210+
# 171| 0: [StringLiteral] Hello world
1211+
# 171| 1: [ReturnStmt] return ...
1212+
# 171| 0: [MethodAccess] f(...)
1213+
# 171| -1: [VarAccess] p0
1214+
# 171| 0: [VarAccess] p1
1215+
# 171| 1: [VarAccess] p2
1216+
# 173| 4: [Method] ident
1217+
# 173| 3: [TypeAccess] T
1218+
#-----| 4: (Parameters)
1219+
# 173| 0: [Parameter] t
1220+
# 173| 0: [TypeAccess] T
1221+
# 173| 5: [BlockStmt] { ... }
1222+
# 173| 0: [ReturnStmt] return ...
1223+
# 173| 0: [VarAccess] t

java/ql/test/kotlin/library-tests/parameter-defaults/erasure.ql

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

3+
class InstantiatedType extends ParameterizedType {
4+
InstantiatedType() { typeArgs(_, _, this) }
5+
}
6+
37
// This checks that all type parameter references are erased in the context of a $default function.
48
predicate containsTypeVariables(Type t) {
5-
t != t.getErasure() and
6-
not t.getErasure().(GenericType).getRawType() = t
9+
t instanceof TypeVariable or
10+
containsTypeVariables(t.(InstantiatedType).getATypeArgument()) or
11+
containsTypeVariables(t.(NestedType).getEnclosingType()) or
12+
containsTypeVariables(t.(Wildcard).getATypeBound().getType())
713
}
814

915
from Expr e

java/ql/test/kotlin/library-tests/parameter-defaults/test.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,15 @@ class VisibilityTests {
161161
private fun i(x: Int, y: Int = 0) = x + y
162162

163163
}
164+
165+
class TestGenericUsedWithinDefaultValue<T> {
166+
167+
// This tests parameter erasure works properly: we should notice that here the type variable T
168+
// isn't used in the specialisation TestGenericUsedWithinDefaultValue<String>, but it can be
169+
// cited in contexts like "the signature of the source declaration of 'TestGenericUsedWithinDefaultValue<String>.f(String)' is 'f(T)'",
170+
// not 'f(Object)' as we might mistakenly conclude if we're inappropriately erasing 'T'.
171+
fun f(x: Int, y: String = TestGenericUsedWithinDefaultValue<String>().ident("Hello world")) { }
172+
173+
fun ident(t: T) = t
174+
175+
}

0 commit comments

Comments
 (0)