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

Skip to content

Commit 6c990c2

Browse files
committed
Add pattern-case support and generally debug switch CFGs
These were reasonably broken beforehand, due to not taking switch rules into account in enough places, and confusing the expression/statement switch rule distinction with the distinction between switch statements and expressions. (For example, `switch(x) { 1 -> System.out.println("Hello world") ... }` is a statement, but has a rule expression).
1 parent f4b45fa commit 6c990c2

7 files changed

Lines changed: 177 additions & 22 deletions

File tree

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

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ private module ControlFlowGraphImpl {
435435
}
436436

437437
/**
438-
* Gets a SwitchCase's successor SwitchCase, if any.
438+
* Holds if `succ` is `pred`'s successor `SwitchCase`.
439439
*/
440440
private predicate nextSwitchCase(SwitchCase pred, SwitchCase succ) {
441441
exists(SwitchExpr se, int idx | se.getCase(idx) = pred and se.getCase(idx + 1) = succ)
@@ -723,7 +723,7 @@ private module ControlFlowGraphImpl {
723723
*/
724724
private predicate last(ControlFlowNode n, ControlFlowNode last, Completion completion) {
725725
// Exceptions are propagated from any sub-expression.
726-
// As are any break, continue, or return completions.
726+
// As are any break, yield, continue, or return completions.
727727
exists(Expr e | e.getParent() = n |
728728
last(e, last, completion) and not completion instanceof NormalOrBooleanCompletion
729729
)
@@ -859,7 +859,7 @@ private module ControlFlowGraphImpl {
859859
// any other abnormal completion is propagated
860860
last(switch.getAStmt(), last, completion) and
861861
completion != anonymousBreakCompletion() and
862-
completion != NormalCompletion()
862+
not completion instanceof NormalOrBooleanCompletion
863863
or
864864
// if the last case completes normally, then so does the switch
865865
last(switch.getStmt(strictcount(switch.getAStmt()) - 1), last, NormalCompletion()) and
@@ -879,32 +879,43 @@ private module ControlFlowGraphImpl {
879879
// any other abnormal completion is propagated
880880
last(switch.getAStmt(), last, completion) and
881881
not completion instanceof YieldCompletion and
882-
completion != NormalCompletion()
882+
not completion instanceof NormalOrBooleanCompletion
883883
)
884884
or
885-
// the last node in a case rule is the last node in the right-hand side
886-
// if the rhs is a statement we wrap the completion as a break
887-
exists(Completion caseCompletion |
888-
last(n.(SwitchCase).getRuleStatement(), last, caseCompletion) and
885+
// the last node in a case rule in statement context is the last node in the right-hand side.
886+
// If the rhs is a statement, we wrap the completion as a break.
887+
exists(Completion caseCompletion, SwitchStmt parent, SwitchCase case |
888+
case = n and
889+
case = parent.getACase() and
890+
last(case.getRuleStatementOrExpressionStatement(), last, caseCompletion) and
889891
if caseCompletion instanceof NormalOrBooleanCompletion
890892
then completion = anonymousBreakCompletion()
891893
else completion = caseCompletion
892894
)
893895
or
894-
// ...and if the rhs is an expression we wrap the completion as a yield
895-
exists(Completion caseCompletion |
896-
last(n.(SwitchCase).getRuleExpression(), last, caseCompletion) and
897-
if caseCompletion instanceof NormalOrBooleanCompletion
898-
then completion = YieldCompletion(caseCompletion)
899-
else completion = caseCompletion
896+
// ...and when a switch occurs in expression context, we wrap the RHS in a yield statement.
897+
// Note the wrapping can only occur in the expression case, because a statement would need
898+
// to have explicit `yield` statements.
899+
exists(SwitchExpr parent, SwitchCase case |
900+
case = n and
901+
case = parent.getACase() and
902+
(
903+
exists(Completion caseCompletion |
904+
last(case.getRuleExpression(), last, caseCompletion) and
905+
if caseCompletion instanceof NormalOrBooleanCompletion
906+
then completion = YieldCompletion(caseCompletion)
907+
else completion = caseCompletion
908+
)
909+
or
910+
last(case.getRuleStatement(), last, completion)
911+
)
900912
)
901913
or
902-
// The last node in a case could always be a failing pattern check.
903-
last = n.(PatternCase) and
904-
completion = basicBooleanCompletion(false)
905-
or
906-
// The last node in a non-rule case is its variable declaration.
914+
// The normal last node in a non-rule pattern case is its variable declaration.
915+
// Note that either rule or non-rule pattern cases can end with pattern match failure, whereupon
916+
// they branch to the next candidate pattern. This is accounted for in the `succ` relation.
907917
last = n.(PatternCase).getDecl() and
918+
not n.(PatternCase).isRule() and
908919
completion = NormalCompletion()
909920
or
910921
// the last statement of a synchronized statement is the last statement of its body
@@ -1231,6 +1242,10 @@ private module ControlFlowGraphImpl {
12311242
)
12321243
or
12331244
// Statements within a switch body execute sequentially.
1245+
// Note this includes non-rule case statements and the successful pattern match successor
1246+
// of a non-rule pattern case statement. Rule case statements do not complete normally
1247+
// (they always break or yield), and the case of pattern matching failure branching to the
1248+
// next case is specially handled in the `PatternCase` logic below.
12341249
exists(int i |
12351250
last(switch.getStmt(i), n, completion) and result = first(switch.getStmt(i + 1))
12361251
)
@@ -1251,6 +1266,10 @@ private module ControlFlowGraphImpl {
12511266
)
12521267
or
12531268
// Statements within a switch body execute sequentially.
1269+
// Note this includes non-rule case statements and the successful pattern match successor
1270+
// of a non-rule pattern case statement. Rule case statements do not complete normally
1271+
// (they always break or yield), and the case of pattern matching failure branching to the
1272+
// next case is specially handled in the `PatternCase` logic below.
12541273
exists(int i |
12551274
last(switch.getStmt(i), n, completion) and result = first(switch.getStmt(i + 1))
12561275
)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1514,7 +1514,7 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
15141514
* which may be either a normal `case` or a `default`.
15151515
*/
15161516
SwitchCase getCase(int i) {
1517-
result = rank[i](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
1517+
result = rank[i + 1](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
15181518
}
15191519

15201520
/**

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,8 @@ class SwitchStmt extends Stmt, @switchstmt {
387387
* which may be either a normal `case` or a `default`.
388388
*/
389389
SwitchCase getCase(int i) {
390-
result = rank[i](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
390+
result =
391+
rank[i + 1](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
391392
}
392393

393394
/**
@@ -469,7 +470,17 @@ class SwitchCase extends Stmt, @case {
469470
* This predicate is mutually exclusive with `getRuleExpression`.
470471
*/
471472
Stmt getRuleStatement() {
472-
result.getParent() = this and result.getIndex() = -1 and not result instanceof ExprStmt
473+
result = this.getRuleStatementOrExpressionStatement() and not result instanceof ExprStmt
474+
}
475+
476+
/**
477+
* Gets the statement, including an expression statement, on the RHS of the arrow, if any.
478+
*
479+
* This means this could be an explicit `case e1 -> { s1; ... }` or an implicit
480+
* `case e1 -> stmt;` rule.
481+
*/
482+
Stmt getRuleStatementOrExpressionStatement() {
483+
result.getParent() = this and result.getIndex() = -1
473484
}
474485
}
475486

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
public class Test {
2+
3+
public static void test(Object thing) {
4+
5+
switch (thing) {
6+
case String s -> System.out.println(s);
7+
case Integer i -> System.out.println("An integer: " + i);
8+
default -> { }
9+
}
10+
11+
switch (thing) {
12+
case String s:
13+
System.out.println(s);
14+
break;
15+
case Integer i:
16+
System.out.println("An integer:" + i);
17+
break;
18+
default:
19+
break;
20+
}
21+
22+
var thingAsString = switch(thing) {
23+
case String s -> s;
24+
case Integer i -> "An integer: " + i;
25+
default -> "Something else";
26+
};
27+
28+
var thingAsString2 = switch(thing) {
29+
case String s:
30+
yield s;
31+
case Integer i:
32+
yield "An integer: " + i;
33+
default:
34+
yield "Something else";
35+
};
36+
37+
}
38+
39+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
//semmle-extractor-options: --javac-args --release 21
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
| Test.java:1:14:1:17 | super(...) | Test.java:1:14:1:17 | Test |
2+
| Test.java:1:14:1:17 | { ... } | Test.java:1:14:1:17 | super(...) |
3+
| Test.java:3:41:37:3 | { ... } | Test.java:5:6:5:19 | switch (...) |
4+
| Test.java:5:6:5:19 | switch (...) | Test.java:5:14:5:18 | thing |
5+
| Test.java:5:14:5:18 | thing | Test.java:6:8:6:23 | case T t ... |
6+
| Test.java:6:8:6:23 | case T t ... | Test.java:6:20:6:20 | s |
7+
| Test.java:6:8:6:23 | case T t ... | Test.java:7:8:7:24 | case T t ... |
8+
| Test.java:6:20:6:20 | s | Test.java:6:25:6:34 | System.out |
9+
| Test.java:6:25:6:34 | System.out | Test.java:6:44:6:44 | s |
10+
| Test.java:6:25:6:45 | println(...) | Test.java:11:6:11:19 | switch (...) |
11+
| Test.java:6:25:6:46 | <Expr>; | Test.java:6:25:6:34 | System.out |
12+
| Test.java:6:44:6:44 | s | Test.java:6:25:6:45 | println(...) |
13+
| Test.java:7:8:7:24 | case T t ... | Test.java:7:21:7:21 | i |
14+
| Test.java:7:8:7:24 | case T t ... | Test.java:8:8:8:17 | default |
15+
| Test.java:7:21:7:21 | i | Test.java:7:26:7:35 | System.out |
16+
| Test.java:7:26:7:35 | System.out | Test.java:7:45:7:58 | "An integer: " |
17+
| Test.java:7:26:7:63 | println(...) | Test.java:11:6:11:19 | switch (...) |
18+
| Test.java:7:26:7:64 | <Expr>; | Test.java:7:26:7:35 | System.out |
19+
| Test.java:7:45:7:58 | "An integer: " | Test.java:7:62:7:62 | i |
20+
| Test.java:7:45:7:62 | ... + ... | Test.java:7:26:7:63 | println(...) |
21+
| Test.java:7:62:7:62 | i | Test.java:7:45:7:62 | ... + ... |
22+
| Test.java:8:8:8:17 | default | Test.java:8:19:8:21 | { ... } |
23+
| Test.java:8:19:8:21 | { ... } | Test.java:11:6:11:19 | switch (...) |
24+
| Test.java:11:6:11:19 | switch (...) | Test.java:11:14:11:18 | thing |
25+
| Test.java:11:14:11:18 | thing | Test.java:12:8:12:21 | case T t ... |
26+
| Test.java:12:8:12:21 | case T t ... | Test.java:12:20:12:20 | s |
27+
| Test.java:12:8:12:21 | case T t ... | Test.java:15:8:15:22 | case T t ... |
28+
| Test.java:12:20:12:20 | s | Test.java:13:10:13:31 | <Expr>; |
29+
| Test.java:13:10:13:19 | System.out | Test.java:13:29:13:29 | s |
30+
| Test.java:13:10:13:30 | println(...) | Test.java:14:10:14:15 | break |
31+
| Test.java:13:10:13:31 | <Expr>; | Test.java:13:10:13:19 | System.out |
32+
| Test.java:13:29:13:29 | s | Test.java:13:10:13:30 | println(...) |
33+
| Test.java:14:10:14:15 | break | Test.java:22:6:26:7 | var ...; |
34+
| Test.java:15:8:15:22 | case T t ... | Test.java:15:21:15:21 | i |
35+
| Test.java:15:8:15:22 | case T t ... | Test.java:18:8:18:15 | default |
36+
| Test.java:15:21:15:21 | i | Test.java:16:10:16:47 | <Expr>; |
37+
| Test.java:16:10:16:19 | System.out | Test.java:16:29:16:41 | "An integer:" |
38+
| Test.java:16:10:16:46 | println(...) | Test.java:17:10:17:15 | break |
39+
| Test.java:16:10:16:47 | <Expr>; | Test.java:16:10:16:19 | System.out |
40+
| Test.java:16:29:16:41 | "An integer:" | Test.java:16:45:16:45 | i |
41+
| Test.java:16:29:16:45 | ... + ... | Test.java:16:10:16:46 | println(...) |
42+
| Test.java:16:45:16:45 | i | Test.java:16:29:16:45 | ... + ... |
43+
| Test.java:17:10:17:15 | break | Test.java:22:6:26:7 | var ...; |
44+
| Test.java:18:8:18:15 | default | Test.java:19:10:19:15 | break |
45+
| Test.java:19:10:19:15 | break | Test.java:22:6:26:7 | var ...; |
46+
| Test.java:22:6:26:7 | var ...; | Test.java:22:26:22:38 | switch (...) |
47+
| Test.java:22:10:22:38 | thingAsString | Test.java:28:6:35:7 | var ...; |
48+
| Test.java:22:26:22:38 | switch (...) | Test.java:22:33:22:37 | thing |
49+
| Test.java:22:33:22:37 | thing | Test.java:23:8:23:23 | case T t ... |
50+
| Test.java:23:8:23:23 | case T t ... | Test.java:23:20:23:20 | s |
51+
| Test.java:23:8:23:23 | case T t ... | Test.java:24:8:24:24 | case T t ... |
52+
| Test.java:23:20:23:20 | s | Test.java:23:25:23:25 | s |
53+
| Test.java:23:25:23:25 | s | Test.java:22:10:22:38 | thingAsString |
54+
| Test.java:24:8:24:24 | case T t ... | Test.java:24:21:24:21 | i |
55+
| Test.java:24:8:24:24 | case T t ... | Test.java:25:8:25:17 | default |
56+
| Test.java:24:21:24:21 | i | Test.java:24:26:24:39 | "An integer: " |
57+
| Test.java:24:26:24:39 | "An integer: " | Test.java:24:43:24:43 | i |
58+
| Test.java:24:26:24:43 | ... + ... | Test.java:22:10:22:38 | thingAsString |
59+
| Test.java:24:43:24:43 | i | Test.java:24:26:24:43 | ... + ... |
60+
| Test.java:25:8:25:17 | default | Test.java:25:19:25:34 | "Something else" |
61+
| Test.java:25:19:25:34 | "Something else" | Test.java:22:10:22:38 | thingAsString |
62+
| Test.java:28:6:35:7 | var ...; | Test.java:28:27:28:39 | switch (...) |
63+
| Test.java:28:10:28:39 | thingAsString2 | Test.java:3:22:3:25 | test |
64+
| Test.java:28:27:28:39 | switch (...) | Test.java:28:34:28:38 | thing |
65+
| Test.java:28:34:28:38 | thing | Test.java:29:8:29:21 | case T t ... |
66+
| Test.java:29:8:29:21 | case T t ... | Test.java:29:20:29:20 | s |
67+
| Test.java:29:8:29:21 | case T t ... | Test.java:31:8:31:22 | case T t ... |
68+
| Test.java:29:20:29:20 | s | Test.java:30:10:30:17 | yield ... |
69+
| Test.java:30:10:30:17 | yield ... | Test.java:30:16:30:16 | s |
70+
| Test.java:30:16:30:16 | s | Test.java:28:10:28:39 | thingAsString2 |
71+
| Test.java:31:8:31:22 | case T t ... | Test.java:31:21:31:21 | i |
72+
| Test.java:31:8:31:22 | case T t ... | Test.java:33:8:33:15 | default |
73+
| Test.java:31:21:31:21 | i | Test.java:32:10:32:34 | yield ... |
74+
| Test.java:32:10:32:34 | yield ... | Test.java:32:16:32:29 | "An integer: " |
75+
| Test.java:32:16:32:29 | "An integer: " | Test.java:32:33:32:33 | i |
76+
| Test.java:32:16:32:33 | ... + ... | Test.java:28:10:28:39 | thingAsString2 |
77+
| Test.java:32:33:32:33 | i | Test.java:32:16:32:33 | ... + ... |
78+
| Test.java:33:8:33:15 | default | Test.java:34:10:34:32 | yield ... |
79+
| Test.java:34:10:34:32 | yield ... | Test.java:34:16:34:31 | "Something else" |
80+
| Test.java:34:16:34:31 | "Something else" | Test.java:28:10:28:39 | thingAsString2 |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import java
2+
3+
from ControlFlowNode cn
4+
where cn.getFile().getBaseName() = "Test.java"
5+
select cn, cn.getASuccessor()

0 commit comments

Comments
 (0)