diff --git a/java/ql/lib/config/semmlecode.dbscheme b/java/ql/lib/config/semmlecode.dbscheme index 53c0d42e216..0486bc8cd10 100644 --- a/java/ql/lib/config/semmlecode.dbscheme +++ b/java/ql/lib/config/semmlecode.dbscheme @@ -774,6 +774,7 @@ case @expr.kind of | 86 = @valueeqexpr | 87 = @valueneexpr | 88 = @propertyref +| 89 = @recordpatternexpr ; /** Holds if this `when` expression was written as an `if` expression. */ diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 837cc576a3f..54798ee3b10 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -529,7 +529,7 @@ private module ControlFlowGraphImpl { or this instanceof LocalVariableDeclExpr and not this = any(InstanceOfExpr ioe).getLocalVariableDeclExpr() and - not this = any(PatternCase pc).getDecl() + not this = any(PatternCase pc).getPattern() or this instanceof StringTemplateExpr or @@ -971,7 +971,7 @@ private module ControlFlowGraphImpl { ( if exists(pc.getGuard()) then last(pc.getGuard(), last, BooleanCompletion(true, _)) - else last = pc.getDecl() + else last = pc.getPattern() ) and not pc.isRule() and completion = NormalCompletion() @@ -1332,7 +1332,7 @@ private module ControlFlowGraphImpl { last(case.(PatternCase).getGuard(), preBodyNode, completion) and completion = basicBooleanCompletion(true) ) else ( - preBodyNode = case.(PatternCase).getDecl() and completion = NormalCompletion() + preBodyNode = case.(PatternCase).getPattern() and completion = NormalCompletion() ) ) else ( preBodyNode = case and completion = NormalCompletion() @@ -1351,7 +1351,7 @@ private module ControlFlowGraphImpl { result = getASuccessorSwitchCase(case) or completion = basicBooleanCompletion(true) and - result = case.getDecl() + result = case.getPattern() ) ) or @@ -1362,7 +1362,7 @@ private module ControlFlowGraphImpl { exists(PatternCase case, Expr guard | guard = case.getGuard() and ( - n = case.getDecl() and + n = case.getPattern() and result = first(guard) and completion = NormalCompletion() or diff --git a/java/ql/lib/semmle/code/java/Dependency.qll b/java/ql/lib/semmle/code/java/Dependency.qll index cbf753ce54c..7c01a43e186 100644 --- a/java/ql/lib/semmle/code/java/Dependency.qll +++ b/java/ql/lib/semmle/code/java/Dependency.qll @@ -82,7 +82,7 @@ predicate depends(RefType t, RefType dep) { or // the type accessed in a pattern-switch case statement in `t`. exists(PatternCase pc | t = pc.getEnclosingCallable().getDeclaringType() | - usesType(pc.getDecl().getType(), dep) + usesType(pc.getPattern().getType(), dep) ) ) } diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index a16aac6f990..358c413807d 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -2541,3 +2541,10 @@ class NotNullExpr extends UnaryExpr, @notnullexpr { override string getAPrimaryQlClass() { result = "NotNullExpr" } } + +/** A record pattern expr, as in `if (x instanceof SomeRecord(int field))`. */ +class RecordPatternExpr extends Expr, @recordpatternexpr { + override string toString() { result = this.getType().toString() + "(...)" } + + override string getAPrimaryQlClass() { result = "RecordPatternExpr" } +} diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index c5002356108..467239839ad 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -507,7 +507,7 @@ class SwitchCase extends Stmt, @case { class ConstCase extends SwitchCase { ConstCase() { exists(Expr e | - e.getParent() = this and e.getIndex() >= 0 and not e instanceof LocalVariableDeclExpr + e.getParent() = this and e.getIndex() >= 0 and not e instanceof Pattern ) // 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. @@ -531,14 +531,35 @@ class ConstCase extends SwitchCase { override string getAPrimaryQlClass() { result = "ConstCase" } } +/** + * A binding or record pattern. + * + * Note binding patterns are represented as `LocalVariableDeclExpr`s. + */ +class Pattern extends Expr { + Pattern() { + (this.getParent() instanceof SwitchCase or this.getParent() instanceof InstanceOfExpr) + and + (this instanceof LocalVariableDeclExpr or this instanceof RecordPatternExpr) + } + + LocalVariableDeclExpr asBindingPattern() { + result = this + } + + RecordPatternExpr asRecordPattern() { + result = this + } +} + /** A pattern case of a `switch` statement */ class PatternCase extends SwitchCase { - LocalVariableDeclExpr patternVar; + Pattern pattern; - PatternCase() { patternVar.isNthChildOf(this, 0) } + PatternCase() { pattern.isNthChildOf(this, 0) } - /** Gets the variable declared by this pattern case. */ - LocalVariableDeclExpr getDecl() { result.isNthChildOf(this, 0) } + /** Gets this case's pattern. */ + Pattern getPattern() { result.isNthChildOf(this, 0) } /** Gets the guard applicable to this pattern case, if any. */ Expr getGuard() { result.isNthChildOf(this, -3) } diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index b11c2e91903..157bb0d9a99 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -180,7 +180,7 @@ private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) { selector = sc.getSelectorExpr() and ( if sc instanceof PatternCase - then caseblock.getFirstNode() = sc.(PatternCase).getDecl().getControlFlowNode() + then caseblock.getFirstNode() = sc.(PatternCase).getPattern().getControlFlowNode() else ( caseblock.getFirstNode() = sc.getControlFlowNode() and // Check there is no fall-through edge from a previous case: diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index 47258af0a27..19014d18752 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -447,7 +447,7 @@ private predicate patternCaseGuarded(Expr e, RefType t) { exists(PatternCase pc | e = getAProbableAlias([pc.getSwitch().getExpr(), pc.getSwitchExpr().getExpr()]) and guardControls_v1(pc, e.getBasicBlock(), true) and - t = pc.getDecl().getType() + t = pc.getPattern().getType() ) } diff --git a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll index c9ae5353127..1cfc327a3a5 100644 --- a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll +++ b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll @@ -82,7 +82,7 @@ private predicate step(Node n1, Node n2) { ) or exists(PatternCase pc | - pc.getDecl() = def.(BaseSsaUpdate).getDefiningExpr() and + pc.getPattern() = def.(BaseSsaUpdate).getDefiningExpr() and ( pc.getSwitch().getExpr() = n1.asExpr() or diff --git a/java/ql/test/library-tests/printAst/A.java b/java/ql/test/library-tests/printAst/A.java index 23b5732d26b..219fe13c5f1 100644 --- a/java/ql/test/library-tests/printAst/A.java +++ b/java/ql/test/library-tests/printAst/A.java @@ -101,6 +101,10 @@ class A { case E.B -> "It's E.B"; default -> "It's something else"; }; + var recordPatterntest = switch(thing) { + case Middle(Inner(String field)) -> field; + default -> "Doesn't match pattern Middle(Inner(...))"; + }; } } catch (RuntimeException rte) { @@ -122,3 +126,6 @@ class A { */ int i, j, k; } + +record Inner(String field) { } +record Middle(Inner inner) { } diff --git a/java/ql/test/library-tests/printAst/PrintAst.expected b/java/ql/test/library-tests/printAst/PrintAst.expected index 436699f7e46..62b8b44fee3 100644 --- a/java/ql/test/library-tests/printAst/PrintAst.expected +++ b/java/ql/test/library-tests/printAst/PrintAst.expected @@ -283,36 +283,51 @@ A.java: # 101| -1: [TypeAccess] E # 102| 2: [DefaultCase] default # 102| -1: [StringLiteral] "It's something else" -# 106| 0: [CatchClause] catch (...) +# 104| 11: [LocalVariableDeclStmt] var ...; +# 104| 1: [LocalVariableDeclExpr] recordPatterntest +# 104| 0: [SwitchExpr] switch (...) +# 104| -1: [VarAccess] thing +# 105| 0: [PatternCase] case T t ... +# 105| -1: [VarAccess] field +# 105| 0: [RecordPatternExpr] Middle(...) +# 105| 0: [RecordPatternExpr] Inner(...) +# 105| 0: [LocalVariableDeclExpr] field +# 106| 1: [DefaultCase] default +# 106| -1: [StringLiteral] "Doesn't match pattern Middle(Inner(...))" +# 110| 0: [CatchClause] catch (...) #-----| 0: (Single Local Variable Declaration) -# 106| 0: [TypeAccess] RuntimeException -# 106| 1: [LocalVariableDeclExpr] rte -# 106| 1: [BlockStmt] { ... } -# 107| 0: [ReturnStmt] return ... -# 111| 10: [Class] E -# 115| 3: [FieldDeclaration] E A; +# 110| 0: [TypeAccess] RuntimeException +# 110| 1: [LocalVariableDeclExpr] rte +# 110| 1: [BlockStmt] { ... } +# 111| 0: [ReturnStmt] return ... +# 115| 10: [Class] E +# 119| 3: [FieldDeclaration] E A; #-----| -3: (Javadoc) -# 112| 1: [Javadoc] /** Javadoc for enum constant */ -# 113| 0: [JavadocText] Javadoc for enum constant -# 115| -1: [TypeAccess] E -# 115| 0: [ClassInstanceExpr] new E(...) -# 115| -3: [TypeAccess] E -# 116| 4: [FieldDeclaration] E B; +# 116| 1: [Javadoc] /** Javadoc for enum constant */ +# 117| 0: [JavadocText] Javadoc for enum constant +# 119| -1: [TypeAccess] E +# 119| 0: [ClassInstanceExpr] new E(...) +# 119| -3: [TypeAccess] E +# 120| 4: [FieldDeclaration] E B; #-----| -3: (Javadoc) -# 112| 1: [Javadoc] /** Javadoc for enum constant */ -# 113| 0: [JavadocText] Javadoc for enum constant -# 116| -1: [TypeAccess] E -# 116| 0: [ClassInstanceExpr] new E(...) -# 116| -3: [TypeAccess] E -# 117| 5: [FieldDeclaration] E C; +# 116| 1: [Javadoc] /** Javadoc for enum constant */ +# 117| 0: [JavadocText] Javadoc for enum constant +# 120| -1: [TypeAccess] E +# 120| 0: [ClassInstanceExpr] new E(...) +# 120| -3: [TypeAccess] E +# 121| 5: [FieldDeclaration] E C; #-----| -3: (Javadoc) -# 112| 1: [Javadoc] /** Javadoc for enum constant */ -# 113| 0: [JavadocText] Javadoc for enum constant -# 117| -1: [TypeAccess] E -# 117| 0: [ClassInstanceExpr] new E(...) -# 117| -3: [TypeAccess] E -# 123| 11: [FieldDeclaration] int i, ...; +# 116| 1: [Javadoc] /** Javadoc for enum constant */ +# 117| 0: [JavadocText] Javadoc for enum constant +# 121| -1: [TypeAccess] E +# 121| 0: [ClassInstanceExpr] new E(...) +# 121| -3: [TypeAccess] E +# 127| 11: [FieldDeclaration] int i, ...; #-----| -3: (Javadoc) -# 120| 1: [Javadoc] /** Javadoc for fields */ -# 121| 0: [JavadocText] Javadoc for fields -# 123| -1: [TypeAccess] int +# 124| 1: [Javadoc] /** Javadoc for fields */ +# 125| 0: [JavadocText] Javadoc for fields +# 127| -1: [TypeAccess] int +# 130| 2: [Class] Inner +# 130| 2: [FieldDeclaration] String field; +# 131| 3: [Class] Middle +# 131| 2: [FieldDeclaration] Inner inner;