Implement dataflow for record patterns

Strategy: there is now a regular flow step from an instance-of LHS / switch expr to the pattern, 0 or more read steps corresponding to record pattern destructors, and then finally a normal SSA def/use step connecting the binding patterns to their first uses.
This commit is contained in:
Chris Smowton
2023-11-01 20:33:09 +00:00
parent 05addde957
commit 20b97af02f
6 changed files with 55 additions and 3 deletions

View File

@@ -576,6 +576,10 @@ lambdaKind(
int bodyKind: int ref
);
isCanonicalConstr(
int constructorid: @constructor ref
);
arrays(
unique int id: @array,
string nodeName: string ref,

View File

@@ -1639,7 +1639,11 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
/** Gets the name of the variable declared by this local variable declaration expression. */
string getName() { result = this.getVariable().getName() }
/** Gets the switch statement or expression whose pattern declares this identifier, if any. */
/**
* Gets the switch statement or expression whose pattern declares this identifier, if any.
*
* Note this only applies to a direct binding pattern, such as `case T t`, not a record pattern.
*/
StmtParent getAssociatedSwitch() { result = this.getParent().(PatternCase).getParent() }
/** Holds if this is a declaration stemming from a pattern switch case. */

View File

@@ -746,6 +746,13 @@ class DataClass extends Class {
*/
class Record extends Class {
Record() { isRecord(this) }
/**
* Gets the canonical constructor of this record.
*/
Constructor getCanonicalConstructor() {
result = this.getAConstructor() and isCanonicalConstr(result)
}
}
/** An intersection type. */

View File

@@ -232,6 +232,17 @@ predicate storeStep(Node node1, ContentSet f, Node node2) {
pragma[only_bind_out](node2.getEnclosingCallable())
}
private Field getLexicallyOrderedRecordField(Record r, int idx) {
result =
rank[idx + 1](Field f, int i, Parameter p |
f = r.getAField() and
p = r.getCanonicalConstructor().getParameter(i) and
f.getName() = p.getName()
|
f order by i
)
}
/**
* Holds if data can flow from `node1` to `node2` via a read of `f`.
* Thus, `node1` references an object with a field `f` whose value ends up in
@@ -256,6 +267,13 @@ predicate readStep(Node node1, ContentSet f, Node node2) {
node2.asExpr() = get
)
or
exists(RecordPatternExpr rpe, Pattern subPattern, int i |
node1.asExpr() = rpe and
subPattern = rpe.getSubPattern(i) and
node2.asExpr() = subPattern and
f.(FieldContent).getField() = getLexicallyOrderedRecordField(rpe.getType(), i)
)
or
f instanceof ArrayContent and arrayReadStep(node1, node2, _)
or
f instanceof CollectionContent and collectionReadStep(node1, node2)

View File

@@ -190,6 +190,18 @@ predicate simpleAstFlowStep(Expr e1, Expr e2) {
e2 = any(NotNullExpr nne | e1 = nne.getExpr())
or
e2.(WhenExpr).getBranch(_).getAResult() = e1
or
exists(SwitchExpr se |
e1 = se.getExpr() and e2 = se.getACase().(PatternCase).getPattern()
)
or
exists(SwitchStmt ss |
e1 = ss.getExpr() and e2 = ss.getACase().(PatternCase).getPattern()
)
or
exists(InstanceOfExpr ioe |
e1 = ioe.getExpr() and e2 = ioe.getLocalVariableDeclExpr()
)
}
private predicate simpleLocalFlowStep0(Node node1, Node node2) {
@@ -198,8 +210,7 @@ private predicate simpleLocalFlowStep0(Node node1, Node node2) {
exists(SsaExplicitUpdate upd |
upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() or
upd.getDefiningExpr().(AssignOp) = node1.asExpr() or
upd.getDefiningExpr().(LocalVariableDeclExpr).getAssociatedSwitch().(SwitchStmt).getExpr() = node1.asExpr() or
upd.getDefiningExpr().(LocalVariableDeclExpr).getAssociatedSwitch().(SwitchExpr).getExpr() = node1.asExpr()
upd.getDefiningExpr().(Pattern).asBindingPattern() = node1.asExpr()
|
node2.asExpr() = upd.getAFirstUse() and
not capturedVariableRead(node2)