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

Skip to content

Commit f4b45fa

Browse files
committed
Support switch cases with binding patterns
1 parent fefc02d commit f4b45fa

8 files changed

Lines changed: 279 additions & 52 deletions

File tree

java/ql/consistency-queries/children.ql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ predicate gapInChildren(Element e, int i) {
4646
// value should be, because kotlinc doesn't load annotation defaults and we
4747
// want to leave a space for another extractor to fill in the default if it
4848
// is able.
49-
not e instanceof Annotation
49+
not e instanceof Annotation and
50+
// Pattern case statements legitimately have a TypeAccess (-2) and a pattern (0) but not a rule (-1)
51+
not (i = -1 and e instanceof PatternCase and not e.(PatternCase).isRule())
5052
}
5153

5254
predicate lateFirstChild(Element e, int i) {

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

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,15 @@ private module ControlFlowGraphImpl {
434434
)
435435
}
436436

437+
/**
438+
* Gets a SwitchCase's successor SwitchCase, if any.
439+
*/
440+
private predicate nextSwitchCase(SwitchCase pred, SwitchCase succ) {
441+
exists(SwitchExpr se, int idx | se.getCase(idx) = pred and se.getCase(idx + 1) = succ)
442+
or
443+
exists(SwitchStmt ss, int idx | ss.getCase(idx) = pred and ss.getCase(idx + 1) = succ)
444+
}
445+
437446
/**
438447
* Expressions and statements with CFG edges in post-order AST traversal.
439448
*
@@ -467,7 +476,8 @@ private module ControlFlowGraphImpl {
467476
this instanceof NotInstanceOfExpr
468477
or
469478
this instanceof LocalVariableDeclExpr and
470-
not this = any(InstanceOfExpr ioe).getLocalVariableDeclExpr()
479+
not this = any(InstanceOfExpr ioe).getLocalVariableDeclExpr() and
480+
not this = any(PatternCase pc).getDecl()
471481
or
472482
this instanceof StringTemplateExpr
473483
or
@@ -493,7 +503,9 @@ private module ControlFlowGraphImpl {
493503
or
494504
this.(BlockStmt).getNumStmt() = 0
495505
or
496-
this instanceof SwitchCase and not this.(SwitchCase).isRule()
506+
this instanceof SwitchCase and
507+
not this.(SwitchCase).isRule() and
508+
not this instanceof PatternCase
497509
or
498510
this instanceof EmptyStmt
499511
or
@@ -887,6 +899,14 @@ private module ControlFlowGraphImpl {
887899
else completion = caseCompletion
888900
)
889901
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.
907+
last = n.(PatternCase).getDecl() and
908+
completion = NormalCompletion()
909+
or
890910
// the last statement of a synchronized statement is the last statement of its body
891911
last(n.(SynchronizedStmt).getBlock(), last, completion)
892912
or
@@ -1201,8 +1221,14 @@ private module ControlFlowGraphImpl {
12011221
// From the entry point control is transferred first to the expression...
12021222
n = switch and result = first(switch.getExpr())
12031223
or
1204-
// ...and then to one of the cases.
1205-
last(switch.getExpr(), n, completion) and result = first(switch.getACase())
1224+
// ...and then for a vanilla switch to any case, or for a pattern switch to the first one.
1225+
exists(SwitchCase firstExecutedCase |
1226+
if switch.getACase() instanceof PatternCase
1227+
then firstExecutedCase = switch.getCase(0)
1228+
else firstExecutedCase = switch.getACase()
1229+
|
1230+
last(switch.getExpr(), n, completion) and result = first(firstExecutedCase)
1231+
)
12061232
or
12071233
// Statements within a switch body execute sequentially.
12081234
exists(int i |
@@ -1216,19 +1242,43 @@ private module ControlFlowGraphImpl {
12161242
n = switch and result = first(switch.getExpr())
12171243
or
12181244
// ...and then to one of the cases.
1219-
last(switch.getExpr(), n, completion) and result = first(switch.getACase())
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+
)
12201252
or
12211253
// Statements within a switch body execute sequentially.
12221254
exists(int i |
12231255
last(switch.getStmt(i), n, completion) and result = first(switch.getStmt(i + 1))
12241256
)
12251257
)
12261258
or
1259+
// Edge from rule SwitchCases to their body, after any variable assignment if applicable.
12271260
// No edges in a non-rule SwitchCase - the constant expression in a ConstCase isn't included in the CFG.
1228-
exists(SwitchCase case | completion = NormalCompletion() |
1229-
n = case and result = first(case.getRuleExpression())
1261+
exists(SwitchCase case, ControlFlowNode preBodyNode |
1262+
completion = NormalCompletion() and
1263+
if case instanceof PatternCase
1264+
then preBodyNode = case.(PatternCase).getDecl()
1265+
else preBodyNode = case
1266+
|
1267+
n = preBodyNode and result = first(case.getRuleExpression())
12301268
or
1231-
n = case and result = first(case.getRuleStatement())
1269+
n = preBodyNode and result = first(case.getRuleStatement())
1270+
)
1271+
or
1272+
// A pattern case conducts a type test, then branches to either the next case or the assignment.
1273+
exists(PatternCase case |
1274+
n = case and
1275+
(
1276+
completion = basicBooleanCompletion(false) and
1277+
nextSwitchCase(case, result)
1278+
or
1279+
completion = basicBooleanCompletion(true) and
1280+
result = case.getDecl()
1281+
)
12321282
)
12331283
or
12341284
// Yield

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

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1509,17 +1509,28 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
15091509
*/
15101510
Stmt getStmt(int index) { result = this.getAStmt() and result.getIndex() = index }
15111511

1512+
/**
1513+
* Gets the `i`th case of this `switch` expression,
1514+
* which may be either a normal `case` or a `default`.
1515+
*/
1516+
SwitchCase getCase(int i) {
1517+
result = rank[i](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
1518+
}
1519+
15121520
/**
15131521
* Gets a case of this `switch` expression,
15141522
* which may be either a normal `case` or a `default`.
15151523
*/
1516-
SwitchCase getACase() { result = this.getAConstCase() or result = this.getDefaultCase() }
1524+
SwitchCase getACase() { result.getParent() = this }
15171525

15181526
/** Gets a (non-default) `case` of this `switch` expression. */
1519-
ConstCase getAConstCase() { result.getParent() = this }
1527+
ConstCase getAConstCase() { result = this.getACase() }
1528+
1529+
/** Gets a (non-default) pattern `case` of this `switch` expression. */
1530+
PatternCase getAPatternCase() { result = this.getACase() }
15201531

15211532
/** Gets the `default` case of this switch expression, if any. */
1522-
DefaultCase getDefaultCase() { result.getParent() = this }
1533+
DefaultCase getDefaultCase() { result = this.getACase() }
15231534

15241535
/** Gets the expression of this `switch` expression. */
15251536
Expr getExpr() { result.getParent() = this }
@@ -1592,7 +1603,9 @@ class NotInstanceOfExpr extends Expr, @notinstanceofexpr {
15921603
* A local variable declaration expression.
15931604
*
15941605
* Contexts in which such expressions may occur include
1595-
* local variable declaration statements and `for` loops.
1606+
* local variable declaration statements, `for` loops,
1607+
* and binding patterns such as `if (x instanceof T t)` and
1608+
* `case String s:`.
15961609
*/
15971610
class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
15981611
/** Gets an access to the variable declared by this local variable declaration expression. */
@@ -1612,18 +1625,33 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
16121625
exists(EnhancedForStmt efs | efs.getVariable() = this | result.isNthChildOf(efs, -1))
16131626
or
16141627
exists(InstanceOfExpr ioe | this.getParent() = ioe | result.isNthChildOf(ioe, 1))
1628+
or
1629+
exists(PatternCase pc | this.getParent() = pc | result.isNthChildOf(pc, -2))
16151630
}
16161631

16171632
/** Gets the name of the variable declared by this local variable declaration expression. */
16181633
string getName() { result = this.getVariable().getName() }
16191634

1635+
/** Gets the switch statement or expression whose pattern declares this identifier, if any. */
1636+
StmtParent getAssociatedSwitch() {
1637+
result = this.getParent().(PatternCase).getParent()
1638+
}
1639+
1640+
/** Holds if this is a declaration stemming from a pattern switch case. */
1641+
predicate hasAssociatedSwitch() {
1642+
exists(this.getAssociatedSwitch())
1643+
}
1644+
16201645
/** Gets the initializer expression of this local variable declaration expression, if any. */
1621-
Expr getInit() { result.isNthChildOf(this, 0) }
1646+
Expr getInit() {
1647+
result.isNthChildOf(this, 0)
1648+
}
16221649

16231650
/** Holds if this variable declaration implicitly initializes the variable. */
16241651
predicate hasImplicitInit() {
16251652
exists(CatchClause cc | cc.getVariable() = this) or
1626-
exists(EnhancedForStmt efs | efs.getVariable() = this)
1653+
exists(EnhancedForStmt efs | efs.getVariable() = this) or
1654+
this.hasAssociatedSwitch()
16271655
}
16281656

16291657
/** Gets a printable representation of this expression. */

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

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -382,17 +382,28 @@ class SwitchStmt extends Stmt, @switchstmt {
382382
*/
383383
Stmt getStmt(int index) { result = this.getAStmt() and result.getIndex() = index }
384384

385+
/**
386+
* Gets the `i`th case of this `switch` statement,
387+
* which may be either a normal `case` or a `default`.
388+
*/
389+
SwitchCase getCase(int i) {
390+
result = rank[i](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
391+
}
392+
385393
/**
386394
* Gets a case of this `switch` statement,
387395
* which may be either a normal `case` or a `default`.
388396
*/
389-
SwitchCase getACase() { result = this.getAConstCase() or result = this.getDefaultCase() }
397+
SwitchCase getACase() { result.getParent() = this }
398+
399+
/** Gets a (non-default) constant `case` of this `switch` statement. */
400+
ConstCase getAConstCase() { result = this.getACase() }
390401

391-
/** Gets a (non-default) `case` of this `switch` statement. */
392-
ConstCase getAConstCase() { result.getParent() = this }
402+
/** Gets a (non-default) pattern `case` of this `switch` statement. */
403+
PatternCase getAPatternCase() { result = this.getACase() }
393404

394405
/** Gets the `default` case of this switch statement, if any. */
395-
DefaultCase getDefaultCase() { result.getParent() = this }
406+
DefaultCase getDefaultCase() { result = this.getACase() }
396407

397408
/** Gets the expression of this `switch` statement. */
398409
Expr getExpr() { result.getParent() = this }
@@ -464,15 +475,15 @@ class SwitchCase extends Stmt, @case {
464475

465476
/** A constant `case` of a switch statement. */
466477
class ConstCase extends SwitchCase {
467-
ConstCase() { exists(Expr e | e.getParent() = this | e.getIndex() >= 0) }
478+
ConstCase() { exists(Literal e | e.getParent() = this and e.getIndex() >= 0) }
468479

469480
/** Gets the `case` constant at index 0. */
470-
Expr getValue() { result.getParent() = this and result.getIndex() = 0 }
481+
Expr getValue() { result.isNthChildOf(this, 0) }
471482

472483
/**
473484
* Gets the `case` constant at the specified index.
474485
*/
475-
Expr getValue(int i) { result.getParent() = this and result.getIndex() = i and i >= 0 }
486+
Expr getValue(int i) { result.isNthChildOf(this, i) and i >= 0 }
476487

477488
override string pp() { result = "case ..." }
478489

@@ -483,6 +494,24 @@ class ConstCase extends SwitchCase {
483494
override string getAPrimaryQlClass() { result = "ConstCase" }
484495
}
485496

497+
/** A pattern case of a `switch` statement */
498+
class PatternCase extends SwitchCase {
499+
LocalVariableDeclExpr patternVar;
500+
501+
PatternCase() { patternVar.isNthChildOf(this, 0) }
502+
503+
/** Gets the variable declared by this pattern case. */
504+
LocalVariableDeclExpr getDecl() { result.isNthChildOf(this, 0) }
505+
506+
override string pp() { result = "case T t ..." }
507+
508+
override string toString() { result = "case T t ..." }
509+
510+
override string getHalsteadID() { result = "PatternCase" }
511+
512+
override string getAPrimaryQlClass() { result = "PatternCase" }
513+
}
514+
486515
/** A `default` case of a `switch` statement */
487516
class DefaultCase extends SwitchCase {
488517
DefaultCase() { not exists(Expr e | e.getParent() = this | e.getIndex() >= 0) }

java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ private predicate step(Node n1, Node n2) {
8080
for.getVariable() = def.(BaseSsaUpdate).getDefiningExpr() and
8181
for.getExpr() = n1.asExpr()
8282
)
83+
or
84+
exists(PatternCase pc |
85+
pc.getDecl() = def.(BaseSsaUpdate).getDefiningExpr() and
86+
(
87+
pc.getSwitch().getExpr() = n1.asExpr()
88+
or
89+
pc.getSwitchExpr().getExpr() = n1.asExpr()
90+
)
91+
)
8392
|
8493
v.getAnUltimateDefinition() = def and
8594
v.getAUse() = n2.asExpr()

java/ql/test/library-tests/printAst/A.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,34 @@ void varDecls(Object[] things) {
5050
if (thing instanceof String s) {
5151
throw new RuntimeException(s);
5252
}
53+
switch (thing) {
54+
case String s -> System.out.println(s);
55+
case Integer i -> System.out.println("An integer: " + i);
56+
default -> { }
57+
}
58+
switch (thing) {
59+
case String s:
60+
System.out.println(s);
61+
break;
62+
case Integer i:
63+
System.out.println("An integer:" + i);
64+
break;
65+
default:
66+
break;
67+
}
68+
var thingAsString = switch(thing) {
69+
case String s -> s;
70+
case Integer i -> "An integer: " + i;
71+
default -> "Something else";
72+
};
73+
var thingAsString2 = switch(thing) {
74+
case String s:
75+
yield s;
76+
case Integer i:
77+
yield "An integer: " + i;
78+
default:
79+
yield "Something else";
80+
};
5381
}
5482
}
5583
catch (RuntimeException rte) {
@@ -70,4 +98,4 @@ enum E {
7098
* Javadoc for fields
7199
*/
72100
int i, j, k;
73-
}
101+
}

0 commit comments

Comments
 (0)