Skip to content

Commit 6fc99e3

Browse files
authored
Merge pull request #16023 from smowton/smowton/feature/jdk22-support
Java: support Java 22 language features
2 parents 7377cbb + dcebcc3 commit 6fc99e3

File tree

30 files changed

+1183
-124
lines changed

30 files changed

+1183
-124
lines changed

java/ql/consistency-queries/children.ql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ predicate gapInChildren(Element e, int i) {
4949
not e instanceof Annotation and
5050
// Pattern case statements legitimately have a TypeAccess (-2) and a pattern (0) but not a rule (-1)
5151
not (i = -1 and e instanceof PatternCase and not e.(PatternCase).isRule()) and
52+
// Pattern case statements can have a gap at -3 when they have more than one pattern but no guard.
53+
not (
54+
i = -3 and count(e.(PatternCase).getAPattern()) > 1 and not exists(e.(PatternCase).getGuard())
55+
) and
56+
// Pattern case statements may have some missing type accesses, depending on the nature of the direct child
57+
not (
58+
(i = -2 or i < -4) and
59+
e instanceof PatternCase
60+
) and
5261
// Instanceof with a record pattern is not expected to have a type access in position 1
5362
not (i = 1 and e.(InstanceOfExpr).getPattern() instanceof RecordPatternExpr) and
5463
// RecordPatternExpr extracts type-accesses only for its LocalVariableDeclExpr children
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The Java extractor and QL libraries now support Java 22, including support for anonymous variables, lambda parameters and patterns.
5+
* Pattern cases with multiple patterns and that fall through to or from other pattern cases are now supported. The `PatternCase` class gains the new `getPatternAtIndex` and `getAPattern` predicates, and deprecates `getPattern`.

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

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -489,14 +489,14 @@ private module ControlFlowGraphImpl {
489489
private Stmt getSwitchStatement(SwitchBlock switch, int i) { result.isNthChildOf(switch, i) }
490490

491491
/**
492-
* Holds if `last` is the last node in a pattern case `pc`'s succeeding bind-and-test operation,
492+
* Holds if `last` is the last node in any of pattern case `pc`'s succeeding bind-and-test operations,
493493
* immediately before either falling through to execute successor statements or execute a rule body
494494
* if present. `completion` is the completion kind of the last operation.
495495
*/
496496
private predicate lastPatternCaseMatchingOp(
497497
PatternCase pc, ControlFlowNode last, Completion completion
498498
) {
499-
last(pc.getPattern(), last, completion) and
499+
last(pc.getAPattern(), last, completion) and
500500
completion = NormalCompletion() and
501501
not exists(pc.getGuard())
502502
or
@@ -776,6 +776,18 @@ private module ControlFlowGraphImpl {
776776
last(try.getFinally(), last, NormalCompletion())
777777
}
778778

779+
private predicate isNextNormalSwitchStmt(SwitchBlock switch, Stmt pred, Stmt succ) {
780+
exists(int i, Stmt immediateSucc |
781+
getSwitchStatement(switch, i) = pred and
782+
getSwitchStatement(switch, i + 1) = immediateSucc and
783+
(
784+
if immediateSucc instanceof PatternCase
785+
then isNextNormalSwitchStmt(switch, immediateSucc, succ)
786+
else succ = immediateSucc
787+
)
788+
)
789+
}
790+
779791
/**
780792
* Bind `last` to a cfg node nested inside `n` (or, indeed, `n` itself) such
781793
* that `last` may be the last node during an execution of `n` and finish
@@ -927,9 +939,15 @@ private module ControlFlowGraphImpl {
927939
completion != anonymousBreakCompletion() and
928940
not completion instanceof NormalOrBooleanCompletion
929941
or
930-
// if the last case completes normally, then so does the switch
931-
last(switch.getStmt(strictcount(switch.getAStmt()) - 1), last, NormalCompletion()) and
932-
completion = NormalCompletion()
942+
// if a statement without a non-pattern-case successor completes normally (or for a pattern case
943+
// the guard succeeds) then the switch completes normally.
944+
exists(Stmt lastNormalStmt, Completion stmtCompletion |
945+
lastNormalStmt = getSwitchStatement(switch, _) and
946+
not isNextNormalSwitchStmt(switch, lastNormalStmt, _) and
947+
last(lastNormalStmt, last, stmtCompletion) and
948+
(stmtCompletion = NormalCompletion() or stmtCompletion = BooleanCompletion(true, _)) and
949+
completion = NormalCompletion()
950+
)
933951
or
934952
// if no default case exists, then normal completion of the expression may terminate the switch
935953
// Note this can't happen if there are pattern cases or a null literal, as
@@ -973,9 +991,9 @@ private module ControlFlowGraphImpl {
973991
)
974992
or
975993
// A pattern case statement can complete:
976-
// * On failure of its type test (boolean false)
994+
// * On failure of its final type test (boolean false)
977995
// * On failure of its guard test if any (boolean false)
978-
// * On completion of its variable declarations, if it is not a rule and has no guard (normal completion)
996+
// * On completion of one of its pattern variable declarations, if it is not a rule and has no guard (normal completion)
979997
// * On success of its guard test, if it is not a rule (boolean true)
980998
// (the latter two cases are accounted for by lastPatternCaseMatchingOp)
981999
exists(PatternCase pc | n = pc |
@@ -1315,9 +1333,13 @@ private module ControlFlowGraphImpl {
13151333
// Note this includes non-rule case statements and the successful pattern match successor
13161334
// of a non-rule pattern case statement. Rule case statements do not complete normally
13171335
// (they always break or yield).
1318-
exists(int i |
1319-
last(getSwitchStatement(switch, i), n, completion) and
1320-
result = first(getSwitchStatement(switch, i + 1)) and
1336+
// Exception: falling through into a pattern case statement (which necessarily does not
1337+
// declare any named variables) must skip one or more such statements, otherwise we would
1338+
// incorrectly apply their type test and/or guard.
1339+
exists(Stmt pred, Stmt succ |
1340+
isNextNormalSwitchStmt(switch, pred, succ) and
1341+
last(pred, n, completion) and
1342+
result = first(succ) and
13211343
(completion = NormalCompletion() or completion = BooleanCompletion(true, _))
13221344
)
13231345
or
@@ -1328,16 +1350,19 @@ private module ControlFlowGraphImpl {
13281350
)
13291351
or
13301352
// Pattern cases have internal edges:
1331-
// * Type test success -true-> variable declarations
1353+
// * Type test success -true-> one of the possible sets of variable declarations
1354+
// n.b. for unnamed patterns (e.g. case A _, B _) this means that *one* of the
1355+
// type tests has succeeded. There aren't enough nodes in the AST to describe
1356+
// a sequential test in detail, so CFG consumers have to watch out for this case.
13321357
// * Variable declarations -normal-> guard evaluation
13331358
// * Variable declarations -normal-> rule execution (when there is no guard)
13341359
// * Guard success -true-> rule execution
13351360
exists(PatternCase pc |
13361361
n = pc and
13371362
completion = basicBooleanCompletion(true) and
1338-
result = first(pc.getPattern())
1363+
result = first(pc.getAPattern())
13391364
or
1340-
last(pc.getPattern(), n, completion) and
1365+
last(pc.getAPattern(), n, completion) and
13411366
completion = NormalCompletion() and
13421367
result = first(pc.getGuard())
13431368
or

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ predicate depends(RefType t, RefType dep) {
8484
or
8585
// A type accessed in a pattern-switch case statement in `t`.
8686
exists(PatternCase pc | t = pc.getEnclosingCallable().getDeclaringType() |
87-
usesType(pc.getPattern().getAChildExpr*().getType(), dep)
87+
usesType(pc.getAPattern().getAChildExpr*().getType(), dep)
8888
)
8989
)
9090
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ predicate numDepends(RefType t, RefType dep, int value) {
107107
or
108108
// the type accessed in a pattern-switch case statement in `t`.
109109
exists(PatternCase pc | elem = pc and t = pc.getEnclosingCallable().getDeclaringType() |
110-
usesType(pc.getPattern().getAChildExpr*().getType(), dep)
110+
usesType(pc.getAPattern().getAChildExpr*().getType(), dep)
111111
)
112112
)
113113
}

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

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,7 +1590,9 @@ class InstanceOfExpr extends Expr, @instanceofexpr {
15901590
* Note that this won't get anything when record pattern matching is used-- for more general patterns,
15911591
* use `getPattern`.
15921592
*/
1593-
LocalVariableDeclExpr getLocalVariableDeclExpr() { result = this.getPattern().asBindingPattern() }
1593+
LocalVariableDeclExpr getLocalVariableDeclExpr() {
1594+
result = this.getPattern().asBindingOrUnnamedPattern()
1595+
}
15941596

15951597
/**
15961598
* Gets the access to the type on the right-hand side of the `instanceof` operator.
@@ -1681,7 +1683,10 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
16811683
or
16821684
exists(InstanceOfExpr ioe | this.getParent() = ioe | result.isNthChildOf(ioe, 1))
16831685
or
1684-
exists(PatternCase pc | this.getParent() = pc | result.isNthChildOf(pc, -2))
1686+
exists(PatternCase pc, int index, int typeAccessIdx | this.isNthChildOf(pc, index) |
1687+
(if index = 0 then typeAccessIdx = -2 else typeAccessIdx = (-3 - index)) and
1688+
result.isNthChildOf(pc, typeAccessIdx)
1689+
)
16851690
or
16861691
exists(RecordPatternExpr rpe, int index |
16871692
this.isNthChildOf(rpe, index) and result.isNthChildOf(rpe, -(index + 1))
@@ -1691,6 +1696,9 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
16911696
/** Gets the name of the variable declared by this local variable declaration expression. */
16921697
string getName() { result = this.getVariable().getName() }
16931698

1699+
/** Holds if this is an anonymous local variable, `_` */
1700+
predicate isAnonymous() { this.getName() = "" }
1701+
16941702
/**
16951703
* Gets the switch statement or expression whose pattern declares this identifier, if any.
16961704
*/
@@ -1700,7 +1708,7 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
17001708
or
17011709
pc = result.(SwitchExpr).getAPatternCase()
17021710
|
1703-
this = pc.getPattern().getAChildExpr*()
1711+
this = pc.getAPattern().getAChildExpr*()
17041712
)
17051713
}
17061714

@@ -1739,17 +1747,17 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
17391747
or
17401748
exists(SwitchStmt switch |
17411749
result = switch.getExpr() and
1742-
this = switch.getAPatternCase().getPattern().asBindingPattern()
1750+
this = switch.getAPatternCase().getAPattern().asBindingOrUnnamedPattern()
17431751
)
17441752
or
17451753
exists(SwitchExpr switch |
17461754
result = switch.getExpr() and
1747-
this = switch.getAPatternCase().getPattern().asBindingPattern()
1755+
this = switch.getAPatternCase().getAPattern().asBindingOrUnnamedPattern()
17481756
)
17491757
or
17501758
exists(InstanceOfExpr ioe |
17511759
result = ioe.getExpr() and
1752-
this = ioe.getPattern().asBindingPattern()
1760+
this = ioe.getPattern().asBindingOrUnnamedPattern()
17531761
)
17541762
}
17551763

@@ -1763,7 +1771,9 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
17631771
}
17641772

17651773
/** Gets a printable representation of this expression. */
1766-
override string toString() { result = this.getName() }
1774+
override string toString() {
1775+
if this.getName() = "" then result = "<anonymous local variable>" else result = this.getName()
1776+
}
17671777

17681778
override string getAPrimaryQlClass() { result = "LocalVariableDeclExpr" }
17691779
}
@@ -2671,9 +2681,9 @@ class NotNullExpr extends UnaryExpr, @notnullexpr {
26712681
}
26722682

26732683
/**
2674-
* A binding or record pattern.
2684+
* A binding, unnamed or record pattern.
26752685
*
2676-
* Note binding patterns are represented as `LocalVariableDeclExpr`s.
2686+
* Note binding and unnamed patterns are represented as `LocalVariableDeclExpr`s.
26772687
*/
26782688
class PatternExpr extends Expr {
26792689
PatternExpr() {
@@ -2686,9 +2696,14 @@ class PatternExpr extends Expr {
26862696
}
26872697

26882698
/**
2689-
* Gets this pattern cast to a binding pattern.
2699+
* Gets this pattern cast to a binding or unnamed pattern.
26902700
*/
2691-
LocalVariableDeclExpr asBindingPattern() { result = this }
2701+
LocalVariableDeclExpr asBindingOrUnnamedPattern() { result = this }
2702+
2703+
/**
2704+
* DEPRECATED: alias for `asBindingOrUnnamedPattern`.
2705+
*/
2706+
deprecated LocalVariableDeclExpr asBindingPattern() { result = this.asBindingOrUnnamedPattern() }
26922707

26932708
/**
26942709
* Gets this pattern cast to a record pattern.
@@ -2724,4 +2739,14 @@ class RecordPatternExpr extends Expr, @recordpatternexpr {
27242739
)
27252740
)
27262741
}
2742+
2743+
/**
2744+
* Holds if this record pattern declares any identifiers (i.e., at least one leaf declaration is named).
2745+
*/
2746+
predicate declaresAnyIdentifiers() {
2747+
exists(PatternExpr subPattern | subPattern = this.getSubPattern(_) |
2748+
subPattern.asRecordPattern().declaresAnyIdentifiers() or
2749+
not subPattern.asBindingOrUnnamedPattern().isAnonymous()
2750+
)
2751+
}
27272752
}

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

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ private class PpInstanceOfExpr extends PpAst, InstanceOfExpr {
386386
i = 3 and result = " " and this.getPattern() instanceof LocalVariableDeclExpr
387387
or
388388
i = 4 and
389-
result = this.getPattern().asBindingPattern().getName()
389+
result = this.getPattern().asBindingOrUnnamedPattern().getName()
390390
}
391391

392392
override PpAst getChild(int i) {
@@ -400,7 +400,8 @@ private class PpInstanceOfExpr extends PpAst, InstanceOfExpr {
400400

401401
private class PpLocalVariableDeclExpr extends PpAst, LocalVariableDeclExpr {
402402
override string getPart(int i) {
403-
i = 0 and result = this.getName()
403+
i = 0 and
404+
(if this.isAnonymous() then result = "_" else result = this.getName())
404405
or
405406
i = 1 and result = " = " and exists(this.getInit())
406407
}
@@ -782,28 +783,54 @@ private class PpSwitchCase extends PpAst, SwitchCase {
782783
}
783784

784785
private class PpPatternCase extends PpAst, PatternCase {
786+
private predicate isAnonymousPattern(int n) {
787+
this.getPattern(n).asBindingOrUnnamedPattern().isAnonymous()
788+
}
789+
785790
override string getPart(int i) {
786-
i = 0 and result = "case "
787-
or
788-
i = 2 and this.getPattern() instanceof LocalVariableDeclExpr and result = " "
789-
or
790-
i = 3 and result = this.getPattern().asBindingPattern().getName()
791-
or
792-
i = 4 and result = ":" and not this.isRule()
793-
or
794-
i = 4 and result = " -> " and this.isRule()
791+
exists(int n, int base | exists(this.getPattern(n)) and base = n * 4 |
792+
i = base and
793+
(if n = 0 then result = "case " else result = ", ")
794+
or
795+
i = base + 2 and
796+
this.getPattern(n) instanceof LocalVariableDeclExpr and
797+
(
798+
exists(this.getPattern(n).asBindingOrUnnamedPattern().getTypeAccess())
799+
or
800+
not this.isAnonymousPattern(n)
801+
) and
802+
result = " "
803+
or
804+
i = base + 3 and
805+
(
806+
if this.isAnonymousPattern(n)
807+
then result = "_"
808+
else result = this.getPattern(n).asBindingOrUnnamedPattern().getName()
809+
)
810+
)
795811
or
796-
i = 6 and result = ";" and exists(this.getRuleExpression())
812+
exists(int base | base = (max(int n | exists(this.getPattern(n))) + 1) * 4 |
813+
i = base and result = ":" and not this.isRule()
814+
or
815+
i = base and result = " -> " and this.isRule()
816+
or
817+
i = base + 2 and result = ";" and exists(this.getRuleExpression())
818+
)
797819
}
798820

799821
override PpAst getChild(int i) {
800-
i = 1 and result = this.getPattern().asBindingPattern().getTypeAccess()
801-
or
802-
i = 1 and result = this.getPattern().asRecordPattern()
803-
or
804-
i = 5 and result = this.getRuleExpression()
822+
exists(int n, int base | exists(this.getPattern(n)) and base = n * 4 |
823+
i = base + 1 and
824+
result = this.getPattern(n).asBindingOrUnnamedPattern().getTypeAccess()
825+
or
826+
i = base + 1 and result = this.getPattern(n).asRecordPattern()
827+
)
805828
or
806-
i = 5 and result = this.getRuleStatement()
829+
exists(int base | base = (max(int n | exists(this.getPattern(n))) + 1) * 4 |
830+
i = base + 1 and result = this.getRuleExpression()
831+
or
832+
i = base + 1 and result = this.getRuleStatement()
833+
)
807834
}
808835
}
809836

0 commit comments

Comments
 (0)