Support switch cases with binding patterns

This commit is contained in:
Chris Smowton
2023-10-18 18:40:47 +01:00
parent fefc02d650
commit f4b45fa511
8 changed files with 279 additions and 52 deletions

View File

@@ -434,6 +434,15 @@ private module ControlFlowGraphImpl {
)
}
/**
* Gets a SwitchCase's successor SwitchCase, if any.
*/
private predicate nextSwitchCase(SwitchCase pred, SwitchCase succ) {
exists(SwitchExpr se, int idx | se.getCase(idx) = pred and se.getCase(idx + 1) = succ)
or
exists(SwitchStmt ss, int idx | ss.getCase(idx) = pred and ss.getCase(idx + 1) = succ)
}
/**
* Expressions and statements with CFG edges in post-order AST traversal.
*
@@ -467,7 +476,8 @@ private module ControlFlowGraphImpl {
this instanceof NotInstanceOfExpr
or
this instanceof LocalVariableDeclExpr and
not this = any(InstanceOfExpr ioe).getLocalVariableDeclExpr()
not this = any(InstanceOfExpr ioe).getLocalVariableDeclExpr() and
not this = any(PatternCase pc).getDecl()
or
this instanceof StringTemplateExpr
or
@@ -493,7 +503,9 @@ private module ControlFlowGraphImpl {
or
this.(BlockStmt).getNumStmt() = 0
or
this instanceof SwitchCase and not this.(SwitchCase).isRule()
this instanceof SwitchCase and
not this.(SwitchCase).isRule() and
not this instanceof PatternCase
or
this instanceof EmptyStmt
or
@@ -887,6 +899,14 @@ private module ControlFlowGraphImpl {
else completion = caseCompletion
)
or
// The last node in a case could always be a failing pattern check.
last = n.(PatternCase) and
completion = basicBooleanCompletion(false)
or
// The last node in a non-rule case is its variable declaration.
last = n.(PatternCase).getDecl() and
completion = NormalCompletion()
or
// the last statement of a synchronized statement is the last statement of its body
last(n.(SynchronizedStmt).getBlock(), last, completion)
or
@@ -1201,8 +1221,14 @@ private module ControlFlowGraphImpl {
// From the entry point control is transferred first to the expression...
n = switch and result = first(switch.getExpr())
or
// ...and then to one of the cases.
last(switch.getExpr(), n, completion) and result = first(switch.getACase())
// ...and then for a vanilla switch to any case, or for a pattern switch to the first one.
exists(SwitchCase firstExecutedCase |
if switch.getACase() instanceof PatternCase
then firstExecutedCase = switch.getCase(0)
else firstExecutedCase = switch.getACase()
|
last(switch.getExpr(), n, completion) and result = first(firstExecutedCase)
)
or
// Statements within a switch body execute sequentially.
exists(int i |
@@ -1216,7 +1242,13 @@ private module ControlFlowGraphImpl {
n = switch and result = first(switch.getExpr())
or
// ...and then to one of the cases.
last(switch.getExpr(), n, completion) and result = first(switch.getACase())
exists(SwitchCase firstExecutedCase |
if switch.getACase() instanceof PatternCase
then firstExecutedCase = switch.getCase(0)
else firstExecutedCase = switch.getACase()
|
last(switch.getExpr(), n, completion) and result = first(firstExecutedCase)
)
or
// Statements within a switch body execute sequentially.
exists(int i |
@@ -1224,11 +1256,29 @@ private module ControlFlowGraphImpl {
)
)
or
// Edge from rule SwitchCases to their body, after any variable assignment if applicable.
// No edges in a non-rule SwitchCase - the constant expression in a ConstCase isn't included in the CFG.
exists(SwitchCase case | completion = NormalCompletion() |
n = case and result = first(case.getRuleExpression())
exists(SwitchCase case, ControlFlowNode preBodyNode |
completion = NormalCompletion() and
if case instanceof PatternCase
then preBodyNode = case.(PatternCase).getDecl()
else preBodyNode = case
|
n = preBodyNode and result = first(case.getRuleExpression())
or
n = case and result = first(case.getRuleStatement())
n = preBodyNode and result = first(case.getRuleStatement())
)
or
// A pattern case conducts a type test, then branches to either the next case or the assignment.
exists(PatternCase case |
n = case and
(
completion = basicBooleanCompletion(false) and
nextSwitchCase(case, result)
or
completion = basicBooleanCompletion(true) and
result = case.getDecl()
)
)
or
// Yield

View File

@@ -1509,17 +1509,28 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
*/
Stmt getStmt(int index) { result = this.getAStmt() and result.getIndex() = index }
/**
* Gets the `i`th case of this `switch` expression,
* which may be either a normal `case` or a `default`.
*/
SwitchCase getCase(int i) {
result = rank[i](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
}
/**
* Gets a case of this `switch` expression,
* which may be either a normal `case` or a `default`.
*/
SwitchCase getACase() { result = this.getAConstCase() or result = this.getDefaultCase() }
SwitchCase getACase() { result.getParent() = this }
/** Gets a (non-default) `case` of this `switch` expression. */
ConstCase getAConstCase() { result.getParent() = this }
ConstCase getAConstCase() { result = this.getACase() }
/** Gets a (non-default) pattern `case` of this `switch` expression. */
PatternCase getAPatternCase() { result = this.getACase() }
/** Gets the `default` case of this switch expression, if any. */
DefaultCase getDefaultCase() { result.getParent() = this }
DefaultCase getDefaultCase() { result = this.getACase() }
/** Gets the expression of this `switch` expression. */
Expr getExpr() { result.getParent() = this }
@@ -1592,7 +1603,9 @@ class NotInstanceOfExpr extends Expr, @notinstanceofexpr {
* A local variable declaration expression.
*
* Contexts in which such expressions may occur include
* local variable declaration statements and `for` loops.
* local variable declaration statements, `for` loops,
* and binding patterns such as `if (x instanceof T t)` and
* `case String s:`.
*/
class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
/** Gets an access to the variable declared by this local variable declaration expression. */
@@ -1612,18 +1625,33 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
exists(EnhancedForStmt efs | efs.getVariable() = this | result.isNthChildOf(efs, -1))
or
exists(InstanceOfExpr ioe | this.getParent() = ioe | result.isNthChildOf(ioe, 1))
or
exists(PatternCase pc | this.getParent() = pc | result.isNthChildOf(pc, -2))
}
/** Gets the name of the variable declared by this local variable declaration expression. */
string getName() { result = this.getVariable().getName() }
/** Gets the switch statement or expression whose pattern declares this identifier, if any. */
StmtParent getAssociatedSwitch() {
result = this.getParent().(PatternCase).getParent()
}
/** Holds if this is a declaration stemming from a pattern switch case. */
predicate hasAssociatedSwitch() {
exists(this.getAssociatedSwitch())
}
/** Gets the initializer expression of this local variable declaration expression, if any. */
Expr getInit() { result.isNthChildOf(this, 0) }
Expr getInit() {
result.isNthChildOf(this, 0)
}
/** Holds if this variable declaration implicitly initializes the variable. */
predicate hasImplicitInit() {
exists(CatchClause cc | cc.getVariable() = this) or
exists(EnhancedForStmt efs | efs.getVariable() = this)
exists(EnhancedForStmt efs | efs.getVariable() = this) or
this.hasAssociatedSwitch()
}
/** Gets a printable representation of this expression. */

View File

@@ -382,17 +382,28 @@ class SwitchStmt extends Stmt, @switchstmt {
*/
Stmt getStmt(int index) { result = this.getAStmt() and result.getIndex() = index }
/**
* Gets the `i`th case of this `switch` statement,
* which may be either a normal `case` or a `default`.
*/
SwitchCase getCase(int i) {
result = rank[i](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
}
/**
* Gets a case of this `switch` statement,
* which may be either a normal `case` or a `default`.
*/
SwitchCase getACase() { result = this.getAConstCase() or result = this.getDefaultCase() }
SwitchCase getACase() { result.getParent() = this }
/** Gets a (non-default) `case` of this `switch` statement. */
ConstCase getAConstCase() { result.getParent() = this }
/** Gets a (non-default) constant `case` of this `switch` statement. */
ConstCase getAConstCase() { result = this.getACase() }
/** Gets a (non-default) pattern `case` of this `switch` statement. */
PatternCase getAPatternCase() { result = this.getACase() }
/** Gets the `default` case of this switch statement, if any. */
DefaultCase getDefaultCase() { result.getParent() = this }
DefaultCase getDefaultCase() { result = this.getACase() }
/** Gets the expression of this `switch` statement. */
Expr getExpr() { result.getParent() = this }
@@ -464,15 +475,15 @@ class SwitchCase extends Stmt, @case {
/** A constant `case` of a switch statement. */
class ConstCase extends SwitchCase {
ConstCase() { exists(Expr e | e.getParent() = this | e.getIndex() >= 0) }
ConstCase() { exists(Literal e | e.getParent() = this and e.getIndex() >= 0) }
/** Gets the `case` constant at index 0. */
Expr getValue() { result.getParent() = this and result.getIndex() = 0 }
Expr getValue() { result.isNthChildOf(this, 0) }
/**
* Gets the `case` constant at the specified index.
*/
Expr getValue(int i) { result.getParent() = this and result.getIndex() = i and i >= 0 }
Expr getValue(int i) { result.isNthChildOf(this, i) and i >= 0 }
override string pp() { result = "case ..." }
@@ -483,6 +494,24 @@ class ConstCase extends SwitchCase {
override string getAPrimaryQlClass() { result = "ConstCase" }
}
/** A pattern case of a `switch` statement */
class PatternCase extends SwitchCase {
LocalVariableDeclExpr patternVar;
PatternCase() { patternVar.isNthChildOf(this, 0) }
/** Gets the variable declared by this pattern case. */
LocalVariableDeclExpr getDecl() { result.isNthChildOf(this, 0) }
override string pp() { result = "case T t ..." }
override string toString() { result = "case T t ..." }
override string getHalsteadID() { result = "PatternCase" }
override string getAPrimaryQlClass() { result = "PatternCase" }
}
/** A `default` case of a `switch` statement */
class DefaultCase extends SwitchCase {
DefaultCase() { not exists(Expr e | e.getParent() = this | e.getIndex() >= 0) }

View File

@@ -80,6 +80,15 @@ private predicate step(Node n1, Node n2) {
for.getVariable() = def.(BaseSsaUpdate).getDefiningExpr() and
for.getExpr() = n1.asExpr()
)
or
exists(PatternCase pc |
pc.getDecl() = def.(BaseSsaUpdate).getDefiningExpr() and
(
pc.getSwitch().getExpr() = n1.asExpr()
or
pc.getSwitchExpr().getExpr() = n1.asExpr()
)
)
|
v.getAnUltimateDefinition() = def and
v.getAUse() = n2.asExpr()