Skip to content

Commit 3e476f9

Browse files
committed
Kotlin: Exclude captured variables from constant loop condition check
1 parent 0bc5741 commit 3e476f9

File tree

8 files changed

+83
-3
lines changed

8 files changed

+83
-3
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ class LocalVariableDecl extends @localvar, LocalScopeVariable {
6161
override Expr getInitializer() { result = this.getDeclExpr().getInit() }
6262

6363
override string getAPrimaryQlClass() { result = "LocalVariableDecl" }
64+
65+
/** Holds if this non-final variable is captured by a nested callable. */
66+
predicate isCaptured() {
67+
not this.(Modifiable).isFinal() and exists(this.getACapturingCallable())
68+
}
69+
70+
/** Gets a callable that captures this non-final variable, if any. */
71+
Callable getACapturingCallable() {
72+
not this.(Modifiable).isFinal() and
73+
result = this.getAnAccess().getEnclosingCallable() and
74+
result != this.getCallable()
75+
}
6476
}
6577

6678
/** A formal parameter of a callable. */

java/ql/src/Likely Bugs/Termination/ConstantLoopCondition.ql

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ where
8484
not fa.getField().isFinal() and
8585
fa.getParent*() = cond
8686
) and
87-
not exists(ArrayAccess aa | aa.getParent*() = cond)
87+
not exists(ArrayAccess aa | aa.getParent*() = cond) and
88+
not exists(RValue use |
89+
use.getParent*() = cond and use.getVariable().(LocalVariableDecl).isCaptured()
90+
)
8891
select cond, "$@ might not terminate, as this loop condition is constant within the loop.", loop,
8992
"Loop"

java/ql/test/kotlin/library-tests/variables/variableAccesses.expected

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ varAcc
1414
| variables.kt:30:9:30:9 | this |
1515
| variables.kt:32:9:32:12 | this |
1616
| variables.kt:33:9:33:12 | this |
17+
| variables.kt:52:31:52:31 | f |
18+
| variables.kt:58:17:58:17 | c |
19+
| variables.kt:59:17:59:17 | d |
20+
| variables.kt:65:17:65:17 | e |
21+
| variables.kt:66:17:66:17 | f |
1722
extensionReceiverAcc
1823
| variables.kt:29:9:29:9 | this |
1924
| variables.kt:30:9:30:9 | this |

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,29 @@ isFinal
22
| variables.kt:6:9:6:26 | int local1 | final |
33
| variables.kt:8:9:8:26 | int local2 | non-final |
44
| variables.kt:10:9:10:26 | int local3 | final |
5+
| variables.kt:55:5:55:16 | boolean c | non-final |
6+
| variables.kt:56:5:56:16 | boolean d | final |
7+
| variables.kt:62:5:62:16 | boolean e | non-final |
8+
| variables.kt:63:5:63:16 | boolean f | final |
59
compileTimeConstant
610
| variables.kt:3:5:3:21 | prop |
711
| variables.kt:3:5:3:21 | this.prop |
812
| variables.kt:7:17:7:22 | local1 |
913
| variables.kt:15:1:15:21 | VariablesKt.topLevel |
1014
| variables.kt:15:1:15:21 | VariablesKt.topLevel |
15+
| variables.kt:59:17:59:17 | d |
16+
| variables.kt:66:17:66:17 | f |
17+
isCaptured
18+
| variables.kt:6:9:6:26 | int local1 | not captured |
19+
| variables.kt:8:9:8:26 | int local2 | not captured |
20+
| variables.kt:10:9:10:26 | int local3 | not captured |
21+
| variables.kt:55:5:55:16 | boolean c | captured |
22+
| variables.kt:56:5:56:16 | boolean d | not captured |
23+
| variables.kt:62:5:62:16 | boolean e | captured |
24+
| variables.kt:63:5:63:16 | boolean f | not captured |
25+
varCaptured
26+
| variables.kt:55:5:55:16 | boolean c | variables.kt:57:9:60:5 | invoke |
27+
| variables.kt:62:5:62:16 | boolean e | variables.kt:64:5:67:5 | fn2 |
1128
#select
1229
| variables.kt:3:5:3:21 | prop | int | variables.kt:3:21:3:21 | 1 |
1330
| variables.kt:5:20:5:29 | param | int | file://:0:0:0:0 | <none> |
@@ -18,3 +35,8 @@ compileTimeConstant
1835
| variables.kt:21:11:21:18 | o | C1 | file://:0:0:0:0 | <none> |
1936
| variables.kt:21:11:21:18 | o | C1 | variables.kt:21:11:21:18 | o |
2037
| variables.kt:28:9:28:10 | <this> | C1 | file://:0:0:0:0 | <none> |
38+
| variables.kt:52:9:52:26 | f | Function0<Unit> | file://:0:0:0:0 | <none> |
39+
| variables.kt:55:5:55:16 | boolean c | boolean | variables.kt:55:13:55:16 | true |
40+
| variables.kt:56:5:56:16 | boolean d | boolean | variables.kt:56:13:56:16 | true |
41+
| variables.kt:62:5:62:16 | boolean e | boolean | variables.kt:62:13:62:16 | true |
42+
| variables.kt:63:5:63:16 | boolean f | boolean | variables.kt:63:13:63:16 | true |

java/ql/test/kotlin/library-tests/variables/variables.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,22 @@ class C3 {
4747
this@C3.f0()
4848
}
4949
}
50-
}
50+
}
51+
52+
fun fn0(f: Function0<Unit>) = f()
53+
54+
fun fn1() {
55+
var c = true
56+
val d = true
57+
fn0 {
58+
println(c)
59+
println(d)
60+
}
61+
62+
var e = true
63+
val f = true
64+
fun fn2() {
65+
println(e)
66+
println(f)
67+
}
68+
}

java/ql/test/kotlin/library-tests/variables/variables.ql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,9 @@ query predicate isFinal(LocalVariableDecl v, string isFinal) {
4343
query predicate compileTimeConstant(CompileTimeConstantExpr e) {
4444
exists(Variable v | v.getAnAccess() = e)
4545
}
46+
47+
query predicate isCaptured(LocalVariableDecl v, string captured) {
48+
if v.isCaptured() then captured = "captured" else captured = "not captured"
49+
}
50+
51+
query predicate varCaptured(LocalVariableDecl v, Callable c) { v.getACapturingCallable() = c }

java/ql/test/kotlin/query-tests/ConstantLoopCondition/A.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,18 @@ fun fn1() {
77
c = false
88
}
99
}
10+
11+
var d = true
12+
while (d) { // FALSE NEGATIVE
13+
fn0 {
14+
println(d)
15+
}
16+
}
17+
18+
val e = true
19+
while (e) {
20+
fn0 {
21+
println(e)
22+
}
23+
}
1024
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
| A.kt:5:12:5:12 | c | $@ might not terminate, as this loop condition is constant within the loop. | A.kt:5:5:9:5 | while (...) | Loop |
1+
| A.kt:19:12:19:12 | e | $@ might not terminate, as this loop condition is constant within the loop. | A.kt:19:5:23:5 | while (...) | Loop |

0 commit comments

Comments
 (0)