Skip to content

Commit 357e460

Browse files
authored
Merge pull request github#11588 from tamasvajk/kotlin-extension-defaults
Kotlin: Fix extraction of `$default` extension functions
2 parents 7d1f10b + 6bcfdfc commit 357e460

File tree

16 files changed

+177
-83
lines changed

16 files changed

+177
-83
lines changed

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

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,7 @@ open class KotlinFileExtractor(
957957
val locId = getLocation(f, null)
958958
val extReceiver = f.extensionReceiverParameter
959959
val dispatchReceiver = if (f.shouldExtractAsStatic) null else f.dispatchReceiverParameter
960-
val parameterTypes = listOfNotNull(extReceiver?.let { erase(it.type) }) + getDefaultsMethodArgTypes(f)
960+
val parameterTypes = getDefaultsMethodArgTypes(f)
961961
val allParamTypeResults = parameterTypes.mapIndexed { i, paramType ->
962962
val paramId = tw.getLabelFor<DbParam>(getValueParameterLabel(id, i))
963963
extractValueParameter(paramId, paramType, "p$i", locId, id, i, paramId, isVararg = false, syntheticParameterNames = true, isCrossinline = false, isNoinline = false).also {
@@ -975,6 +975,11 @@ open class KotlinFileExtractor(
975975
val methodId = id.cast<DbMethod>()
976976
extractMethod(methodId, locId, shortName, erase(f.returnType), paramsSignature, parentId, methodId, origin = null, extractTypeAccess = extractMethodAndParameterTypeAccesses)
977977
addModifiers(id, "static")
978+
if (extReceiver != null) {
979+
val idx = if (dispatchReceiver != null) 1 else 0
980+
val extendedType = allParamTypeResults[idx]
981+
tw.writeKtExtensionFunctions(methodId, extendedType.javaResult.id, extendedType.kotlinResult.id)
982+
}
978983
}
979984
tw.writeHasLocation(id, locId)
980985
if (f.visibility != DescriptorVisibilities.PRIVATE && f.visibility != DescriptorVisibilities.PRIVATE_TO_THIS) {
@@ -1040,8 +1045,8 @@ open class KotlinFileExtractor(
10401045
val realFnIdxOffset = if (f.extensionReceiverParameter != null) 1 else 0
10411046
val paramMappings = f.valueParameters.mapIndexed { idx, param -> Triple(param.type, idx + paramIdxOffset, idx + realFnIdxOffset) } +
10421047
listOfNotNull(
1043-
dispatchReceiver?.let { Triple(it.type, realFnIdxOffset, -1) },
1044-
extReceiver?.let { Triple(it.type, 0, 0) }
1048+
dispatchReceiver?.let { Triple(it.type, 0, -1) },
1049+
extReceiver?.let { Triple(it.type, if (dispatchReceiver != null) 1 else 0, 0) }
10451050
)
10461051
paramMappings.forEach { (type, fromIdx, toIdx) ->
10471052
extractVariableAccess(tw.getLabelFor<DbParam>(getValueParameterLabel(id, fromIdx)), type, locId, thisCallId, toIdx, id, returnId)
@@ -1184,6 +1189,7 @@ open class KotlinFileExtractor(
11841189
id
11851190

11861191
val extReceiver = f.extensionReceiverParameter
1192+
// The following parameter order is correct, because member $default methods (where the order would be [dispatchParam], [extensionParam], normalParams) are not extracted here
11871193
val fParameters = listOfNotNull(extReceiver) + (overriddenAttributes?.valueParameters ?: f.valueParameters)
11881194
val paramTypes = fParameters.mapIndexed { i, vp ->
11891195
extractValueParameter(vp, id, i, typeSubstitution, sourceDeclaration, classTypeArgsIncludingOuterClasses, extractTypeAccess = extractMethodAndParameterTypeAccesses, overriddenAttributes?.sourceLoc)
@@ -1793,11 +1799,12 @@ open class KotlinFileExtractor(
17931799
) ?: pluginContext.irBuiltIns.anyType
17941800

17951801
private fun getDefaultsMethodArgTypes(f: IrFunction) =
1796-
// The $default method has type ([extensionReceiver], [dispatchReceiver], paramTypes..., int, Object)
1802+
// The $default method has type ([dispatchReceiver], [extensionReceiver], paramTypes..., int, Object)
17971803
// All parameter types are erased. The trailing int is a mask indicating which parameter values are real
17981804
// and which should be replaced by defaults. The final Object parameter is apparently always null.
17991805
(
18001806
listOfNotNull(if (f.shouldExtractAsStatic) null else f.dispatchReceiverParameter?.type) +
1807+
listOfNotNull(f.extensionReceiverParameter?.type) +
18011808
f.valueParameters.map { it.type } +
18021809
listOf(pluginContext.irBuiltIns.intType, getDefaultsMethodLastArgType(f))
18031810
).map { erase(it) }
@@ -1816,17 +1823,16 @@ open class KotlinFileExtractor(
18161823

18171824
private fun getDefaultsMethodLabel(f: IrFunction): Label<out DbCallable> {
18181825
val defaultsMethodName = if (f is IrConstructor) "<init>" else getDefaultsMethodName(f)
1819-
val normalArgTypes = getDefaultsMethodArgTypes(f)
1820-
val extensionParamType = f.extensionReceiverParameter?.let { erase(it.type) }
1826+
val argTypes = getDefaultsMethodArgTypes(f)
18211827

18221828
val defaultMethodLabelStr = getFunctionLabel(
18231829
f.parent,
18241830
maybeParentId = null,
18251831
defaultsMethodName,
1826-
normalArgTypes,
1832+
argTypes,
18271833
erase(f.returnType),
1828-
extensionParamType,
1829-
listOf(),
1834+
extensionParamType = null, // if there's any, that's included already in argTypes
1835+
functionTypeParameters = listOf(),
18301836
classTypeArgsIncludingOuterClasses = null,
18311837
overridesCollectionsMethod = false,
18321838
javaSignature = null,
@@ -1886,13 +1892,14 @@ open class KotlinFileExtractor(
18861892
extensionReceiver: IrExpression?
18871893
) {
18881894
var nextIdx = 0
1889-
if (extensionReceiver != null) {
1890-
extractExpressionExpr(extensionReceiver, enclosingCallable, id, nextIdx++, enclosingStmt)
1891-
}
18921895
if (dispatchReceiver != null && !callTarget.shouldExtractAsStatic) {
18931896
extractExpressionExpr(dispatchReceiver, enclosingCallable, id, nextIdx++, enclosingStmt)
18941897
}
18951898

1899+
if (extensionReceiver != null) {
1900+
extractExpressionExpr(extensionReceiver, enclosingCallable, id, nextIdx++, enclosingStmt)
1901+
}
1902+
18961903
val valueArgsWithDummies = valueArguments.zip(callTarget.valueParameters).map {
18971904
(expr, param) -> expr ?: IrConstImpl.defaultValueForType(0, 0, param.type)
18981905
}
@@ -4046,8 +4053,7 @@ open class KotlinFileExtractor(
40464053
// Use of 'this' in a function where the dispatch receiver is passed like an ordinary parameter,
40474054
// such as a `$default` static function that substitutes in default arguments as needed.
40484055
val paramDeclarerId = overriddenAttributes.id ?: useDeclarationParent(thisParamParent, false)
4049-
val extensionParamOffset = if (thisParamParent.extensionReceiverParameter != null) 1 else 0
4050-
val replacementParamId = tw.getLabelFor<DbParam>(getValueParameterLabel(paramDeclarerId, replaceWithParamIdx + extensionParamOffset))
4056+
val replacementParamId = tw.getLabelFor<DbParam>(getValueParameterLabel(paramDeclarerId, replaceWithParamIdx))
40514057
extractVariableAccess(replacementParamId, e.type, locId, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt)
40524058
return
40534059
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The extraction of Kotlin extension methods has been improved when default parameter values are present. The dispatch and extension receiver parameters are extracted in the correct order. The `ExtensionMethod::getExtensionReceiverParameterIndex` predicate has been introduced to facilitate getting the correct extension parameter index.

java/ql/lib/semmle/code/java/Expr.qll

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1896,7 +1896,8 @@ class VarAccess extends Expr, @varaccess {
18961896
class ExtensionReceiverAccess extends VarAccess {
18971897
ExtensionReceiverAccess() {
18981898
exists(Parameter p |
1899-
this.getVariable() = p and p.getPosition() = 0 and p.getCallable() instanceof ExtensionMethod
1899+
this.getVariable() = p and
1900+
p.isExtensionParameter()
19001901
)
19011902
}
19021903

java/ql/lib/semmle/code/java/Member.qll

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -327,18 +327,8 @@ class Callable extends StmtParent, Member, @callable {
327327
this instanceof Method and
328328
result instanceof Method and
329329
this.getName() + "$default" = result.getName() and
330-
extraLeadingParams <= 1 and
331-
(
332-
if ktExtensionFunctions(this, _, _)
333-
then
334-
// Both extension receivers are expected to occur at arg0, with any
335-
// dispatch receiver inserted afterwards in the $default proxy's parameter list.
336-
// Check the extension receiver matches here, and note regular args
337-
// are bumped one position to the right.
338-
regularParamsStartIdx = extraLeadingParams + 1 and
339-
this.getParameterType(0).getErasure() = eraseRaw(result.getParameterType(0))
340-
else regularParamsStartIdx = extraLeadingParams
341-
) and
330+
extraLeadingParams <= 1 and // 0 for static methods, 1 for instance methods
331+
regularParamsStartIdx = extraLeadingParams and
342332
lastParamType instanceof TypeObject
343333
)
344334
|
@@ -824,4 +814,19 @@ class ExtensionMethod extends Method {
824814
KotlinType getExtendedKotlinType() { result = extendedKotlinType }
825815

826816
override string getAPrimaryQlClass() { result = "ExtensionMethod" }
817+
818+
/**
819+
* Gets the index of the parameter that is the extension receiver. This is typically index 0. In case of `$default`
820+
* extension methods that are defined as members, the index is 1. Index 0 is the dispatch receiver of the `$default`
821+
* method.
822+
*/
823+
int getExtensionReceiverParameterIndex() {
824+
if
825+
exists(Method src |
826+
this = src.getKotlinParameterDefaultsProxy() and
827+
src.getNumberOfParameters() = this.getNumberOfParameters() - 3 // 2 extra parameters + 1 dispatch receiver
828+
)
829+
then result = 1
830+
else result = 0
831+
}
827832
}

java/ql/lib/semmle/code/java/Variable.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class Parameter extends Element, @param, LocalScopeVariable {
9191

9292
/** Holds if this formal parameter is a parameter representing the dispatch receiver in an extension method. */
9393
predicate isExtensionParameter() {
94-
this.getPosition() = 0 and this.getCallable() instanceof ExtensionMethod
94+
this.getPosition() = this.getCallable().(ExtensionMethod).getExtensionReceiverParameterIndex()
9595
}
9696

9797
/**

java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,11 @@ private predicate correspondingKotlinParameterDefaultsArgSpec(
120120
exists(int oldArgParsed |
121121
oldArgParsed = AccessPathSyntax::AccessPath::parseInt(oldArgNumber.splitAt(",").trim())
122122
|
123-
if ktExtensionFunctions(originalCallable, _, _) and oldArgParsed = 0
124-
then defaultsArgSpec = "Argument[0]"
123+
if
124+
ktExtensionFunctions(originalCallable, _, _) and
125+
ktExtensionFunctions(defaultsCallable, _, _) and
126+
oldArgParsed = 0
127+
then defaultsArgSpec = "Argument[" + paramOffset + "]" // 1 if dispatch receiver is present, 0 otherwise.
125128
else defaultsArgSpec = "Argument[" + (oldArgParsed + paramOffset) + "]" + rest
126129
)
127130
)

java/ql/lib/semmle/code/java/security/PathSanitizer.qll

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,9 @@ private class PathNormalizeSanitizer extends MethodAccess {
298298
* what `getQualifier` actually gets in Java and Kotlin.
299299
*/
300300
private Expr getVisualQualifier(MethodAccess ma) {
301-
if getSourceMethod(ma.getMethod()) instanceof ExtensionMethod
302-
then result = ma.getArgument(0)
301+
if ma.getMethod() instanceof ExtensionMethod
302+
then
303+
result = ma.getArgument(ma.getMethod().(ExtensionMethod).getExtensionReceiverParameterIndex())
303304
else result = ma.getQualifier()
304305
}
305306

@@ -310,8 +311,11 @@ private Expr getVisualQualifier(MethodAccess ma) {
310311
*/
311312
bindingset[argPos]
312313
private Argument getVisualArgument(MethodAccess ma, int argPos) {
313-
if getSourceMethod(ma.getMethod()) instanceof ExtensionMethod
314-
then result = ma.getArgument(argPos + 1)
314+
if ma.getMethod() instanceof ExtensionMethod
315+
then
316+
result =
317+
ma.getArgument(argPos + ma.getMethod().(ExtensionMethod).getExtensionReceiverParameterIndex() +
318+
1)
315319
else result = ma.getArgument(argPos)
316320
}
317321

java/ql/test/kotlin/library-tests/jvmoverloads-annotation/PrintAst.expected

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ test.kt:
7272
# 45| 5: [BlockStmt] { ... }
7373
# 45| 0: [ReturnStmt] return ...
7474
# 45| 0: [VarAccess] a
75-
# 45| 5: [Method] testExtensionFunction$default
75+
# 45| 5: [ExtensionMethod] testExtensionFunction$default
7676
# 45| 3: [TypeAccess] int
7777
#-----| 4: (Parameters)
7878
# 45| 0: [Parameter] p0
@@ -116,7 +116,7 @@ test.kt:
116116
# 45| 2: [ReturnStmt] return ...
117117
# 45| 0: [MethodAccess] testExtensionFunction(...)
118118
# 45| -1: [TypeAccess] TestKt
119-
# 45| 0: [VarAccess] p0
119+
# 45| 0: [ExtensionReceiverAccess] this
120120
# 45| 1: [VarAccess] p1
121121
# 45| 2: [VarAccess] p2
122122
# 45| 3: [VarAccess] p3
@@ -356,9 +356,9 @@ test.kt:
356356
# 12| 0: [ReturnStmt] return ...
357357
# 12| 0: [MethodAccess] testMemberExtensionFunction$default(...)
358358
# 12| -1: [TypeAccess] Test
359-
# 0| 0: [ExtensionReceiverAccess] this
360-
# 0| 1: [ThisAccess] Test.this
359+
# 0| 0: [ThisAccess] Test.this
361360
# 0| 0: [TypeAccess] Test
361+
# 0| 1: [ExtensionReceiverAccess] this
362362
# 0| 2: [VarAccess] a
363363
# 1| 3: [NullLiteral] null
364364
# 0| 4: [VarAccess] c
@@ -383,9 +383,9 @@ test.kt:
383383
# 12| 0: [ReturnStmt] return ...
384384
# 12| 0: [MethodAccess] testMemberExtensionFunction$default(...)
385385
# 12| -1: [TypeAccess] Test
386-
# 0| 0: [ExtensionReceiverAccess] this
387-
# 0| 1: [ThisAccess] Test.this
386+
# 0| 0: [ThisAccess] Test.this
388387
# 0| 0: [TypeAccess] Test
388+
# 0| 1: [ExtensionReceiverAccess] this
389389
# 0| 2: [VarAccess] a
390390
# 0| 3: [VarAccess] b
391391
# 0| 4: [VarAccess] c
@@ -411,13 +411,13 @@ test.kt:
411411
# 12| 5: [BlockStmt] { ... }
412412
# 12| 0: [ReturnStmt] return ...
413413
# 12| 0: [VarAccess] a
414-
# 12| 13: [Method] testMemberExtensionFunction$default
414+
# 12| 13: [ExtensionMethod] testMemberExtensionFunction$default
415415
# 12| 3: [TypeAccess] int
416416
#-----| 4: (Parameters)
417417
# 12| 0: [Parameter] p0
418-
# 12| 0: [TypeAccess] Test2
419-
# 12| 1: [Parameter] p1
420418
# 12| 0: [TypeAccess] Test
419+
# 12| 1: [Parameter] p1
420+
# 12| 0: [TypeAccess] Test2
421421
# 12| 2: [Parameter] p2
422422
# 12| 0: [TypeAccess] int
423423
# 12| 3: [Parameter] p3
@@ -456,8 +456,8 @@ test.kt:
456456
# 12| 1: [FloatLiteral] 1.0
457457
# 12| 2: [ReturnStmt] return ...
458458
# 12| 0: [MethodAccess] testMemberExtensionFunction(...)
459-
# 12| -1: [VarAccess] p1
460-
# 12| 0: [VarAccess] p0
459+
# 12| -1: [VarAccess] p0
460+
# 12| 0: [ExtensionReceiverAccess] this
461461
# 12| 1: [VarAccess] p2
462462
# 12| 2: [VarAccess] p3
463463
# 12| 3: [VarAccess] p4

java/ql/test/kotlin/library-tests/jvmoverloads-annotation/test.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
| test.kt:3:1:14:1 | Test | test.kt:12:3:12:121 | testMemberExtensionFunction | testMemberExtensionFunction(Test2,int,double,boolean) |
3030
| test.kt:3:1:14:1 | Test | test.kt:12:3:12:121 | testMemberExtensionFunction | testMemberExtensionFunction(Test2,int,java.lang.String,double,boolean) |
3131
| test.kt:3:1:14:1 | Test | test.kt:12:3:12:121 | testMemberExtensionFunction | testMemberExtensionFunction(Test2,int,java.lang.String,double,float,boolean) |
32-
| test.kt:3:1:14:1 | Test | test.kt:12:3:12:121 | testMemberExtensionFunction$default | testMemberExtensionFunction$default(Test2,Test,int,java.lang.String,double,float,boolean,int,java.lang.Object) |
32+
| test.kt:3:1:14:1 | Test | test.kt:12:3:12:121 | testMemberExtensionFunction$default | testMemberExtensionFunction$default(Test,Test2,int,java.lang.String,double,float,boolean,int,java.lang.Object) |
3333
| test.kt:16:1:28:1 | Test2 | test.kt:16:34:28:1 | Test2 | Test2(int,double,boolean) |
3434
| test.kt:16:1:28:1 | Test2 | test.kt:16:34:28:1 | Test2 | Test2(int,java.lang.String,double,boolean) |
3535
| test.kt:16:1:28:1 | Test2 | test.kt:16:34:28:1 | Test2 | Test2(int,java.lang.String,double,float,boolean) |

java/ql/test/kotlin/library-tests/methods/exprs.expected

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -293,12 +293,47 @@
293293
| methods2.kt:8:5:9:5 | Unit | TypeAccess |
294294
| methods2.kt:8:27:8:32 | int | TypeAccess |
295295
| methods2.kt:8:35:8:40 | int | TypeAccess |
296-
| methods3.kt:3:1:3:42 | Unit | TypeAccess |
297-
| methods3.kt:3:5:3:7 | int | TypeAccess |
298-
| methods3.kt:3:33:3:38 | int | TypeAccess |
299-
| methods3.kt:6:5:6:46 | Unit | TypeAccess |
300-
| methods3.kt:6:9:6:11 | int | TypeAccess |
301-
| methods3.kt:6:37:6:42 | int | TypeAccess |
296+
| methods3.kt:3:1:3:49 | 0 | IntegerLiteral |
297+
| methods3.kt:3:1:3:49 | 1 | IntegerLiteral |
298+
| methods3.kt:3:1:3:49 | ... & ... | AndBitwiseExpr |
299+
| methods3.kt:3:1:3:49 | ... == ... | EQExpr |
300+
| methods3.kt:3:1:3:49 | ...=... | AssignExpr |
301+
| methods3.kt:3:1:3:49 | Methods3Kt | TypeAccess |
302+
| methods3.kt:3:1:3:49 | Object | TypeAccess |
303+
| methods3.kt:3:1:3:49 | String | TypeAccess |
304+
| methods3.kt:3:1:3:49 | Unit | TypeAccess |
305+
| methods3.kt:3:1:3:49 | Unit | TypeAccess |
306+
| methods3.kt:3:1:3:49 | fooBarTopLevelMethodExt(...) | MethodAccess |
307+
| methods3.kt:3:1:3:49 | int | TypeAccess |
308+
| methods3.kt:3:1:3:49 | int | TypeAccess |
309+
| methods3.kt:3:1:3:49 | p1 | VarAccess |
310+
| methods3.kt:3:1:3:49 | p1 | VarAccess |
311+
| methods3.kt:3:1:3:49 | p2 | VarAccess |
312+
| methods3.kt:3:1:3:49 | this | ExtensionReceiverAccess |
313+
| methods3.kt:3:5:3:10 | String | TypeAccess |
314+
| methods3.kt:3:36:3:45 | int | TypeAccess |
315+
| methods3.kt:3:45:3:45 | 1 | IntegerLiteral |
316+
| methods3.kt:6:5:6:45 | 0 | IntegerLiteral |
317+
| methods3.kt:6:5:6:45 | 1 | IntegerLiteral |
318+
| methods3.kt:6:5:6:45 | ... & ... | AndBitwiseExpr |
319+
| methods3.kt:6:5:6:45 | ... == ... | EQExpr |
320+
| methods3.kt:6:5:6:45 | ...=... | AssignExpr |
321+
| methods3.kt:6:5:6:45 | Class3 | TypeAccess |
322+
| methods3.kt:6:5:6:45 | Object | TypeAccess |
323+
| methods3.kt:6:5:6:45 | String | TypeAccess |
324+
| methods3.kt:6:5:6:45 | Unit | TypeAccess |
325+
| methods3.kt:6:5:6:45 | Unit | TypeAccess |
326+
| methods3.kt:6:5:6:45 | fooBarMethodExt(...) | MethodAccess |
327+
| methods3.kt:6:5:6:45 | int | TypeAccess |
328+
| methods3.kt:6:5:6:45 | int | TypeAccess |
329+
| methods3.kt:6:5:6:45 | p0 | VarAccess |
330+
| methods3.kt:6:5:6:45 | p2 | VarAccess |
331+
| methods3.kt:6:5:6:45 | p2 | VarAccess |
332+
| methods3.kt:6:5:6:45 | p3 | VarAccess |
333+
| methods3.kt:6:5:6:45 | this | ExtensionReceiverAccess |
334+
| methods3.kt:6:9:6:14 | String | TypeAccess |
335+
| methods3.kt:6:32:6:41 | int | TypeAccess |
336+
| methods3.kt:6:41:6:41 | 1 | IntegerLiteral |
302337
| methods4.kt:7:5:7:34 | Unit | TypeAccess |
303338
| methods4.kt:7:11:7:29 | InsideNestedTest | TypeAccess |
304339
| methods5.kt:3:1:11:1 | Unit | TypeAccess |

0 commit comments

Comments
 (0)