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

Skip to content

Commit 5fe65ed

Browse files
smowtonigfoo
authored andcommitted
Extract no-when-branch-found calls
These are extracted as "throw new kotlin.NoWhenBranchFoundException();", which is the Java lowering of the intrinsic. In the process, amend the control-flow graph to let when branches propagate `throw`s outwards, and similarly statement expressions.
1 parent d09dff4 commit 5fe65ed

11 files changed

Lines changed: 197 additions & 37 deletions

File tree

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

Lines changed: 87 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,13 +1038,7 @@ open class KotlinFileExtractor(
10381038
val methodId = ids.function
10391039
tw.writeCallableBinding(id, methodId)
10401040

1041-
val idNewexpr = tw.getFreshIdLabel<DbNewexpr>()
1042-
tw.writeExprs_newexpr(idNewexpr, ids.type.javaResult.id, id, -1)
1043-
tw.writeExprsKotlinType(idNewexpr, ids.type.kotlinResult.id)
1044-
tw.writeHasLocation(idNewexpr, locId)
1045-
tw.writeCallableEnclosingExpr(idNewexpr, enclosingCallable)
1046-
tw.writeStatementEnclosingExpr(idNewexpr, enclosingStmt)
1047-
tw.writeCallableBinding(idNewexpr, ids.constructor)
1041+
val idNewexpr = extractNewExpr(ids.constructor, ids.type, locId, id, -1, enclosingCallable, enclosingStmt)
10481042

10491043
@Suppress("UNCHECKED_CAST")
10501044
tw.writeIsAnonymClass(ids.type.javaResult.id as Label<DbClass>, idNewexpr)
@@ -1150,6 +1144,23 @@ open class KotlinFileExtractor(
11501144
result
11511145
}
11521146

1147+
val kotlinNoWhenBranchMatchedExn by lazy {
1148+
val result = pluginContext.referenceClass(FqName("kotlin.NoWhenBranchMatchedException"))?.owner
1149+
result?.let { extractExternalClassLater(it) }
1150+
result
1151+
}
1152+
1153+
val kotlinNoWhenBranchMatchedConstructor by lazy {
1154+
val result = kotlinNoWhenBranchMatchedExn?.declarations?.find {
1155+
it is IrConstructor &&
1156+
it.valueParameters.isEmpty()
1157+
} as IrConstructor?
1158+
if (result == null) {
1159+
logger.error("Couldn't find no-arg constructor for kotlin.NoWhenBranchMatchedException")
1160+
}
1161+
result
1162+
}
1163+
11531164
fun isFunction(target: IrFunction, pkgName: String, classNameLogged: String, classNamePredicate: (String) -> Boolean, fName: String, hasQuestionMark: Boolean? = false): Boolean {
11541165
val verbose = false
11551166
fun verboseln(s: String) { if(verbose) println(s) }
@@ -1220,10 +1231,27 @@ open class KotlinFileExtractor(
12201231
else -> false
12211232
}
12221233

1223-
fun extractCall(c: IrCall, callable: Label<out DbCallable>, parent: Label<out DbExprparent>, idx: Int, enclosingStmt: Label<out DbStmt>) {
1234+
fun extractCall(c: IrCall, callable: Label<out DbCallable>, stmtExprParent: StmtExprParent) {
12241235
with("call", c) {
12251236
val target = c.symbol.owner
12261237

1238+
// The vast majority of types of call want an expr context, so make one available lazily:
1239+
val exprParent by lazy {
1240+
stmtExprParent.expr(c, callable)
1241+
}
1242+
1243+
val parent by lazy {
1244+
exprParent.parent
1245+
}
1246+
1247+
val idx by lazy {
1248+
exprParent.idx
1249+
}
1250+
1251+
val enclosingStmt by lazy {
1252+
exprParent.enclosingStmt
1253+
}
1254+
12271255
fun extractMethodAccess(syntacticCallTarget: IrFunction, extractMethodTypeArguments: Boolean = true, extractClassTypeArguments: Boolean = false) {
12281256
val typeArgs =
12291257
if (extractMethodTypeArguments)
@@ -1451,8 +1479,16 @@ open class KotlinFileExtractor(
14511479
logger.errorElement("Unhandled builtin", c)
14521480
}
14531481
isBuiltinCallInternal(c, "noWhenBranchMatchedException") -> {
1454-
// TODO
1455-
logger.errorElement("Unhandled builtin", c)
1482+
kotlinNoWhenBranchMatchedConstructor?.let {
1483+
val locId = tw.getLocation(c)
1484+
val thrownType = useSimpleTypeClass(it.parentAsClass, listOf(), false)
1485+
val stmtParent = stmtExprParent.stmt(c, callable)
1486+
val throwId = tw.getFreshIdLabel<DbThrowstmt>()
1487+
tw.writeStmts_throwstmt(throwId, stmtParent.parent, stmtParent.idx, callable)
1488+
tw.writeHasLocation(throwId, locId)
1489+
val newExprId = extractNewExpr(it, null, thrownType, locId, throwId, 0, callable, throwId)
1490+
extractTypeAccess(thrownType, callable, newExprId, -3, c, throwId)
1491+
}
14561492
}
14571493
isBuiltinCallInternal(c, "illegalArgumentException") -> {
14581494
// TODO
@@ -1676,38 +1712,61 @@ open class KotlinFileExtractor(
16761712
extractTypeArguments((0 until c.typeArgumentsCount).map { c.getTypeArgument(it)!! }, c, parentExpr, enclosingCallable, enclosingStmt, startIndex, reverse)
16771713
}
16781714

1715+
private fun extractNewExpr(
1716+
methodId: Label<out DbConstructor>,
1717+
constructedType: TypeResults,
1718+
locId: Label<out DbLocation>,
1719+
parent: Label<out DbExprparent>,
1720+
idx: Int,
1721+
callable: Label<out DbCallable>,
1722+
enclosingStmt: Label<out DbStmt>
1723+
): Label<DbNewexpr> {
1724+
val id = tw.getFreshIdLabel<DbNewexpr>()
1725+
tw.writeExprs_newexpr(id, constructedType.javaResult.id, parent, idx)
1726+
tw.writeExprsKotlinType(id, constructedType.kotlinResult.id)
1727+
tw.writeHasLocation(id, locId)
1728+
tw.writeCallableEnclosingExpr(id, callable)
1729+
tw.writeStatementEnclosingExpr(id, enclosingStmt)
1730+
tw.writeCallableBinding(id, methodId)
1731+
return id
1732+
}
1733+
1734+
private fun extractNewExpr(
1735+
calledConstructor: IrFunction,
1736+
constructorTypeArgs: List<IrTypeArgument>?,
1737+
constructedType: TypeResults,
1738+
locId: Label<out DbLocation>,
1739+
parent: Label<out DbExprparent>,
1740+
idx: Int,
1741+
callable: Label<out DbCallable>,
1742+
enclosingStmt: Label<out DbStmt>
1743+
): Label<DbNewexpr> = extractNewExpr(useFunction<DbConstructor>(calledConstructor, constructorTypeArgs), constructedType, locId, parent, idx, callable, enclosingStmt)
1744+
16791745
private fun extractConstructorCall(
16801746
e: IrFunctionAccessExpression,
16811747
parent: Label<out DbExprparent>,
16821748
idx: Int,
16831749
callable: Label<out DbCallable>,
16841750
enclosingStmt: Label<out DbStmt>
16851751
) {
1686-
val id = tw.getFreshIdLabel<DbNewexpr>()
1687-
val type: TypeResults
16881752
val isAnonymous = e.type.isAnonymous
1689-
if (isAnonymous) {
1753+
val type: TypeResults = if (isAnonymous) {
16901754
if (e.typeArgumentsCount > 0) {
16911755
logger.warn("Unexpected type arguments for anonymous class constructor call")
16921756
}
1693-
16941757
val c = (e.type as IrSimpleType).classifier.owner as IrClass
1758+
useAnonymousClass(c)
1759+
} else {
1760+
useType(e.type)
1761+
}
1762+
val locId = tw.getLocation(e)
1763+
val id = extractNewExpr(e.symbol.owner, (e.type as? IrSimpleType)?.arguments, type, locId, parent, idx, callable, enclosingStmt)
16951764

1696-
type = useAnonymousClass(c)
1697-
1765+
if (isAnonymous) {
16981766
@Suppress("UNCHECKED_CAST")
16991767
tw.writeIsAnonymClass(type.javaResult.id as Label<DbClass>, id)
1700-
} else {
1701-
type = useType(e.type)
17021768
}
1703-
val locId = tw.getLocation(e)
1704-
val methodId = useFunction<DbConstructor>(e.symbol.owner, (e.type as? IrSimpleType)?.arguments)
1705-
tw.writeExprs_newexpr(id, type.javaResult.id, parent, idx)
1706-
tw.writeExprsKotlinType(id, type.kotlinResult.id)
1707-
tw.writeHasLocation(id, locId)
1708-
tw.writeCallableEnclosingExpr(id, callable)
1709-
tw.writeStatementEnclosingExpr(id, enclosingStmt)
1710-
tw.writeCallableBinding(id, methodId)
1769+
17111770
for (i in 0 until e.valueArgumentsCount) {
17121771
val arg = e.getValueArgument(i)
17131772
if (arg != null) {
@@ -2055,8 +2114,7 @@ open class KotlinFileExtractor(
20552114
extractConstructorCall(e, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt)
20562115
}
20572116
is IrCall -> {
2058-
val exprParent = parent.expr(e, callable)
2059-
extractCall(e, callable, exprParent.parent, exprParent.idx, exprParent.enclosingStmt)
2117+
extractCall(e, callable, parent)
20602118
}
20612119
is IrStringConcatenation -> {
20622120
val exprParent = parent.expr(e, callable)
@@ -3255,13 +3313,7 @@ open class KotlinFileExtractor(
32553313
tw.writeStatementEnclosingExpr(id, enclosingStmt)
32563314
extractTypeAccess(e.typeOperand, callable, id, 0, e, enclosingStmt)
32573315

3258-
val idNewexpr = tw.getFreshIdLabel<DbNewexpr>()
3259-
tw.writeExprs_newexpr(idNewexpr, ids.type.javaResult.id, id, 1)
3260-
tw.writeExprsKotlinType(idNewexpr, ids.type.kotlinResult.id)
3261-
tw.writeHasLocation(idNewexpr, locId)
3262-
tw.writeCallableEnclosingExpr(idNewexpr, callable)
3263-
tw.writeStatementEnclosingExpr(idNewexpr, enclosingStmt)
3264-
tw.writeCallableBinding(idNewexpr, ids.constructor)
3316+
val idNewexpr = extractNewExpr(ids.constructor, ids.type, locId, id, 1, callable, enclosingStmt)
32653317

32663318
@Suppress("UNCHECKED_CAST")
32673319
tw.writeIsAnonymClass(ids.type.javaResult.id as Label<DbClass>, idNewexpr)

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,7 @@ private module ControlFlowGraphImpl {
914914
last(n.(ExprStmt).getExpr(), last, completion) and completion = NormalCompletion()
915915
or
916916
// the last node in a `StmtExpr` is the last node in the statement
917-
last(n.(StmtExpr).getStmt(), last, completion) and completion = NormalCompletion()
917+
last(n.(StmtExpr).getStmt(), last, completion)
918918
or
919919
// the last statement of a labeled statement is the last statement of its body...
920920
exists(LabeledStmt lbl, Completion bodyCompletion |
@@ -950,7 +950,7 @@ private module ControlFlowGraphImpl {
950950
not exists(whenexpr.getBranch(i + 1))
951951
)
952952
or
953-
// Any branch getting an abnormal completion is propogated
953+
// Any branch getting an abnormal completion is propagated
954954
last(whenexpr.getBranch(_), last, completion) and
955955
not completion instanceof YieldCompletion and
956956
not completion instanceof NormalOrBooleanCompletion
@@ -969,6 +969,11 @@ private module ControlFlowGraphImpl {
969969
not completion = BooleanCompletion(true, _) and
970970
not completion = NormalCompletion()
971971
or
972+
// Similarly any non-normal completion of the RHS
973+
// should propagate outwards:
974+
last(whenbranch.getRhs(), last, completion) and
975+
not completion instanceof NormalOrBooleanCompletion
976+
or
972977
// Otherwise we wrap the completion up in a YieldCompletion
973978
// so that the `when` expression can tell that we have finished,
974979
// and it shouldn't go on to the next branch.

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2988,3 +2988,48 @@ samConversion.kt:
29882988
# 33| 20: [Parameter] i20
29892989
# 33| 21: [Parameter] i21
29902990
# 33| 22: [Parameter] i22
2991+
whenExpr.kt:
2992+
# 0| [CompilationUnit] whenExpr
2993+
# 0| 1: [Class] WhenExprKt
2994+
# 1| 1: [Method] testWhen
2995+
#-----| 4: (Parameters)
2996+
# 1| 0: [Parameter] i
2997+
# 1| 5: [BlockStmt] { ... }
2998+
# 2| 0: [ReturnStmt] return ...
2999+
# 2| 0: [StmtExpr] <Stmt>
3000+
# 2| 0: [BlockStmt] { ... }
3001+
# 2| 0: [LocalVariableDeclStmt] var ...;
3002+
# 2| 1: [LocalVariableDeclExpr] tmp0_subject
3003+
# 2| 0: [VarAccess] i
3004+
# 2| 1: [ExprStmt] <Expr>;
3005+
# 2| 0: [WhenExpr] when ...
3006+
#-----| 0: (branch)
3007+
# 3| 0: [ValueEQExpr] ... (value equals) ...
3008+
# 3| 0: [VarAccess] tmp0_subject
3009+
# 3| 1: [IntegerLiteral] 0
3010+
# 3| 1: [ExprStmt] <Expr>;
3011+
# 3| 0: [IntegerLiteral] 1
3012+
#-----| 1: (branch)
3013+
# 4| 0: [ValueEQExpr] ... (value equals) ...
3014+
# 4| 0: [VarAccess] tmp0_subject
3015+
# 4| 1: [IntegerLiteral] 1
3016+
# 4| 1: [ExprStmt] <Expr>;
3017+
# 4| 0: [IntegerLiteral] 2
3018+
#-----| 2: (branch)
3019+
# 5| 0: [ValueEQExpr] ... (value equals) ...
3020+
# 5| 0: [VarAccess] tmp0_subject
3021+
# 5| 1: [IntegerLiteral] 2
3022+
# 5| 1: [ReturnStmt] return ...
3023+
# 5| 0: [IntegerLiteral] 3
3024+
#-----| 3: (branch)
3025+
# 6| 0: [ValueEQExpr] ... (value equals) ...
3026+
# 6| 0: [VarAccess] tmp0_subject
3027+
# 6| 1: [IntegerLiteral] 3
3028+
# 6| 1: [ThrowStmt] throw ...
3029+
# 6| 0: [ClassInstanceExpr] new Exception(...)
3030+
# 6| -3: [TypeAccess] Exception
3031+
# 6| 0: [StringLiteral] No threes please
3032+
#-----| 4: (branch)
3033+
# 7| 0: [BooleanLiteral] true
3034+
# 7| 1: [ExprStmt] <Expr>;
3035+
# 7| 0: [IntegerLiteral] 999

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,7 @@
113113
| samConversion.kt:10:14:10:23 | ... (value equals) ... | samConversion.kt:10:14:10:18 | ... % ... | samConversion.kt:10:23:10:23 | 0 |
114114
| samConversion.kt:12:14:12:18 | ... % ... | samConversion.kt:12:14:12:14 | j | samConversion.kt:12:18:12:18 | 2 |
115115
| samConversion.kt:12:14:12:23 | ... (value equals) ... | samConversion.kt:12:14:12:18 | ... % ... | samConversion.kt:12:23:12:23 | 1 |
116+
| whenExpr.kt:3:5:3:5 | ... (value equals) ... | whenExpr.kt:3:5:3:5 | tmp0_subject | whenExpr.kt:3:5:3:5 | 0 |
117+
| whenExpr.kt:4:5:4:5 | ... (value equals) ... | whenExpr.kt:4:5:4:5 | tmp0_subject | whenExpr.kt:4:5:4:5 | 1 |
118+
| whenExpr.kt:5:5:5:5 | ... (value equals) ... | whenExpr.kt:5:5:5:5 | tmp0_subject | whenExpr.kt:5:5:5:5 | 2 |
119+
| whenExpr.kt:6:5:6:5 | ... (value equals) ... | whenExpr.kt:6:5:6:5 | tmp0_subject | whenExpr.kt:6:5:6:5 | 3 |

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1758,3 +1758,27 @@
17581758
| samConversion.kt:43:31:45:43 | invoke(...) | samConversion.kt:43:31:45:43 | invoke | MethodAccess |
17591759
| samConversion.kt:43:31:45:43 | this | samConversion.kt:43:31:45:43 | invoke | ThisAccess |
17601760
| samConversion.kt:45:39:45:42 | true | samConversion.kt:43:31:45:43 | invoke | BooleanLiteral |
1761+
| whenExpr.kt:2:10:8:3 | <Stmt> | whenExpr.kt:1:1:9:1 | testWhen | StmtExpr |
1762+
| whenExpr.kt:2:10:8:3 | when ... | whenExpr.kt:1:1:9:1 | testWhen | WhenExpr |
1763+
| whenExpr.kt:2:15:2:15 | i | whenExpr.kt:1:1:9:1 | testWhen | VarAccess |
1764+
| whenExpr.kt:2:15:2:15 | tmp0_subject | whenExpr.kt:1:1:9:1 | testWhen | LocalVariableDeclExpr |
1765+
| whenExpr.kt:3:5:3:5 | 0 | whenExpr.kt:1:1:9:1 | testWhen | IntegerLiteral |
1766+
| whenExpr.kt:3:5:3:5 | ... (value equals) ... | whenExpr.kt:1:1:9:1 | testWhen | ValueEQExpr |
1767+
| whenExpr.kt:3:5:3:5 | tmp0_subject | whenExpr.kt:1:1:9:1 | testWhen | VarAccess |
1768+
| whenExpr.kt:3:10:3:10 | 1 | whenExpr.kt:1:1:9:1 | testWhen | IntegerLiteral |
1769+
| whenExpr.kt:4:5:4:5 | 1 | whenExpr.kt:1:1:9:1 | testWhen | IntegerLiteral |
1770+
| whenExpr.kt:4:5:4:5 | ... (value equals) ... | whenExpr.kt:1:1:9:1 | testWhen | ValueEQExpr |
1771+
| whenExpr.kt:4:5:4:5 | tmp0_subject | whenExpr.kt:1:1:9:1 | testWhen | VarAccess |
1772+
| whenExpr.kt:4:10:4:10 | 2 | whenExpr.kt:1:1:9:1 | testWhen | IntegerLiteral |
1773+
| whenExpr.kt:5:5:5:5 | 2 | whenExpr.kt:1:1:9:1 | testWhen | IntegerLiteral |
1774+
| whenExpr.kt:5:5:5:5 | ... (value equals) ... | whenExpr.kt:1:1:9:1 | testWhen | ValueEQExpr |
1775+
| whenExpr.kt:5:5:5:5 | tmp0_subject | whenExpr.kt:1:1:9:1 | testWhen | VarAccess |
1776+
| whenExpr.kt:5:17:5:17 | 3 | whenExpr.kt:1:1:9:1 | testWhen | IntegerLiteral |
1777+
| whenExpr.kt:6:5:6:5 | 3 | whenExpr.kt:1:1:9:1 | testWhen | IntegerLiteral |
1778+
| whenExpr.kt:6:5:6:5 | ... (value equals) ... | whenExpr.kt:1:1:9:1 | testWhen | ValueEQExpr |
1779+
| whenExpr.kt:6:5:6:5 | tmp0_subject | whenExpr.kt:1:1:9:1 | testWhen | VarAccess |
1780+
| whenExpr.kt:6:16:6:44 | Exception | whenExpr.kt:1:1:9:1 | testWhen | TypeAccess |
1781+
| whenExpr.kt:6:16:6:44 | new Exception(...) | whenExpr.kt:1:1:9:1 | testWhen | ClassInstanceExpr |
1782+
| whenExpr.kt:6:27:6:42 | No threes please | whenExpr.kt:1:1:9:1 | testWhen | StringLiteral |
1783+
| whenExpr.kt:7:13:7:15 | 999 | whenExpr.kt:1:1:9:1 | testWhen | IntegerLiteral |
1784+
| whenExpr.kt:7:13:7:15 | true | whenExpr.kt:1:1:9:1 | testWhen | BooleanLiteral |
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
fun testWhen(i: Int): Int {
2+
return when(i) {
3+
0 -> 1
4+
1 -> 2
5+
2 -> return 3
6+
3 -> throw Exception("No threes please")
7+
else -> 999
8+
}
9+
}
10+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| test.kt:0:0:0:0 | throw ... | test.kt:0:0:0:0 | new NoWhenBranchMatchedException(...) |
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
enum class A {
2+
A1, A2
3+
}
4+
5+
fun test(a: A): Int =
6+
when(a) {
7+
A.A1 -> 1
8+
A.A2 -> 2
9+
}
10+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import java
2+
3+
from ThrowStmt ts
4+
select ts, ts.getExpr()

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,6 @@
6363
| stmts.kt:36:12:36:23 | e | LocalVariableDeclExpr |
6464
| stmts.kt:37:16:37:16 | 1 | IntegerLiteral |
6565
| stmts.kt:40:16:40:16 | 2 | IntegerLiteral |
66+
| stmts.kt:45:11:45:26 | Exception | TypeAccess |
67+
| stmts.kt:45:11:45:26 | new Exception(...) | ClassInstanceExpr |
68+
| stmts.kt:45:22:45:24 | Bar | StringLiteral |

0 commit comments

Comments
 (0)