Basic extraction of record patterns

This commit is contained in:
Chris Smowton
2023-11-01 16:05:53 +00:00
parent 293cc67494
commit daccd04087
10 changed files with 93 additions and 42 deletions

View File

@@ -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. */

View File

@@ -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

View File

@@ -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)
)
)
}

View File

@@ -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" }
}

View File

@@ -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) }

View File

@@ -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:

View File

@@ -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()
)
}

View File

@@ -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

View File

@@ -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) { }

View File

@@ -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;