Skip to content

Commit f19ade3

Browse files
committed
Java: Add StmtExpr
1 parent 8faabb8 commit f19ade3

File tree

11 files changed

+134
-5
lines changed

11 files changed

+134
-5
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: feature
3+
---
4+
* The QL class `StmtExpr` has been added to model statement expressions, that is, expressions whose result is discarded.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class CollectionMutation extends MethodAccess {
8484
CollectionMutation() { this.getMethod() instanceof CollectionMutator }
8585

8686
/** Holds if the result of this call is not immediately discarded. */
87-
predicate resultIsChecked() { not this.getParent() instanceof ExprStmt }
87+
predicate resultIsChecked() { not this instanceof StmtExpr }
8888
}
8989

9090
/** A method that queries the contents of a collection without mutating it. */

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,3 +2133,41 @@ class Argument extends Expr {
21332133
)
21342134
}
21352135
}
2136+
2137+
/**
2138+
* A statement expression, as specified by JLS 17 section 14.8.
2139+
* The result of a statement expression, if any, is discarded.
2140+
*
2141+
* Not to be confused with `ExprStmt`; while the child of an `ExprStmt` is always
2142+
* a `StmtExpr`, the opposite is not true. A `StmtExpr` occurs for example also
2143+
* as 'init' of a `for` statement.
2144+
*/
2145+
class StmtExpr extends Expr {
2146+
StmtExpr() {
2147+
this = any(ExprStmt s).getExpr()
2148+
or
2149+
this = any(ForStmt s).getAnInit() and not this instanceof LocalVariableDeclExpr
2150+
or
2151+
this = any(ForStmt s).getAnUpdate()
2152+
or
2153+
// Only applies to SwitchStmt, but not to SwitchExpr, see JLS 17 section 14.11.2
2154+
// TODO: Possibly redundant depending on how https://github.com/github/codeql/issues/8570 is resolved
2155+
this = any(SwitchStmt s).getACase().getRuleExpression()
2156+
or
2157+
// TODO: Workarounds for https://github.com/github/codeql/issues/3605
2158+
exists(LambdaExpr lambda |
2159+
this = lambda.getExprBody() and
2160+
lambda.asMethod().getReturnType() instanceof VoidType
2161+
)
2162+
or
2163+
exists(MemberRefExpr memberRef, Method implicitMethod, Method overridden |
2164+
implicitMethod = memberRef.asMethod()
2165+
|
2166+
this.getParent().(ReturnStmt).getEnclosingCallable() = implicitMethod and
2167+
// asMethod() has bogus method with wrong return type as result, e.g. `run(): String` (overriding `Runnable.run(): void`)
2168+
// Therefore need to check the overridden method
2169+
implicitMethod.getSourceDeclaration().overridesOrInstantiates*(overridden) and
2170+
overridden.getReturnType() instanceof VoidType
2171+
)
2172+
}
2173+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class MapMutation extends MethodAccess {
5353
MapMutation() { this.getMethod() instanceof MapMutator }
5454

5555
/** Holds if the result of this call is not immediately discarded. */
56-
predicate resultIsChecked() { not this.getParent() instanceof ExprStmt }
56+
predicate resultIsChecked() { not this instanceof StmtExpr }
5757
}
5858

5959
/** A method that queries the contents of the map it belongs to without mutating it. */

java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import Chaining
1818

1919
predicate checkedMethodCall(MethodAccess ma) {
2020
relevantMethodCall(ma, _) and
21-
not ma.getParent() instanceof ExprStmt
21+
not ma instanceof StmtExpr
2222
}
2323

2424
/**

java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ predicate unboundedQueue(RefType t) {
4545

4646
from MethodAccess ma, SpecialMethod m
4747
where
48-
ma.getParent() instanceof ExprStmt and
48+
ma instanceof StmtExpr and
4949
m = ma.getMethod() and
5050
(
5151
m.isMethod("java.util", "Queue", "offer", 1) and not unboundedQueue(m.getDeclaringType())

java/ql/src/experimental/Security/CWE/CWE-297/IgnoredHostnameVerification.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ private class HostnameVerificationCall extends MethodAccess {
2121
}
2222

2323
/** Holds if the result of the call is not used. */
24-
predicate isIgnored() { this = any(ExprStmt es).getExpr() }
24+
predicate isIgnored() { this instanceof StmtExpr }
2525
}
2626

2727
from HostnameVerificationCall verification
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
| StmtExpr.java:7:9:7:18 | toString(...) |
2+
| StmtExpr.java:13:9:13:13 | ...=... |
3+
| StmtExpr.java:14:9:14:11 | ...++ |
4+
| StmtExpr.java:15:9:15:11 | ++... |
5+
| StmtExpr.java:16:9:16:11 | ...-- |
6+
| StmtExpr.java:17:9:17:11 | --... |
7+
| StmtExpr.java:19:9:19:20 | new Object(...) |
8+
| StmtExpr.java:22:9:22:28 | clone(...) |
9+
| StmtExpr.java:25:14:25:39 | println(...) |
10+
| StmtExpr.java:30:17:30:44 | println(...) |
11+
| StmtExpr.java:45:24:45:33 | toString(...) |
12+
| StmtExpr.java:58:28:58:37 | toString(...) |
13+
| StmtExpr.java:60:13:60:22 | toString(...) |
14+
| StmtExpr.java:66:23:66:36 | toString(...) |
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package StmtExpr;
2+
3+
import java.util.function.Supplier;
4+
5+
class StmtExpr {
6+
void test() {
7+
toString();
8+
9+
// LocalVariableDeclarationStatement with init is not a StatementExpression
10+
String s = toString();
11+
12+
int i;
13+
i = 0;
14+
i++;
15+
++i;
16+
i--;
17+
--i;
18+
19+
new Object();
20+
// ArrayCreationExpression cannot be a StatementExpression, but a method access
21+
// on it can be
22+
new int[] {}.clone();
23+
24+
// for statement init can be StatementExpression
25+
for (System.out.println("init");;) {
26+
break;
27+
}
28+
29+
// for statement update is StatementExpression
30+
for (;; System.out.println("update")) {
31+
break;
32+
}
33+
34+
// variable declaration and condition are not StatementExpressions
35+
for (int i1 = 0; i1 < 10;) { }
36+
for (int i1, i2 = 0; i2 < 10;) { }
37+
for (;;) {
38+
break;
39+
}
40+
41+
// Not a StatementExpression
42+
for (int i2 : new int[] {1}) { }
43+
44+
switch(1) {
45+
default -> toString(); // StatementExpression
46+
}
47+
// SwitchExpression has no StatementExpression
48+
String s2 = switch(1) {
49+
default -> toString();
50+
};
51+
52+
// Lambda with non-void return type has no StatementExpression
53+
Supplier<Object> supplier1 = () -> toString();
54+
Supplier<Object> supplier2 = () -> {
55+
return toString();
56+
};
57+
// Lambda with void return type has StatementExpression
58+
Runnable r = () -> toString();
59+
Runnable r2 = () -> {
60+
toString();
61+
};
62+
63+
// Method reference with non-void return type has no StatementExpression
64+
Supplier<Object> supplier3 = StmtExpr::new;
65+
// Method reference with void return type has StatementExpression in implicit method body
66+
Runnable r3 = this::toString;
67+
}
68+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import java
2+
3+
from StmtExpr e
4+
select e

0 commit comments

Comments
 (0)