Handle 'case null, default:'

This commit is contained in:
Chris Smowton
2023-10-31 14:53:49 +00:00
parent 9a450b09be
commit 54a89d6fef
12 changed files with 131 additions and 51 deletions

View File

@@ -1514,7 +1514,8 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
* which may be either a normal `case` or a `default`.
*/
SwitchCase getCase(int i) {
result = rank[i + 1](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
result =
rank[i + 1](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
}
/**
@@ -1532,6 +1533,9 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
/** Gets the `default` case of this switch expression, if any. */
DefaultCase getDefaultCase() { result = this.getACase() }
/** Gets the `default` or `case null, default` case of this switch statement, if any. */
SwitchCase getDefaultOrNullDefaultCase() { result = this.getACase() and result.hasDefaultLabel() }
/** Gets the expression of this `switch` expression. */
Expr getExpr() { result.getParent() = this }
@@ -1543,9 +1547,7 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
}
/** Holds if this switch has a case handling a null literal. */
predicate hasNullCase() {
this.getAConstCase().getValue(_) instanceof NullLiteral
}
predicate hasNullCase() { this.getAConstCase().getValue(_) instanceof NullLiteral }
/** Gets a printable representation of this expression. */
override string toString() { result = "switch (...)" }
@@ -1638,19 +1640,13 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
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()
}
StmtParent getAssociatedSwitch() { result = this.getParent().(PatternCase).getParent() }
/** Holds if this is a declaration stemming from a pattern switch case. */
predicate hasAssociatedSwitch() {
exists(this.getAssociatedSwitch())
}
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() {

View File

@@ -746,7 +746,9 @@ private class PpSwitchCase extends PpAst, SwitchCase {
override string getPart(int i) {
i = 0 and result = "default" and this instanceof DefaultCase
or
i = 0 and result = "case " and this instanceof ConstCase
i = 0 and result = "case " and not this instanceof DefaultCase
or
i = this.lastConstCaseValueIndex() and result = "default" and this instanceof NullDefaultCase
or
exists(int j | i = 2 * j and j != 0 and result = ", " and exists(this.(ConstCase).getValue(j)))
or
@@ -757,8 +759,13 @@ private class PpSwitchCase extends PpAst, SwitchCase {
i = 3 + this.lastConstCaseValueIndex() and result = ";" and exists(this.getRuleExpression())
}
private int getCaseDefaultOffset() {
if this instanceof NullDefaultCase then result = 1 else result = 0
}
private int lastConstCaseValueIndex() {
result = 1 + 2 * max(int j | j = 0 or exists(this.(ConstCase).getValue(j)))
result =
1 + 2 * (max(int j | j = 0 or exists(this.(ConstCase).getValue(j))) + this.getCaseDefaultOffset())
}
override PpAst getChild(int i) {

View File

@@ -406,6 +406,9 @@ class SwitchStmt extends Stmt, @switchstmt {
/** Gets the `default` case of this switch statement, if any. */
DefaultCase getDefaultCase() { result = this.getACase() }
/** Gets the `default` or `case null, default` case of this switch statement, if any. */
SwitchCase getDefaultOrNullDefaultCase() { result = this.getACase() and result.hasDefaultLabel() }
/** Gets the expression of this `switch` statement. */
Expr getExpr() { result.getParent() = this }
@@ -487,14 +490,28 @@ class SwitchCase extends Stmt, @case {
Stmt getRuleStatementOrExpressionStatement() {
result.getParent() = this and result.getIndex() = -1
}
/**
* Holds if this case statement includes the default label, i.e. it is either `default`
* or `case null, default`.
*/
predicate hasDefaultLabel() { this instanceof DefaultCase or this instanceof NullDefaultCase }
}
/** A constant `case` of a switch statement. */
/**
* A constant `case` of a switch statement.
*
* Note this excludes `case null, default` even though that includes a null constant. It
* does however include plain `case null`.
*/
class ConstCase extends SwitchCase {
ConstCase() {
exists(Expr e |
e.getParent() = this and e.getIndex() >= 0 and not e instanceof LocalVariableDeclExpr
)
// For backward compatibility, we don't include `case null, default:` here, on the assumption
// this will come as a surprise to CodeQL that predates that statement's validity.
and not isNullDefaultCase(this)
}
/** Gets the `case` constant at index 0. */
@@ -535,7 +552,11 @@ class PatternCase extends SwitchCase {
override string getAPrimaryQlClass() { result = "PatternCase" }
}
/** A `default` case of a `switch` statement */
/**
* A `default` case of a `switch` statement.
*
* Note this does not include `case null, default` -- for that, see `NullDefaultCase`.
*/
class DefaultCase extends SwitchCase {
DefaultCase() { not exists(Expr e | e.getParent() = this | e.getIndex() >= 0) }
@@ -548,6 +569,19 @@ class DefaultCase extends SwitchCase {
override string getAPrimaryQlClass() { result = "DefaultCase" }
}
/** A `case null, default` statement of a `switch` statement or expression. */
class NullDefaultCase extends SwitchCase {
NullDefaultCase() { isNullDefaultCase(this) }
override string pp() { result = "case null, default" }
override string toString() { result = "case null, default" }
override string getHalsteadID() { result = "NullDefaultCase" }
override string getAPrimaryQlClass() { result = "NullDefaultCase" }
}
/** A `synchronized` statement. */
class SynchronizedStmt extends Stmt, @synchronizedstmt {
/** Gets the expression on which this `synchronized` statement synchronizes. */

View File

@@ -170,10 +170,11 @@ class ConstSwitchStmt extends SwitchStmt {
/** Gets the matching case, if it can be deduced. */
SwitchCase getMatchingCase() {
// Must be a value we can deduce
// TODO: handle other known constants (enum constants, String constants)
exists(this.getExpr().(ConstantExpr).getIntValue()) and
if exists(this.getMatchingConstCase())
then result = this.getMatchingConstCase()
else result = this.getDefaultCase()
else result = this.getDefaultOrNullDefaultCase()
}
/**

View File

@@ -55,9 +55,13 @@ predicate implies_v1(Guard g1, boolean b1, Guard g2, boolean b2) {
)
)
or
g1.(DefaultCase).getSwitch().getAConstCase() = g2 and b1 = true and b2 = false
exists(SwitchCase sc | g1 = sc and sc.hasDefaultLabel() |
sc.getSwitch().getAConstCase() = g2 and b1 = true and b2 = false
)
or
g1.(DefaultCase).getSwitchExpr().getAConstCase() = g2 and b1 = true and b2 = false
exists(SwitchCase sc | g1 = sc and sc.hasDefaultLabel() |
sc.getSwitchExpr().getAConstCase() = g2 and b1 = true and b2 = false
)
or
exists(MethodCall check, int argIndex | check = g1 |
conditionCheckArgument(check, argIndex, _) and