Skip to content

Commit 9a4d86e

Browse files
authored
Merge pull request #8571 from Marcono1234/marcono1234/statement-expression
Java: Add `ValueDiscardingExpr`
2 parents a47e429 + c217a1e commit 9a4d86e

File tree

11 files changed

+174
-5
lines changed

11 files changed

+174
-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 `ValueDiscardingExpr` has been added, representing expressions for which the value of the expression as a whole 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 ValueDiscardingExpr }
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: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,3 +2133,47 @@ class Argument extends Expr {
21332133
)
21342134
}
21352135
}
2136+
2137+
/**
2138+
* An expression for which the value of the expression as a whole is discarded. Only cases
2139+
* of discarded values at the language level (as described by the JLS) are considered;
2140+
* data flow, for example to determine if an assigned variable value is ever read, is not
2141+
* considered. Such expressions can for example appear as part of an `ExprStmt` or as
2142+
* initializer of a `for` loop.
2143+
*
2144+
* For example, for the statement `i++;` the value of the increment expression, that is the
2145+
* old value of variable `i`, is discarded. Whereas for the statement `println(i++);` the
2146+
* value of the increment expression is not discarded but used as argument for the method call.
2147+
*/
2148+
class ValueDiscardingExpr extends Expr {
2149+
ValueDiscardingExpr() {
2150+
(
2151+
this = any(ExprStmt s).getExpr()
2152+
or
2153+
this = any(ForStmt s).getAnInit() and not this instanceof LocalVariableDeclExpr
2154+
or
2155+
this = any(ForStmt s).getAnUpdate()
2156+
or
2157+
// Only applies to SwitchStmt, but not to SwitchExpr, see JLS 17 section 14.11.2
2158+
this = any(SwitchStmt s).getACase().getRuleExpression()
2159+
or
2160+
// TODO: Workarounds for https://github.com/github/codeql/issues/3605
2161+
exists(LambdaExpr lambda |
2162+
this = lambda.getExprBody() and
2163+
lambda.asMethod().getReturnType() instanceof VoidType
2164+
)
2165+
or
2166+
exists(MemberRefExpr memberRef, Method implicitMethod, Method overridden |
2167+
implicitMethod = memberRef.asMethod()
2168+
|
2169+
this.getParent().(ReturnStmt).getEnclosingCallable() = implicitMethod and
2170+
// asMethod() has bogus method with wrong return type as result, e.g. `run(): String` (overriding `Runnable.run(): void`)
2171+
// Therefore need to check the overridden method
2172+
implicitMethod.getSourceDeclaration().overridesOrInstantiates*(overridden) and
2173+
overridden.getReturnType() instanceof VoidType
2174+
)
2175+
) and
2176+
// Ignore if this expression is a method call with `void` as return type
2177+
not this.getType() instanceof VoidType
2178+
}
2179+
}

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 ValueDiscardingExpr }
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 ValueDiscardingExpr
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 ValueDiscardingExpr 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 ValueDiscardingExpr }
2525
}
2626

2727
from HostnameVerificationCall verification
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
| ValueDiscardingExpr.java:9:9:9:18 | toString(...) |
2+
| ValueDiscardingExpr.java:18:9:18:13 | ...=... |
3+
| ValueDiscardingExpr.java:19:9:19:11 | ...++ |
4+
| ValueDiscardingExpr.java:20:9:20:11 | ++... |
5+
| ValueDiscardingExpr.java:21:9:21:11 | ...-- |
6+
| ValueDiscardingExpr.java:22:9:22:11 | --... |
7+
| ValueDiscardingExpr.java:24:9:24:20 | new Object(...) |
8+
| ValueDiscardingExpr.java:27:9:27:28 | clone(...) |
9+
| ValueDiscardingExpr.java:30:14:30:38 | append(...) |
10+
| ValueDiscardingExpr.java:35:17:35:43 | append(...) |
11+
| ValueDiscardingExpr.java:55:24:55:33 | toString(...) |
12+
| ValueDiscardingExpr.java:74:29:74:38 | toString(...) |
13+
| ValueDiscardingExpr.java:76:13:76:22 | toString(...) |
14+
| ValueDiscardingExpr.java:90:23:90:35 | new StmtExpr(...) |
15+
| ValueDiscardingExpr.java:91:23:91:36 | toString(...) |
16+
| ValueDiscardingExpr.java:95:25:95:37 | new String[] |
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package StmtExpr;
2+
3+
import java.util.function.IntConsumer;
4+
import java.util.function.IntFunction;
5+
import java.util.function.Supplier;
6+
7+
class StmtExpr {
8+
void test() {
9+
toString();
10+
11+
// Method call with `void` return type is not a ValueDiscardingExpr
12+
System.out.println("test");
13+
14+
// LocalVariableDeclarationStatement with init is not a ValueDiscardingExpr
15+
String s = toString();
16+
17+
int i;
18+
i = 0;
19+
i++;
20+
++i;
21+
i--;
22+
--i;
23+
24+
new Object();
25+
// Language specification does not permit ArrayCreationExpression at locations where its
26+
// value would be discard, but the value of a method access on it can be discarded
27+
new int[] {}.clone();
28+
29+
// for statement init can discard value
30+
for (System.out.append("init");;) {
31+
break;
32+
}
33+
34+
// for statement update discards value
35+
for (;; System.out.append("update")) {
36+
break;
37+
}
38+
39+
// Method call with `void` return type is not a ValueDiscardingExpr
40+
for (;; System.out.println("update")) {
41+
break;
42+
}
43+
44+
// variable declaration and condition are not ValueDiscardingExpr
45+
for (int i1 = 0; i1 < 10;) { }
46+
for (int i1, i2 = 0; i2 < 10;) { }
47+
for (;;) {
48+
break;
49+
}
50+
51+
// Not a ValueDiscardingExpr
52+
for (int i2 : new int[] {1}) { }
53+
54+
switch(1) {
55+
default -> toString(); // ValueDiscardingExpr
56+
}
57+
58+
switch(1) {
59+
// Method call with `void` return type is not a ValueDiscardingExpr
60+
default -> System.out.println();
61+
}
62+
63+
String s2 = switch(1) {
64+
// Expression in SwitchExpression case rule is not a ValueDiscardingExpr
65+
default -> toString();
66+
};
67+
68+
// Expression in lambda with non-void return type is not a ValueDiscardingExpr
69+
Supplier<Object> supplier1 = () -> toString();
70+
Supplier<Object> supplier2 = () -> {
71+
return toString();
72+
};
73+
// Expression in lambda with void return type is ValueDiscardingExpr
74+
Runnable r1 = () -> toString();
75+
Runnable r2 = () -> {
76+
toString();
77+
};
78+
// Lambda with method call with `void` return type is not a ValueDiscardingExpr
79+
Runnable r3 = () -> System.out.println();
80+
Runnable r4 = () -> {
81+
System.out.println();
82+
};
83+
84+
// Method reference with non-void return type has no ValueDiscardingExpr
85+
Supplier<Object> supplier3 = StmtExpr::new;
86+
IntFunction<Object> f = String[]::new;
87+
Supplier<Object> supplier4 = this::toString;
88+
89+
// Method reference with void return type has ValueDiscardingExpr in implicit method body
90+
Runnable r5 = StmtExpr::new;
91+
Runnable r6 = this::toString;
92+
// Interestingly a method reference expression with function type (int)->void allows usage of
93+
// array creation expressions, even though a regular StatementExpression would not allow it,
94+
// for example the ExpressionStatement `new String[1];` is not allowed by the JLS
95+
IntConsumer c = String[]::new;
96+
97+
// Method reference referring to method with `void` return type is not a ValueDiscardingExpr
98+
Runnable r7 = System.out::println;
99+
}
100+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import java
2+
3+
from ValueDiscardingExpr e
4+
select e

0 commit comments

Comments
 (0)