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

Skip to content

Commit 54a89d6

Browse files
committed
Handle 'case null, default:'
1 parent 9a450b0 commit 54a89d6

12 files changed

Lines changed: 131 additions & 51 deletions

File tree

java/ql/examples/snippets/switchcase.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ where
1414
switch.getExpr().getType() = enum and
1515
missing.getDeclaringType() = enum and
1616
not switch.getAConstCase().getValue() = missing.getAnAccess() and
17-
not exists(switch.getDefaultCase())
17+
not exists(switch.getDefaultOrNullDefaultCase())
1818
select switch

java/ql/lib/config/semmlecode.dbscheme

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,10 @@ providesWith(
992992
string serviceImpl: string ref
993993
);
994994

995+
isNullDefaultCase(
996+
int id: @case ref
997+
);
998+
995999
/*
9961000
* Javadoc
9971001
*/

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

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,7 +1514,8 @@ 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 + 1](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
1517+
result =
1518+
rank[i + 1](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
15181519
}
15191520

15201521
/**
@@ -1532,6 +1533,9 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
15321533
/** Gets the `default` case of this switch expression, if any. */
15331534
DefaultCase getDefaultCase() { result = this.getACase() }
15341535

1536+
/** Gets the `default` or `case null, default` case of this switch statement, if any. */
1537+
SwitchCase getDefaultOrNullDefaultCase() { result = this.getACase() and result.hasDefaultLabel() }
1538+
15351539
/** Gets the expression of this `switch` expression. */
15361540
Expr getExpr() { result.getParent() = this }
15371541

@@ -1543,9 +1547,7 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
15431547
}
15441548

15451549
/** Holds if this switch has a case handling a null literal. */
1546-
predicate hasNullCase() {
1547-
this.getAConstCase().getValue(_) instanceof NullLiteral
1548-
}
1550+
predicate hasNullCase() { this.getAConstCase().getValue(_) instanceof NullLiteral }
15491551

15501552
/** Gets a printable representation of this expression. */
15511553
override string toString() { result = "switch (...)" }
@@ -1638,19 +1640,13 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
16381640
string getName() { result = this.getVariable().getName() }
16391641

16401642
/** Gets the switch statement or expression whose pattern declares this identifier, if any. */
1641-
StmtParent getAssociatedSwitch() {
1642-
result = this.getParent().(PatternCase).getParent()
1643-
}
1643+
StmtParent getAssociatedSwitch() { result = this.getParent().(PatternCase).getParent() }
16441644

16451645
/** Holds if this is a declaration stemming from a pattern switch case. */
1646-
predicate hasAssociatedSwitch() {
1647-
exists(this.getAssociatedSwitch())
1648-
}
1646+
predicate hasAssociatedSwitch() { exists(this.getAssociatedSwitch()) }
16491647

16501648
/** Gets the initializer expression of this local variable declaration expression, if any. */
1651-
Expr getInit() {
1652-
result.isNthChildOf(this, 0)
1653-
}
1649+
Expr getInit() { result.isNthChildOf(this, 0) }
16541650

16551651
/** Holds if this variable declaration implicitly initializes the variable. */
16561652
predicate hasImplicitInit() {

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,9 @@ private class PpSwitchCase extends PpAst, SwitchCase {
746746
override string getPart(int i) {
747747
i = 0 and result = "default" and this instanceof DefaultCase
748748
or
749-
i = 0 and result = "case " and this instanceof ConstCase
749+
i = 0 and result = "case " and not this instanceof DefaultCase
750+
or
751+
i = this.lastConstCaseValueIndex() and result = "default" and this instanceof NullDefaultCase
750752
or
751753
exists(int j | i = 2 * j and j != 0 and result = ", " and exists(this.(ConstCase).getValue(j)))
752754
or
@@ -757,8 +759,13 @@ private class PpSwitchCase extends PpAst, SwitchCase {
757759
i = 3 + this.lastConstCaseValueIndex() and result = ";" and exists(this.getRuleExpression())
758760
}
759761

762+
private int getCaseDefaultOffset() {
763+
if this instanceof NullDefaultCase then result = 1 else result = 0
764+
}
765+
760766
private int lastConstCaseValueIndex() {
761-
result = 1 + 2 * max(int j | j = 0 or exists(this.(ConstCase).getValue(j)))
767+
result =
768+
1 + 2 * (max(int j | j = 0 or exists(this.(ConstCase).getValue(j))) + this.getCaseDefaultOffset())
762769
}
763770

764771
override PpAst getChild(int i) {

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

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,9 @@ class SwitchStmt extends Stmt, @switchstmt {
406406
/** Gets the `default` case of this switch statement, if any. */
407407
DefaultCase getDefaultCase() { result = this.getACase() }
408408

409+
/** Gets the `default` or `case null, default` case of this switch statement, if any. */
410+
SwitchCase getDefaultOrNullDefaultCase() { result = this.getACase() and result.hasDefaultLabel() }
411+
409412
/** Gets the expression of this `switch` statement. */
410413
Expr getExpr() { result.getParent() = this }
411414

@@ -487,14 +490,28 @@ class SwitchCase extends Stmt, @case {
487490
Stmt getRuleStatementOrExpressionStatement() {
488491
result.getParent() = this and result.getIndex() = -1
489492
}
493+
494+
/**
495+
* Holds if this case statement includes the default label, i.e. it is either `default`
496+
* or `case null, default`.
497+
*/
498+
predicate hasDefaultLabel() { this instanceof DefaultCase or this instanceof NullDefaultCase }
490499
}
491500

492-
/** A constant `case` of a switch statement. */
501+
/**
502+
* A constant `case` of a switch statement.
503+
*
504+
* Note this excludes `case null, default` even though that includes a null constant. It
505+
* does however include plain `case null`.
506+
*/
493507
class ConstCase extends SwitchCase {
494508
ConstCase() {
495509
exists(Expr e |
496510
e.getParent() = this and e.getIndex() >= 0 and not e instanceof LocalVariableDeclExpr
497511
)
512+
// For backward compatibility, we don't include `case null, default:` here, on the assumption
513+
// this will come as a surprise to CodeQL that predates that statement's validity.
514+
and not isNullDefaultCase(this)
498515
}
499516

500517
/** Gets the `case` constant at index 0. */
@@ -535,7 +552,11 @@ class PatternCase extends SwitchCase {
535552
override string getAPrimaryQlClass() { result = "PatternCase" }
536553
}
537554

538-
/** A `default` case of a `switch` statement */
555+
/**
556+
* A `default` case of a `switch` statement.
557+
*
558+
* Note this does not include `case null, default` -- for that, see `NullDefaultCase`.
559+
*/
539560
class DefaultCase extends SwitchCase {
540561
DefaultCase() { not exists(Expr e | e.getParent() = this | e.getIndex() >= 0) }
541562

@@ -548,6 +569,19 @@ class DefaultCase extends SwitchCase {
548569
override string getAPrimaryQlClass() { result = "DefaultCase" }
549570
}
550571

572+
/** A `case null, default` statement of a `switch` statement or expression. */
573+
class NullDefaultCase extends SwitchCase {
574+
NullDefaultCase() { isNullDefaultCase(this) }
575+
576+
override string pp() { result = "case null, default" }
577+
578+
override string toString() { result = "case null, default" }
579+
580+
override string getHalsteadID() { result = "NullDefaultCase" }
581+
582+
override string getAPrimaryQlClass() { result = "NullDefaultCase" }
583+
}
584+
551585
/** A `synchronized` statement. */
552586
class SynchronizedStmt extends Stmt, @synchronizedstmt {
553587
/** Gets the expression on which this `synchronized` statement synchronizes. */

java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,11 @@ class ConstSwitchStmt extends SwitchStmt {
170170
/** Gets the matching case, if it can be deduced. */
171171
SwitchCase getMatchingCase() {
172172
// Must be a value we can deduce
173+
// TODO: handle other known constants (enum constants, String constants)
173174
exists(this.getExpr().(ConstantExpr).getIntValue()) and
174175
if exists(this.getMatchingConstCase())
175176
then result = this.getMatchingConstCase()
176-
else result = this.getDefaultCase()
177+
else result = this.getDefaultOrNullDefaultCase()
177178
}
178179

179180
/**

java/ql/lib/semmle/code/java/controlflow/internal/GuardsLogic.qll

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,13 @@ predicate implies_v1(Guard g1, boolean b1, Guard g2, boolean b2) {
5555
)
5656
)
5757
or
58-
g1.(DefaultCase).getSwitch().getAConstCase() = g2 and b1 = true and b2 = false
58+
exists(SwitchCase sc | g1 = sc and sc.hasDefaultLabel() |
59+
sc.getSwitch().getAConstCase() = g2 and b1 = true and b2 = false
60+
)
5961
or
60-
g1.(DefaultCase).getSwitchExpr().getAConstCase() = g2 and b1 = true and b2 = false
62+
exists(SwitchCase sc | g1 = sc and sc.hasDefaultLabel() |
63+
sc.getSwitchExpr().getAConstCase() = g2 and b1 = true and b2 = false
64+
)
6165
or
6266
exists(MethodCall check, int argIndex | check = g1 |
6367
conditionCheckArgument(check, argIndex, _) and

java/ql/src/Advisory/Statements/MissingDefaultInSwitch.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ import java
1515
from SwitchStmt switch
1616
where
1717
not switch.getExpr().getType() instanceof EnumType and
18-
not exists(switch.getDefaultCase())
18+
not exists(switch.getDefaultOrNullDefaultCase())
1919
select switch, "Switch statement does not have a default case."

java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ from SwitchStmt switch, EnumType enum, EnumConstant missing
1717
where
1818
switch.getExpr().getType() = enum and
1919
missing.getDeclaringType() = enum and
20-
not exists(switch.getDefaultCase()) and
20+
not exists(switch.getDefaultOrNullDefaultCase()) and
2121
not switch.getAConstCase().getValue() = missing.getAnAccess()
2222
select switch, "Switch statement does not have a case for $@.", missing, missing.getName()

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,24 @@ public static void test(Object thing) {
5252
default -> { }
5353
}
5454

55+
switch((String)thing) {
56+
case "Const1":
57+
System.out.println("It's Const1!");
58+
case "Const2":
59+
System.out.println("It's Const1 or Const2!");
60+
break;
61+
case String s when s.length() <= 6:
62+
System.out.println("It's <= 6 chars long, and neither Const1 nor Const2");
63+
case "Const3":
64+
System.out.println("It's (<= 6 chars long, and neither Const1 nor Const2), or Const3");
65+
break;
66+
case "Const30":
67+
System.out.println("It's Const30");
68+
break;
69+
case null, default:
70+
System.out.println("It's null, or something else");
71+
}
72+
5573
}
5674

5775
}

0 commit comments

Comments
 (0)