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

Skip to content

Commit 96f3ea4

Browse files
smowtonigfoo
authored andcommitted
Make varargs extraction more Java-like:
* Extract varargs as if they are ordinary positional arguments * Adapt the QL that distinguishes varargs from ordinary arguments to account for Kotlin's varargs which can occur in the middle of the arg list * Add a test checking dataflow through varargs which doesn't work yet due to array-get and array-set not being extracted as IndexExprs * Extract the special case arrayOf(*x) as a clone call, which is (equivalent to) the Java lowering of that operation
1 parent 7368b49 commit 96f3ea4

15 files changed

Lines changed: 230 additions & 122 deletions

File tree

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

Lines changed: 88 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -441,17 +441,20 @@ open class KotlinFileExtractor(
441441

442442
private fun extractValueParameter(vp: IrValueParameter, parent: Label<out DbCallable>, idx: Int, typeSubstitution: TypeSubstitution?, parentSourceDeclaration: Label<out DbCallable>): TypeResults {
443443
with("value parameter", vp) {
444-
return extractValueParameter(useValueParameter(vp, parent), vp.type, vp.name.asString(), tw.getLocation(vp), parent, idx, typeSubstitution, useValueParameter(vp, parentSourceDeclaration))
444+
return extractValueParameter(useValueParameter(vp, parent), vp.type, vp.name.asString(), tw.getLocation(vp), parent, idx, typeSubstitution, useValueParameter(vp, parentSourceDeclaration), vp.isVararg)
445445
}
446446
}
447447

448-
private fun extractValueParameter(id: Label<out DbParam>, t: IrType, name: String, locId: Label<DbLocation>, parent: Label<out DbCallable>, idx: Int, typeSubstitution: TypeSubstitution?, paramSourceDeclaration: Label<out DbParam>): TypeResults {
448+
private fun extractValueParameter(id: Label<out DbParam>, t: IrType, name: String, locId: Label<DbLocation>, parent: Label<out DbCallable>, idx: Int, typeSubstitution: TypeSubstitution?, paramSourceDeclaration: Label<out DbParam>, isVararg: Boolean): TypeResults {
449449
val substitutedType = typeSubstitution?.let { it(t, TypeContext.OTHER, pluginContext) } ?: t
450450
val type = useType(substitutedType)
451451
tw.writeParams(id, type.javaResult.id, idx, parent, paramSourceDeclaration)
452452
tw.writeParamsKotlinType(id, type.kotlinResult.id)
453453
tw.writeHasLocation(id, locId)
454454
tw.writeParamName(id, name)
455+
if (isVararg) {
456+
tw.writeIsVarargsParam(id)
457+
}
455458
return type
456459
}
457460

@@ -1079,9 +1082,15 @@ open class KotlinFileExtractor(
10791082
idxOffset = 0
10801083
}
10811084

1082-
valueArguments.forEachIndexed { i, arg ->
1085+
var i = 0
1086+
valueArguments.forEach { arg ->
10831087
if(arg != null) {
1084-
extractExpressionExpr(arg, enclosingCallable, id, i + idxOffset, enclosingStmt)
1088+
if (arg is IrVararg) {
1089+
arg.elements.forEachIndexed { varargNo, vararg -> extractVarargElement(vararg, enclosingCallable, id, i + idxOffset + varargNo, enclosingStmt) }
1090+
i += arg.elements.size
1091+
} else {
1092+
extractExpressionExpr(arg, enclosingCallable, id, (i++) + idxOffset, enclosingStmt)
1093+
}
10851094
}
10861095
}
10871096
}
@@ -1121,6 +1130,22 @@ open class KotlinFileExtractor(
11211130
result
11221131
}
11231132

1133+
val javaLangObject by lazy {
1134+
val result = pluginContext.referenceClass(FqName("java.lang.Object"))?.owner
1135+
result?.let { extractExternalClassLater(it) }
1136+
result
1137+
}
1138+
1139+
val objectCloneMethod by lazy {
1140+
val result = javaLangObject?.declarations?.find {
1141+
it is IrFunction && it.name.asString() == "clone"
1142+
} as IrFunction?
1143+
if (result == null) {
1144+
logger.error("Couldn't find declaration java.lang.Object.clone(...)")
1145+
}
1146+
result
1147+
}
1148+
11241149
fun extractCall(c: IrCall, callable: Label<out DbCallable>, parent: Label<out DbExprparent>, idx: Int, enclosingStmt: Label<out DbStmt>) {
11251150
with("call", c) {
11261151
fun isFunction(pkgName: String, className: String, fName: String, hasQuestionMark: Boolean? = false): Boolean {
@@ -1458,37 +1483,63 @@ open class KotlinFileExtractor(
14581483
|| isBuiltinCallKotlin(c, "shortArrayOf")
14591484
|| isBuiltinCallKotlin(c, "byteArrayOf")
14601485
|| isBuiltinCallKotlin(c, "booleanArrayOf") -> {
1461-
val id = tw.getFreshIdLabel<DbArraycreationexpr>()
1462-
val type = useType(c.type)
1463-
tw.writeExprs_arraycreationexpr(id, type.javaResult.id, parent, idx)
1464-
tw.writeExprsKotlinType(id, type.kotlinResult.id)
1465-
val locId = tw.getLocation(c)
1466-
tw.writeHasLocation(id, locId)
1467-
tw.writeCallableEnclosingExpr(id, callable)
1468-
1469-
if (isBuiltinCallKotlin(c, "arrayOf")) {
1470-
if (c.typeArgumentsCount == 1) {
1471-
extractTypeArguments(c, id, callable, enclosingStmt,-1)
1472-
} else {
1473-
logger.errorElement("Expected to find one type argument in arrayOf call", c )
1486+
1487+
val arg = if (c.valueArgumentsCount == 1) c.getValueArgument(0) else {
1488+
logger.errorElement("Expected to find only one (vararg) argument in ${c.symbol.owner.name.asString()} call", c)
1489+
null
1490+
}?.let {
1491+
if (it is IrVararg) it else {
1492+
logger.errorElement("Expected to find vararg argument in ${c.symbol.owner.name.asString()} call", c)
1493+
null
14741494
}
1475-
} else {
1476-
val elementType = c.type.getArrayElementType(pluginContext.irBuiltIns)
1477-
extractTypeAccess(elementType, callable, id, -1, c, enclosingStmt)
14781495
}
1479-
1480-
if (c.valueArgumentsCount == 1) {
1481-
val vararg = c.getValueArgument(0)
1482-
if (vararg is IrVararg) {
1496+
1497+
// If this is [someType]ArrayOf(*x), x, otherwise null
1498+
val clonedArray = arg?.let {
1499+
if (arg.elements.size == 1) {
1500+
val onlyElement = arg.elements[0]
1501+
if (onlyElement is IrSpreadElement)
1502+
onlyElement.expression
1503+
else null
1504+
} else null
1505+
}
1506+
1507+
if (clonedArray != null) {
1508+
// This is an array clone: extract is as a call to java.lang.Object.clone
1509+
objectCloneMethod?.let {
1510+
extractRawMethodAccess(it, c, callable, parent, idx, enclosingStmt, listOf(), clonedArray, null)
1511+
}
1512+
} else {
1513+
// This is array creation: extract it as a call to new ArrayType[] { ... }
1514+
val id = tw.getFreshIdLabel<DbArraycreationexpr>()
1515+
val type = useType(c.type)
1516+
tw.writeExprs_arraycreationexpr(id, type.javaResult.id, parent, idx)
1517+
tw.writeExprsKotlinType(id, type.kotlinResult.id)
1518+
val locId = tw.getLocation(c)
1519+
tw.writeHasLocation(id, locId)
1520+
tw.writeCallableEnclosingExpr(id, callable)
1521+
1522+
if (isBuiltinCallKotlin(c, "arrayOf")) {
1523+
if (c.typeArgumentsCount == 1) {
1524+
extractTypeArguments(c, id, callable, enclosingStmt,-1)
1525+
} else {
1526+
logger.errorElement("Expected to find one type argument in arrayOf call", c )
1527+
}
1528+
} else {
1529+
val elementType = c.type.getArrayElementType(pluginContext.irBuiltIns)
1530+
extractTypeAccess(elementType, callable, id, -1, c, enclosingStmt)
1531+
}
1532+
1533+
arg?.let {
14831534
val initId = tw.getFreshIdLabel<DbArrayinit>()
14841535
tw.writeExprs_arrayinit(initId, type.javaResult.id, id, -2)
14851536
tw.writeExprsKotlinType(initId, type.kotlinResult.id)
14861537
tw.writeHasLocation(initId, locId)
14871538
tw.writeCallableEnclosingExpr(initId, callable)
14881539
tw.writeStatementEnclosingExpr(initId, enclosingStmt)
1489-
vararg.elements.forEachIndexed { i, arg -> extractVarargElement(arg, callable, initId, i, enclosingStmt) }
1490-
1491-
val dim = vararg.elements.size
1540+
it.elements.forEachIndexed { i, arg -> extractVarargElement(arg, callable, initId, i, enclosingStmt) }
1541+
1542+
val dim = it.elements.size
14921543
val dimId = tw.getFreshIdLabel<DbIntegerliteral>()
14931544
val dimType = useType(pluginContext.irBuiltIns.intType)
14941545
tw.writeExprs_integerliteral(dimId, dimType.javaResult.id, id, 0)
@@ -1497,11 +1548,7 @@ open class KotlinFileExtractor(
14971548
tw.writeCallableEnclosingExpr(dimId, callable)
14981549
tw.writeStatementEnclosingExpr(dimId, enclosingStmt)
14991550
tw.writeNamestrings(dim.toString(), dim.toString(), dimId)
1500-
} else {
1501-
logger.errorElement("Expected to find vararg argument in ${c.symbol.owner.name.asString()} call", c)
15021551
}
1503-
} else {
1504-
logger.errorElement("Expected to find only one (vararg) argument in ${c.symbol.owner.name.asString()} call", c)
15051552
}
15061553
}
15071554
isBuiltinCall(c, "<unsafe-coerce>", "kotlin.jvm.internal") -> {
@@ -2123,16 +2170,7 @@ open class KotlinFileExtractor(
21232170
extractTypeOperatorCall(e, callable, exprParent.parent, exprParent.idx, exprParent.enclosingStmt)
21242171
}
21252172
is IrVararg -> {
2126-
val exprParent = parent.expr(e, callable)
2127-
val id = tw.getFreshIdLabel<DbVarargexpr>()
2128-
val locId = tw.getLocation(e)
2129-
val type = useType(e.type)
2130-
tw.writeExprs_varargexpr(id, type.javaResult.id, exprParent.parent, exprParent.idx)
2131-
tw.writeExprsKotlinType(id, type.kotlinResult.id)
2132-
tw.writeHasLocation(id, locId)
2133-
tw.writeCallableEnclosingExpr(id, callable)
2134-
tw.writeStatementEnclosingExpr(id, exprParent.enclosingStmt)
2135-
e.elements.forEachIndexed { i, arg -> extractVarargElement(arg, callable, id, i, exprParent.enclosingStmt) }
2173+
logger.errorElement("Unexpected IrVararg", e)
21362174
}
21372175
is IrGetObjectValue -> {
21382176
// For `object MyObject { ... }`, the .class has an
@@ -2349,7 +2387,7 @@ open class KotlinFileExtractor(
23492387
stmtIdx: Int
23502388
) {
23512389
val paramId = tw.getFreshIdLabel<DbParam>()
2352-
val paramType = extractValueParameter(paramId, type, paramName, locId, ids.constructor, paramIdx, null, paramId)
2390+
val paramType = extractValueParameter(paramId, type, paramName, locId, ids.constructor, paramIdx, null, paramId, false)
23532391

23542392
val assignmentStmtId = tw.getFreshIdLabel<DbExprstmt>()
23552393
tw.writeStmts_exprstmt(assignmentStmtId, ids.constructorBlock, stmtIdx, ids.constructor)
@@ -2548,7 +2586,7 @@ open class KotlinFileExtractor(
25482586

25492587
val parameters = parameterTypes.mapIndexed { idx, p ->
25502588
val paramId = tw.getFreshIdLabel<DbParam>()
2551-
val paramType = extractValueParameter(paramId, p, "a$idx", locId, methodId, idx, null, paramId)
2589+
val paramType = extractValueParameter(paramId, p, "a$idx", locId, methodId, idx, null, paramId, false)
25522590

25532591
Pair(paramId, paramType)
25542592
}
@@ -2695,18 +2733,17 @@ open class KotlinFileExtractor(
26952733

26962734
fun extractVarargElement(e: IrVarargElement, callable: Label<out DbCallable>, parent: Label<out DbExprparent>, idx: Int, enclosingStmt: Label<out DbStmt>) {
26972735
with("vararg element", e) {
2698-
when(e) {
2699-
is IrExpression -> {
2700-
extractExpressionExpr(e, callable, parent, idx, enclosingStmt)
2701-
}
2702-
is IrSpreadElement -> {
2703-
// TODO:
2704-
logger.errorElement("Unhandled IrSpreadElement", e)
2705-
}
2736+
val argExpr = when(e) {
2737+
is IrExpression -> e
2738+
is IrSpreadElement -> e.expression
27062739
else -> {
27072740
logger.errorElement("Unrecognised IrVarargElement: " + e.javaClass, e)
2741+
null
27082742
}
27092743
}
2744+
argExpr?.let {
2745+
extractExpressionExpr(it, callable, parent, idx, enclosingStmt)
2746+
}
27102747
}
27112748
}
27122749

java/ql/lib/config/semmlecode.dbscheme

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -745,11 +745,10 @@ case @expr.kind of
745745
| 81 = @notinstanceofexpr
746746
| 82 = @stmtexpr
747747
| 83 = @stringtemplateexpr
748-
| 84 = @varargexpr
749-
| 85 = @notnullexpr
750-
| 86 = @unsafecoerceexpr
751-
| 87 = @valueeqexpr
752-
| 88 = @valueneexpr
748+
| 84 = @notnullexpr
749+
| 85 = @unsafecoerceexpr
750+
| 86 = @valueeqexpr
751+
| 87 = @valueneexpr
753752
;
754753

755754
/** Holds if this `when` expression was written as an `if` expression. */

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -467,8 +467,6 @@ private module ControlFlowGraphImpl {
467467
or
468468
this instanceof StringTemplateExpr
469469
or
470-
this instanceof VarArgExpr
471-
or
472470
this instanceof ClassExpr
473471
or
474472
this instanceof RValue
@@ -562,10 +560,6 @@ private module ControlFlowGraphImpl {
562560
result = e.getComponent(index)
563561
)
564562
or
565-
exists(VarArgExpr e | e = this |
566-
result = e.getComponent(index)
567-
)
568-
or
569563
index = 0 and result = this.(ClassExpr).getExpr()
570564
or
571565
index = 0 and result = this.(ReturnStmt).getResult()

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

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2253,7 +2253,6 @@ class Argument extends Expr {
22532253
predicate isExplicitVarargsArray() {
22542254
exists(Array typ, Parameter p, Type ptyp |
22552255
typ = this.getType() and
2256-
pos = call.getNumArgument() - 1 and
22572256
call.getCallee().getParameter(pos) = p and
22582257
p.isVarargs() and
22592258
ptyp = p.getType() and
@@ -2275,11 +2274,13 @@ class Argument extends Expr {
22752274
*/
22762275
predicate isNthVararg(int arrayindex) {
22772276
not this.isExplicitVarargsArray() and
2278-
exists(Callable tgt |
2277+
exists(Callable tgt, Parameter varargsParam, int varargsParamPos |
22792278
call.getCallee() = tgt and
2280-
tgt.isVarargs() and
2281-
arrayindex = pos - tgt.getNumberOfParameters() + 1 and
2282-
arrayindex >= 0
2279+
tgt.getParameter(varargsParamPos) = varargsParam and
2280+
varargsParam.isVarargs() and
2281+
arrayindex = pos - varargsParamPos and
2282+
arrayindex >= 0 and
2283+
arrayindex <= (call.getNumArgument() - tgt.getNumberOfParameters())
22832284
)
22842285
}
22852286
}
@@ -2375,29 +2376,6 @@ class StringTemplateExpr extends Expr, @stringtemplateexpr {
23752376
override string getAPrimaryQlClass() { result = "StringTemplateExpr" }
23762377
}
23772378

2378-
/**
2379-
* A Kotlin(TODO: Should Java make these too?) vararg expression.
2380-
* This is the argument to a function that corresponds to a `vararg`
2381-
* parameter.
2382-
*/
2383-
class VarArgExpr extends Expr, @varargexpr {
2384-
/**
2385-
* Gets the `i`th component of this vararg. TODO: Is this always Expr?
2386-
*
2387-
* For example, in the string template `"foo${bar}baz"`, the 0th
2388-
* component is the string literal `"foo"`, the 1st component is
2389-
* the variable access `bar`, and the 2nd component is the string
2390-
* literal `"bar"`.
2391-
*/
2392-
Expr getComponent(int i) { result.isNthChildOf(this, i) }
2393-
2394-
override string toString() { result = "..." }
2395-
2396-
override string getHalsteadID() { result = "VarArgExpr" }
2397-
2398-
override string getAPrimaryQlClass() { result = "VarArgExpr" }
2399-
}
2400-
24012379
/** A Kotlin not-null expression. For example, `expr!!`. */
24022380
class NotNullExpr extends UnaryExpr, @notnullexpr {
24032381
override string toString() { result = "...!!" }

java/ql/test/kotlin/library-tests/arrays/arrayCreations.expected

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,13 @@ arrayCreationInit
103103
| arrayCreations.kt:19:43:19:61 | new Integer[] | arrayCreations.kt:19:43:19:61 | {...} | arrayCreations.kt:19:54:19:54 | 2 | 1 |
104104
| arrayCreations.kt:19:43:19:61 | new Integer[] | arrayCreations.kt:19:43:19:61 | {...} | arrayCreations.kt:19:57:19:57 | 3 | 2 |
105105
| arrayCreations.kt:19:43:19:61 | new Integer[] | arrayCreations.kt:19:43:19:61 | {...} | arrayCreations.kt:19:60:19:60 | 4 | 3 |
106+
cloneCalls
107+
| arrayCreations.kt:29:18:29:29 | clone(...) | file://:0:0:0:0 | Integer[] |
108+
| arrayCreations.kt:30:18:30:35 | clone(...) | file://:0:0:0:0 | double[] |
109+
| arrayCreations.kt:31:18:31:34 | clone(...) | file://:0:0:0:0 | float[] |
110+
| arrayCreations.kt:32:18:32:33 | clone(...) | file://:0:0:0:0 | long[] |
111+
| arrayCreations.kt:33:18:33:32 | clone(...) | file://:0:0:0:0 | int[] |
112+
| arrayCreations.kt:34:18:34:33 | clone(...) | file://:0:0:0:0 | char[] |
113+
| arrayCreations.kt:35:18:35:34 | clone(...) | file://:0:0:0:0 | short[] |
114+
| arrayCreations.kt:36:18:36:33 | clone(...) | file://:0:0:0:0 | byte[] |
115+
| arrayCreations.kt:37:18:37:36 | clone(...) | file://:0:0:0:0 | boolean[] |

java/ql/test/kotlin/library-tests/arrays/arrayCreations.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,15 @@ class TestArrayCreation {
2525
val a13 = IntArray(5) { 1 }
2626
var a14 = IntArray(5) { it * 1 }
2727
val a15 = Array(4) { IntArray(2) }
28+
29+
val clone1 = arrayOf(*a1)
30+
val clone2 = doubleArrayOf(*a2)
31+
val clone3 = floatArrayOf(*a3)
32+
val clone4 = longArrayOf(*a4)
33+
val clone5 = intArrayOf(*a5)
34+
val clone6 = charArrayOf(*a6)
35+
val clone7 = shortArrayOf(*a7)
36+
val clone8 = byteArrayOf(*a8)
37+
val clone9 = booleanArrayOf(*a9)
2838
}
2939
}

java/ql/test/kotlin/library-tests/arrays/arrayCreations.ql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ query predicate arrayCreationInit(ArrayCreationExpr ace, ArrayInit init, Expr e,
1212
ace.getInit() = init and
1313
init.getInit(idx) = e
1414
}
15+
16+
query predicate cloneCalls(MethodAccess ma, Type resultType) {
17+
ma.getMethod().getName() = "clone" and resultType = ma.getType()
18+
}

java/ql/test/kotlin/library-tests/arrays/test.expected

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ cloneMethods
44
| file://:0:0:0:0 | clone | clone() | file://:0:0:0:0 | int[] | file://:0:0:0:0 | int[] | file://:0:0:0:0 | Kotlin nullable FakeKotlinClass |
55
| file://:0:0:0:0 | clone | clone() | file://:0:0:0:0 | int[][] | file://:0:0:0:0 | int[][] | file://:0:0:0:0 | Kotlin nullable FakeKotlinClass |
66
sourceSignatures
7-
| arrayCreations.kt:3:1:29:1 | <obinit> | <obinit>() |
8-
| arrayCreations.kt:3:1:29:1 | TestArrayCreation | TestArrayCreation() |
9-
| arrayCreations.kt:4:3:28:3 | test1 | test1() |
7+
| arrayCreations.kt:3:1:39:1 | <obinit> | <obinit>() |
8+
| arrayCreations.kt:3:1:39:1 | TestArrayCreation | TestArrayCreation() |
9+
| arrayCreations.kt:4:3:38:3 | test1 | test1() |
1010
| arrayCreations.kt:22:29:22:33 | | |
1111
| arrayCreations.kt:22:29:22:33 | invoke | invoke(int) |
1212
| arrayCreations.kt:23:24:23:28 | | |

0 commit comments

Comments
 (0)