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

Skip to content

Commit 144218e

Browse files
committed
Implement switch CFG when there are mixed constant and pattern cases
1 parent 54a89d6 commit 144218e

3 files changed

Lines changed: 158 additions & 28 deletions

File tree

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

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -437,12 +437,62 @@ private module ControlFlowGraphImpl {
437437
}
438438

439439
/**
440-
* Holds if `succ` is `pred`'s successor `SwitchCase`.
440+
* Gets the `i`th `SwitchCase` defined on `switch`, if one exists.
441441
*/
442-
private predicate nextSwitchCase(SwitchCase pred, SwitchCase succ) {
443-
exists(SwitchExpr se, int idx | se.getCase(idx) = pred and se.getCase(idx + 1) = succ)
444-
or
445-
exists(SwitchStmt ss, int idx | ss.getCase(idx) = pred and ss.getCase(idx + 1) = succ)
442+
private SwitchCase getCase(StmtParent switch, int i) {
443+
result = switch.(SwitchExpr).getCase(i) or result = switch.(SwitchStmt).getCase(i)
444+
}
445+
446+
/**
447+
* Gets the `i`th `PatternCase` defined on `switch`, if one exists.
448+
*/
449+
private PatternCase getPatternCase(StmtParent switch, int i) {
450+
result = rank[i + 1](PatternCase pc, int caseIdx | pc = getCase(switch, caseIdx) | pc order by caseIdx asc)
451+
}
452+
453+
/**
454+
* Gets the PatternCase after pc, if one exists.
455+
*/
456+
private PatternCase getNextPatternCase(PatternCase pc) {
457+
exists(int idx, StmtParent switch | pc = getPatternCase(switch, idx) and result = getPatternCase(switch, idx + 1))
458+
}
459+
460+
/**
461+
* Gets a `SwitchCase` that may be `pred`'s direct successor.
462+
*
463+
* This means any switch case that comes after `pred` up to the next pattern case, if any, except for `case null`.
464+
*
465+
* Because we know the switch block contains at least one pattern, we know by https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11
466+
* that any default case comes after the last pattern case.
467+
*/
468+
private SwitchCase getASuccessorSwitchCase(PatternCase pred) {
469+
result.getParent() = pred.getParent() and
470+
result.getIndex() > pred.getIndex() and
471+
not result.(ConstCase).getValue(_) instanceof NullLiteral and
472+
(
473+
result.getIndex() <= getNextPatternCase(pred).getIndex()
474+
or
475+
not exists(getNextPatternCase(pred))
476+
)
477+
}
478+
479+
/**
480+
* Gets a `SwitchCase` that may occur first in `switch`.
481+
*
482+
* If the block contains at least one PatternCase, this is any case up to and including that case, or
483+
* the case handling the null literal if any.
484+
*
485+
* Otherwise it is any case in the switch block.
486+
*/
487+
private SwitchCase getAFirstSwitchCase(StmtParent switch) {
488+
result.getParent() = switch and
489+
(
490+
result.(ConstCase).getValue(_) instanceof NullLiteral
491+
or
492+
not exists(getPatternCase(switch, _))
493+
or
494+
result.getIndex() <= getPatternCase(switch, 0).getIndex()
495+
)
446496
}
447497

448498
/**
@@ -868,7 +918,7 @@ private module ControlFlowGraphImpl {
868918
completion = NormalCompletion()
869919
or
870920
// if no default case exists, then normal completion of the expression may terminate the switch
871-
not exists(switch.getDefaultCase()) and
921+
not exists(switch.getDefaultOrNullDefaultCase()) and
872922
last(switch.getExpr(), last, completion) and
873923
completion = NormalCompletion()
874924
)
@@ -1241,14 +1291,8 @@ private module ControlFlowGraphImpl {
12411291
// From the entry point control is transferred first to the expression...
12421292
n = switch and result = first(switch.getExpr())
12431293
or
1244-
// ...and then for a vanilla switch to any case, or for a pattern switch to the first one.
1245-
exists(SwitchCase firstExecutedCase |
1246-
if switch.getACase() instanceof PatternCase
1247-
then firstExecutedCase = switch.getCase(0)
1248-
else firstExecutedCase = switch.getACase()
1249-
|
1250-
last(switch.getExpr(), n, completion) and result = first(firstExecutedCase)
1251-
)
1294+
// ...and then to any case up to and including the first pattern case, if any.
1295+
last(switch.getExpr(), n, completion) and result = first(getAFirstSwitchCase(switch))
12521296
or
12531297
// Statements within a switch body execute sequentially.
12541298
// Note this includes non-rule case statements and the successful pattern match successor
@@ -1265,14 +1309,8 @@ private module ControlFlowGraphImpl {
12651309
// From the entry point control is transferred first to the expression...
12661310
n = switch and result = first(switch.getExpr())
12671311
or
1268-
// ...and then to one of the cases.
1269-
exists(SwitchCase firstExecutedCase |
1270-
if switch.getACase() instanceof PatternCase
1271-
then firstExecutedCase = switch.getCase(0)
1272-
else firstExecutedCase = switch.getACase()
1273-
|
1274-
last(switch.getExpr(), n, completion) and result = first(firstExecutedCase)
1275-
)
1312+
// ...and then to any case up to and including the first pattern case, if any.
1313+
last(switch.getExpr(), n, completion) and result = first(getAFirstSwitchCase(switch))
12761314
or
12771315
// Statements within a switch body execute sequentially.
12781316
// Note this includes non-rule case statements and the successful pattern match successor
@@ -1310,7 +1348,7 @@ private module ControlFlowGraphImpl {
13101348
n = case and
13111349
(
13121350
completion = basicBooleanCompletion(false) and
1313-
nextSwitchCase(case, result)
1351+
result = getASuccessorSwitchCase(case)
13141352
or
13151353
completion = basicBooleanCompletion(true) and
13161354
result = case.getDecl()
@@ -1330,7 +1368,7 @@ private module ControlFlowGraphImpl {
13301368
or
13311369
last(guard, n, completion) and
13321370
completion = basicBooleanCompletion(false) and
1333-
nextSwitchCase(case, result)
1371+
result = getASuccessorSwitchCase(case)
13341372
)
13351373
)
13361374
or

java/ql/test/library-tests/pattern-switch/cfg/Test.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,20 @@ public static void test(Object thing) {
7070
System.out.println("It's null, or something else");
7171
}
7272

73+
switch(thing) {
74+
case String s:
75+
System.out.println(s);
76+
break;
77+
case null:
78+
System.out.println("It's null");
79+
break;
80+
case Integer i:
81+
System.out.println("An integer:" + i);
82+
break;
83+
default:
84+
break;
85+
}
86+
7387
}
7488

7589
}

java/ql/test/library-tests/pattern-switch/cfg/test.expected

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
| Test.java:1:14:1:17 | super(...) | Test.java:1:14:1:17 | Test |
22
| Test.java:1:14:1:17 | { ... } | Test.java:1:14:1:17 | super(...) |
3-
| Test.java:3:41:55:3 | { ... } | Test.java:5:6:5:19 | switch (...) |
3+
| Test.java:3:41:87:3 | { ... } | Test.java:5:6:5:19 | switch (...) |
44
| Test.java:5:6:5:19 | switch (...) | Test.java:5:14:5:18 | thing |
55
| Test.java:5:14:5:18 | thing | Test.java:6:8:6:23 | case T t ... |
66
| Test.java:6:8:6:23 | case T t ... | Test.java:6:20:6:20 | s |
@@ -123,7 +123,7 @@
123123
| Test.java:50:27:50:41 | ... == ... | Test.java:51:8:51:44 | case T t ... |
124124
| Test.java:50:41:50:41 | 3 | Test.java:50:27:50:41 | ... == ... |
125125
| Test.java:50:46:50:55 | System.out | Test.java:50:65:50:74 | "Length 3" |
126-
| Test.java:50:46:50:75 | println(...) | Test.java:3:22:3:25 | test |
126+
| Test.java:50:46:50:75 | println(...) | Test.java:55:6:55:26 | switch (...) |
127127
| Test.java:50:46:50:76 | <Expr>; | Test.java:50:46:50:55 | System.out |
128128
| Test.java:50:65:50:74 | "Length 3" | Test.java:50:46:50:75 | println(...) |
129129
| Test.java:51:8:51:44 | case T t ... | Test.java:51:20:51:20 | s |
@@ -135,8 +135,86 @@
135135
| Test.java:51:27:51:41 | ... == ... | Test.java:52:8:52:17 | default |
136136
| Test.java:51:41:51:41 | 5 | Test.java:51:27:51:41 | ... == ... |
137137
| Test.java:51:46:51:55 | System.out | Test.java:51:65:51:74 | "Length 5" |
138-
| Test.java:51:46:51:75 | println(...) | Test.java:3:22:3:25 | test |
138+
| Test.java:51:46:51:75 | println(...) | Test.java:55:6:55:26 | switch (...) |
139139
| Test.java:51:46:51:76 | <Expr>; | Test.java:51:46:51:55 | System.out |
140140
| Test.java:51:65:51:74 | "Length 5" | Test.java:51:46:51:75 | println(...) |
141141
| Test.java:52:8:52:17 | default | Test.java:52:19:52:21 | { ... } |
142-
| Test.java:52:19:52:21 | { ... } | Test.java:3:22:3:25 | test |
142+
| Test.java:52:19:52:21 | { ... } | Test.java:55:6:55:26 | switch (...) |
143+
| Test.java:55:6:55:26 | switch (...) | Test.java:55:21:55:25 | thing |
144+
| Test.java:55:13:55:25 | (...)... | Test.java:56:8:56:21 | case ... |
145+
| Test.java:55:13:55:25 | (...)... | Test.java:58:8:58:21 | case ... |
146+
| Test.java:55:13:55:25 | (...)... | Test.java:61:8:61:42 | case T t ... |
147+
| Test.java:55:21:55:25 | thing | Test.java:55:13:55:25 | (...)... |
148+
| Test.java:56:8:56:21 | case ... | Test.java:57:10:57:44 | <Expr>; |
149+
| Test.java:57:10:57:19 | System.out | Test.java:57:29:57:42 | "It's Const1!" |
150+
| Test.java:57:10:57:43 | println(...) | Test.java:58:8:58:21 | case ... |
151+
| Test.java:57:10:57:44 | <Expr>; | Test.java:57:10:57:19 | System.out |
152+
| Test.java:57:29:57:42 | "It's Const1!" | Test.java:57:10:57:43 | println(...) |
153+
| Test.java:58:8:58:21 | case ... | Test.java:59:10:59:54 | <Expr>; |
154+
| Test.java:59:10:59:19 | System.out | Test.java:59:29:59:52 | "It's Const1 or Const2!" |
155+
| Test.java:59:10:59:53 | println(...) | Test.java:60:10:60:15 | break |
156+
| Test.java:59:10:59:54 | <Expr>; | Test.java:59:10:59:19 | System.out |
157+
| Test.java:59:29:59:52 | "It's Const1 or Const2!" | Test.java:59:10:59:53 | println(...) |
158+
| Test.java:60:10:60:15 | break | Test.java:73:6:73:18 | switch (...) |
159+
| Test.java:61:8:61:42 | case T t ... | Test.java:61:20:61:20 | s |
160+
| Test.java:61:8:61:42 | case T t ... | Test.java:63:8:63:21 | case ... |
161+
| Test.java:61:8:61:42 | case T t ... | Test.java:66:8:66:22 | case ... |
162+
| Test.java:61:8:61:42 | case T t ... | Test.java:69:8:69:26 | case null, default |
163+
| Test.java:61:20:61:20 | s | Test.java:61:27:61:27 | s |
164+
| Test.java:61:27:61:27 | s | Test.java:61:27:61:36 | length(...) |
165+
| Test.java:61:27:61:36 | length(...) | Test.java:61:41:61:41 | 6 |
166+
| Test.java:61:27:61:41 | ... <= ... | Test.java:62:10:62:83 | <Expr>; |
167+
| Test.java:61:27:61:41 | ... <= ... | Test.java:63:8:63:21 | case ... |
168+
| Test.java:61:27:61:41 | ... <= ... | Test.java:66:8:66:22 | case ... |
169+
| Test.java:61:27:61:41 | ... <= ... | Test.java:69:8:69:26 | case null, default |
170+
| Test.java:61:41:61:41 | 6 | Test.java:61:27:61:41 | ... <= ... |
171+
| Test.java:62:10:62:19 | System.out | Test.java:62:29:62:81 | "It's <= 6 chars long, and neither Const1 nor Const2" |
172+
| Test.java:62:10:62:82 | println(...) | Test.java:63:8:63:21 | case ... |
173+
| Test.java:62:10:62:83 | <Expr>; | Test.java:62:10:62:19 | System.out |
174+
| Test.java:62:29:62:81 | "It's <= 6 chars long, and neither Const1 nor Const2" | Test.java:62:10:62:82 | println(...) |
175+
| Test.java:63:8:63:21 | case ... | Test.java:64:10:64:96 | <Expr>; |
176+
| Test.java:64:10:64:19 | System.out | Test.java:64:29:64:94 | "It's (<= 6 chars long, and neither Const1 nor Const2), or Const3" |
177+
| Test.java:64:10:64:95 | println(...) | Test.java:65:10:65:15 | break |
178+
| Test.java:64:10:64:96 | <Expr>; | Test.java:64:10:64:19 | System.out |
179+
| Test.java:64:29:64:94 | "It's (<= 6 chars long, and neither Const1 nor Const2), or Const3" | Test.java:64:10:64:95 | println(...) |
180+
| Test.java:65:10:65:15 | break | Test.java:73:6:73:18 | switch (...) |
181+
| Test.java:66:8:66:22 | case ... | Test.java:67:10:67:44 | <Expr>; |
182+
| Test.java:67:10:67:19 | System.out | Test.java:67:29:67:42 | "It's Const30" |
183+
| Test.java:67:10:67:43 | println(...) | Test.java:68:10:68:15 | break |
184+
| Test.java:67:10:67:44 | <Expr>; | Test.java:67:10:67:19 | System.out |
185+
| Test.java:67:29:67:42 | "It's Const30" | Test.java:67:10:67:43 | println(...) |
186+
| Test.java:68:10:68:15 | break | Test.java:73:6:73:18 | switch (...) |
187+
| Test.java:69:8:69:26 | case null, default | Test.java:70:10:70:60 | <Expr>; |
188+
| Test.java:70:10:70:19 | System.out | Test.java:70:29:70:58 | "It's null, or something else" |
189+
| Test.java:70:10:70:59 | println(...) | Test.java:73:6:73:18 | switch (...) |
190+
| Test.java:70:10:70:60 | <Expr>; | Test.java:70:10:70:19 | System.out |
191+
| Test.java:70:29:70:58 | "It's null, or something else" | Test.java:70:10:70:59 | println(...) |
192+
| Test.java:73:6:73:18 | switch (...) | Test.java:73:13:73:17 | thing |
193+
| Test.java:73:13:73:17 | thing | Test.java:74:8:74:21 | case T t ... |
194+
| Test.java:73:13:73:17 | thing | Test.java:77:8:77:17 | case ... |
195+
| Test.java:74:8:74:21 | case T t ... | Test.java:74:20:74:20 | s |
196+
| Test.java:74:8:74:21 | case T t ... | Test.java:80:8:80:22 | case T t ... |
197+
| Test.java:74:20:74:20 | s | Test.java:75:10:75:31 | <Expr>; |
198+
| Test.java:75:10:75:19 | System.out | Test.java:75:29:75:29 | s |
199+
| Test.java:75:10:75:30 | println(...) | Test.java:76:10:76:15 | break |
200+
| Test.java:75:10:75:31 | <Expr>; | Test.java:75:10:75:19 | System.out |
201+
| Test.java:75:29:75:29 | s | Test.java:75:10:75:30 | println(...) |
202+
| Test.java:76:10:76:15 | break | Test.java:3:22:3:25 | test |
203+
| Test.java:77:8:77:17 | case ... | Test.java:78:10:78:41 | <Expr>; |
204+
| Test.java:78:10:78:19 | System.out | Test.java:78:29:78:39 | "It's null" |
205+
| Test.java:78:10:78:40 | println(...) | Test.java:79:10:79:15 | break |
206+
| Test.java:78:10:78:41 | <Expr>; | Test.java:78:10:78:19 | System.out |
207+
| Test.java:78:29:78:39 | "It's null" | Test.java:78:10:78:40 | println(...) |
208+
| Test.java:79:10:79:15 | break | Test.java:3:22:3:25 | test |
209+
| Test.java:80:8:80:22 | case T t ... | Test.java:80:21:80:21 | i |
210+
| Test.java:80:8:80:22 | case T t ... | Test.java:83:8:83:15 | default |
211+
| Test.java:80:21:80:21 | i | Test.java:81:10:81:47 | <Expr>; |
212+
| Test.java:81:10:81:19 | System.out | Test.java:81:29:81:41 | "An integer:" |
213+
| Test.java:81:10:81:46 | println(...) | Test.java:82:10:82:15 | break |
214+
| Test.java:81:10:81:47 | <Expr>; | Test.java:81:10:81:19 | System.out |
215+
| Test.java:81:29:81:41 | "An integer:" | Test.java:81:45:81:45 | i |
216+
| Test.java:81:29:81:45 | ... + ... | Test.java:81:10:81:46 | println(...) |
217+
| Test.java:81:45:81:45 | i | Test.java:81:29:81:45 | ... + ... |
218+
| Test.java:82:10:82:15 | break | Test.java:3:22:3:25 | test |
219+
| Test.java:83:8:83:15 | default | Test.java:84:10:84:15 | break |
220+
| Test.java:84:10:84:15 | break | Test.java:3:22:3:25 | test |

0 commit comments

Comments
 (0)