Skip to content

Commit 1dd3e38

Browse files
authored
Merge pull request github#12133 from d10c/swift/case-let-dataflow
Swift: `case let` dataflow
2 parents 08d2d3b + d0de4a5 commit 1dd3e38

28 files changed

+2338
-365
lines changed

swift/ql/.generated.list

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,6 @@ ql/lib/codeql/swift/elements/pattern/IsPatternConstructor.qll 209ad40227f49dfe1b
232232
ql/lib/codeql/swift/elements/pattern/NamedPatternConstructor.qll 437ccf0a28c204a83861babc91e0e422846630f001554a3d7764b323c8632a26 91c52727ccf6b035cc1f0c2ca1eb91482ef28fa874bca382fb34f9226c31c84e
233233
ql/lib/codeql/swift/elements/pattern/OptionalSomePatternConstructor.qll bc33a81415edfa4294ad9dfb57f5aa929ea4d7f21a013f145949352009a93975 9fb2afa86dc9cedd28af9f27543ea8babf431a4ba48e9941bcd484b9aa0aeab5
234234
ql/lib/codeql/swift/elements/pattern/ParenPatternConstructor.qll 7229439aac7010dbb82f2aaa48aedf47b189e21cc70cb926072e00faa8358369 341cfacd838185178e95a2a7bb29f198e46954098f6d13890351a82943969809
235-
ql/lib/codeql/swift/elements/pattern/Pattern.qll a5cee4c72446f884107acc248a10b098871dc027513452c569294aaeecbc5890 cbecbc4b2d9bea7b571b9d184a0bb8cf472f66106e96d4f70e0e98d627f269a5
236235
ql/lib/codeql/swift/elements/pattern/TuplePatternConstructor.qll 208fe1f6af1eb569ea4cd5f76e8fbe4dfab9a6264f6c12b762f074a237934bdc 174c55ad1bf3058059ed6c3c3502d6099cda95fbfce925cfd261705accbddbcd
237236
ql/lib/codeql/swift/elements/pattern/TypedPatternConstructor.qll 1befdd0455e94d4daa0332b644b74eae43f98bab6aab7491a37176a431c36c34 e574ecf38aac7d9381133bfb894da8cb96aec1c933093f4f7cc951dba8152570
238237
ql/lib/codeql/swift/elements/stmt/BraceStmtConstructor.qll eb2b4817d84da4063eaa0b95fe22131cc980c761dcf41f1336566e95bc28aae4 c8e5f7fecd01a7724d8f58c2cd8c845264be91252281f37e3eb20f4a6f421c72

swift/ql/lib/codeql/swift/controlflow/CfgNodes.qll

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,16 @@ class ExprCfgNode extends CfgNode {
118118
Expr getExpr() { result = e }
119119
}
120120

121+
/** A control-flow node that wraps a pattern. */
122+
class PatternCfgNode extends CfgNode {
123+
Pattern p;
124+
125+
PatternCfgNode() { p = this.getNode().asAstNode() }
126+
127+
/** Gets the underlying pattern. */
128+
Pattern getPattern() { result = p }
129+
}
130+
121131
/** A control-flow node that wraps a property getter. */
122132
class PropertyGetterCfgNode extends CfgNode {
123133
override PropertyGetterElement n;

swift/ql/lib/codeql/swift/controlflow/internal/Completion.qll

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ private predicate switchMatching(SwitchStmt switch, CaseStmt c, AstNode ast) {
185185
(
186186
c.getALabel() = ast
187187
or
188-
isSubPattern+(c.getALabel().getPattern(), ast)
188+
ast.(Pattern).getImmediateEnclosingPattern+() = c.getALabel().getPattern()
189189
)
190190
}
191191

@@ -216,27 +216,10 @@ predicate catchMatchingPattern(DoCatchStmt s, CaseStmt c, Pattern pattern) {
216216
exists(CaseLabelItem cli | catchMatching(s, c, cli) |
217217
cli.getPattern() = pattern
218218
or
219-
isSubPattern+(cli.getPattern(), pattern)
219+
pattern.getImmediateEnclosingPattern+() = cli.getPattern()
220220
)
221221
}
222222

223-
/** Holds if `sub` is a subpattern of `p`. */
224-
private predicate isSubPattern(Pattern p, Pattern sub) {
225-
sub = p.(BindingPattern).getSubPattern().getFullyUnresolved()
226-
or
227-
sub = p.(EnumElementPattern).getSubPattern().getFullyUnresolved()
228-
or
229-
sub = p.(IsPattern).getSubPattern().getFullyUnresolved()
230-
or
231-
sub = p.(OptionalSomePattern).getFullyUnresolved()
232-
or
233-
sub = p.(ParenPattern).getResolveStep()
234-
or
235-
sub = p.(TuplePattern).getAnElement().getFullyUnresolved()
236-
or
237-
sub = p.(TypedPattern).getSubPattern().getFullyUnresolved()
238-
}
239-
240223
/** Gets the value of `e` if it is a constant value, disregarding conversions. */
241224
private string getExprValue(Expr e) {
242225
result = e.(IntegerLiteralExpr).getStringValue()
@@ -260,8 +243,6 @@ private string getPatternValue(Pattern p) {
260243
private predicate isIrrefutableMatch(Pattern p) {
261244
(p instanceof NamedPattern or p instanceof AnyPattern)
262245
or
263-
isIrrefutableMatch(p.(BindingPattern).getSubPattern().getFullyUnresolved())
264-
or
265246
isIrrefutableMatch(p.(TypedPattern).getSubPattern().getFullyUnresolved())
266247
or
267248
// A pattern hidden underneath a conversion is also an irrefutable pattern.

swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -897,21 +897,12 @@ module Patterns {
897897
}
898898
}
899899

900-
private class BindingTree extends AstPostOrderTree {
900+
private class BindingTree extends AstStandardPostOrderTree {
901901
override BindingPattern ast;
902902

903-
final override predicate propagatesAbnormal(ControlFlowElement n) {
904-
n.asAstNode() = ast.getSubPattern().getFullyUnresolved()
905-
}
906-
907-
final override predicate first(ControlFlowElement n) {
908-
astFirst(ast.getSubPattern().getFullyUnresolved(), n)
909-
}
910-
911-
override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
912-
astLast(ast.getSubPattern().getFullyUnresolved(), pred, c) and
913-
c.(MatchingCompletion).isMatch() and
914-
succ.asAstNode() = ast
903+
final override ControlFlowElement getChildElement(int i) {
904+
i = 0 and
905+
result.asAstNode() = ast.getResolveStep()
915906
}
916907
}
917908

swift/ql/lib/codeql/swift/dataflow/Ssa.qll

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ module Ssa {
3838
// if let x5 = optional { ... }
3939
// guard let x6 = optional else { ... }
4040
// ```
41-
exists(Pattern pattern |
41+
exists(NamedPattern pattern |
4242
bb.getNode(i).getNode().asAstNode() = pattern and
43-
v.getParentPattern() = pattern and
43+
v = pattern.getVarDecl() and
4444
certain = true
4545
)
4646
or
@@ -153,14 +153,10 @@ module Ssa {
153153
value.getNode().asAstNode() = a.getSource()
154154
)
155155
or
156-
exists(
157-
VarDecl var, SsaInput::BasicBlock bb, int blockIndex, PatternBindingDecl pbd, Expr init
158-
|
159-
this.definesAt(var, bb, blockIndex) and
160-
pbd.getAPattern() = bb.getNode(blockIndex).getNode().asAstNode() and
161-
init = var.getParentInitializer()
162-
|
163-
value.getAst() = init
156+
exists(SsaInput::BasicBlock bb, int blockIndex, NamedPattern np |
157+
this.definesAt(_, bb, blockIndex) and
158+
np = bb.getNode(blockIndex).getNode().asAstNode() and
159+
value.getNode().asAstNode() = np
164160
)
165161
or
166162
exists(SsaInput::BasicBlock bb, int blockIndex, ConditionElement ce, Expr init |

swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ private class ExprNodeImpl extends ExprNode, NodeImpl {
4040
override DataFlowCallable getEnclosingCallable() { result = TDataFlowFunc(n.getScope()) }
4141
}
4242

43+
private class PatternNodeImpl extends PatternNode, NodeImpl {
44+
override Location getLocationImpl() { result = pattern.getLocation() }
45+
46+
override string toStringImpl() { result = pattern.toString() }
47+
48+
override DataFlowCallable getEnclosingCallable() { result = TDataFlowFunc(n.getScope()) }
49+
}
50+
4351
private class SsaDefinitionNodeImpl extends SsaDefinitionNode, NodeImpl {
4452
override Location getLocationImpl() { result = def.getLocation() }
4553

@@ -63,6 +71,7 @@ private module Cached {
6371
cached
6472
newtype TNode =
6573
TExprNode(CfgNode n, Expr e) { hasExprNode(n, e) } or
74+
TPatternNode(CfgNode n, Pattern p) { hasPatternNode(n, p) } or
6675
TSsaDefinitionNode(Ssa::Definition def) or
6776
TInoutReturnNode(ParamDecl param) { modifiableParam(param) } or
6877
TSummaryNode(FlowSummary::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state) {
@@ -175,6 +184,20 @@ private module Cached {
175184
nodeFrom.asExpr() = ie.getBranch(_)
176185
)
177186
or
187+
// flow from Expr to Pattern
188+
exists(Expr e, Pattern p |
189+
nodeFrom.asExpr() = e and
190+
nodeTo.asPattern() = p and
191+
p.getImmediateMatchingExpr() = e
192+
)
193+
or
194+
// flow from Pattern to an identity-preserving sub-Pattern:
195+
nodeTo.asPattern() =
196+
[
197+
nodeFrom.asPattern().(IsPattern).getSubPattern(),
198+
nodeFrom.asPattern().(TypedPattern).getSubPattern()
199+
]
200+
or
178201
// flow through a flow summary (extension of `SummaryModelCsv`)
179202
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
180203
}
@@ -201,7 +224,8 @@ private module Cached {
201224
cached
202225
newtype TContent =
203226
TFieldContent(FieldDecl f) or
204-
TTupleContent(int index) { exists(any(TupleExpr te).getElement(index)) }
227+
TTupleContent(int index) { exists(any(TupleExpr te).getElement(index)) } or
228+
TEnumContent(ParamDecl f) { exists(EnumElementDecl d | d.getAParam() = f) }
205229
}
206230

207231
/**
@@ -230,6 +254,11 @@ private predicate hasExprNode(CfgNode n, Expr e) {
230254
n.(PropertyObserverCfgNode).getAssignExpr() = e
231255
}
232256

257+
private predicate hasPatternNode(PatternCfgNode n, Pattern p) {
258+
n.getPattern() = p and
259+
p = p.resolve() // no need to turn hidden-AST patterns (`let`s, parens) into data flow nodes
260+
}
261+
233262
import Cached
234263

235264
/** Holds if `n` should be hidden from path explanations. */
@@ -558,6 +587,29 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
558587
c.isSingleton(any(Content::TupleContent tc | tc.getIndex() = tuple.getIndex()))
559588
)
560589
or
590+
// creation of an enum `.variant(v1, v2)`
591+
exists(EnumElementExpr enum, int pos |
592+
node1.asExpr() = enum.getArgument(pos).getExpr() and
593+
node2.asExpr() = enum and
594+
c.isSingleton(any(Content::EnumContent ec | ec.getParam() = enum.getElement().getParam(pos)))
595+
)
596+
or
597+
// creation of an optional via implicit conversion,
598+
// i.e. from `f(x)` where `x: T` into `f(.some(x))` where the context `f` expects a `T?`.
599+
exists(InjectIntoOptionalExpr e |
600+
e.convertsFrom(node1.asExpr()) and
601+
node2 = node1 and // HACK: we should ideally have a separate Node case for the (hidden) InjectIntoOptionalExpr
602+
c instanceof OptionalSomeContentSet
603+
)
604+
or
605+
// creation of an optional by returning from a failable initializer (`init?`)
606+
exists(ConstructorDecl init |
607+
node1.asExpr().(CallExpr).getStaticTarget() = init and
608+
node2 = node1 and // HACK: again, we should ideally have a separate Node case here, and not reuse the CallExpr
609+
c instanceof OptionalSomeContentSet and
610+
init.isFailable()
611+
)
612+
or
561613
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, c, node2)
562614
}
563615

@@ -578,6 +630,34 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
578630
node2.asExpr() = tuple and
579631
c.isSingleton(any(Content::TupleContent tc | tc.getIndex() = tuple.getIndex()))
580632
)
633+
or
634+
// read of an enum member via `case let .variant(v1, v2)` pattern matching
635+
exists(EnumElementPattern enumPat, ParamDecl enumParam, Pattern subPat |
636+
node1.asPattern() = enumPat and
637+
node2.asPattern() = subPat and
638+
c.isSingleton(any(Content::EnumContent ec | ec.getParam() = enumParam))
639+
|
640+
exists(int idx |
641+
enumPat.getElement().getParam(idx) = enumParam and
642+
enumPat.getSubPattern(idx) = subPat
643+
)
644+
)
645+
or
646+
// read of a tuple member via `case let (v1, v2)` pattern matching
647+
exists(TuplePattern tupPat, int idx, Pattern subPat |
648+
node1.asPattern() = tupPat and
649+
node2.asPattern() = subPat and
650+
c.isSingleton(any(Content::TupleContent tc | tc.getIndex() = idx))
651+
|
652+
tupPat.getElement(idx) = subPat
653+
)
654+
or
655+
// read of an optional .some member via `let x: T = y: T?` pattern matching
656+
exists(OptionalSomePattern pat |
657+
node1.asPattern() = pat and
658+
node2.asPattern() = pat.getSubPattern() and
659+
c instanceof OptionalSomeContentSet
660+
)
581661
}
582662

583663
/**
@@ -595,6 +675,22 @@ predicate clearsContent(Node n, ContentSet c) {
595675
*/
596676
predicate expectsContent(Node n, ContentSet c) { none() }
597677

678+
/**
679+
* The global singleton `Optional.some` content set.
680+
*/
681+
private class OptionalSomeContentSet extends ContentSet {
682+
OptionalSomeContentSet() {
683+
exists(EnumDecl optional, EnumElementDecl some |
684+
some.getDeclaringDecl() = optional and
685+
some.getName() = "some" and
686+
optional.getName() = "Optional" and
687+
optional.getModule().getName() = "Swift"
688+
|
689+
this.isSingleton(any(Content::EnumContent ec | ec.getParam() = some.getParam(0)))
690+
)
691+
}
692+
}
693+
598694
private newtype TDataFlowType = TODO_DataFlowType()
599695

600696
class DataFlowType extends TDataFlowType {

swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPublic.qll

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ class Node extends TNode {
3737
*/
3838
Expr asExpr() { none() }
3939

40+
/**
41+
* Gets this node's underlying pattern, if any.
42+
*/
43+
Pattern asPattern() { none() }
44+
4045
/**
4146
* Gets this data flow node's corresponding control flow node.
4247
*/
@@ -66,6 +71,20 @@ class ExprNode extends Node, TExprNode {
6671
override ControlFlowNode getCfgNode() { result = n }
6772
}
6873

74+
/**
75+
* A pattern, viewed as a node in a data flow graph.
76+
*/
77+
class PatternNode extends Node, TPatternNode {
78+
CfgNode n;
79+
Pattern pattern;
80+
81+
PatternNode() { this = TPatternNode(n, pattern) }
82+
83+
override Pattern asPattern() { result = pattern }
84+
85+
override ControlFlowNode getCfgNode() { result = n }
86+
}
87+
6988
/**
7089
* The value of a parameter at function entry, viewed as a node in a data
7190
* flow graph.
@@ -171,6 +190,20 @@ module Content {
171190

172191
override string toString() { result = "Tuple element at index " + index.toString() }
173192
}
193+
194+
/** A parameter of an enum element. */
195+
class EnumContent extends Content, TEnumContent {
196+
private ParamDecl p;
197+
198+
EnumContent() { this = TEnumContent(p) }
199+
200+
/** Gets the declaration of the enum parameter. */
201+
ParamDecl getParam() { result = p }
202+
203+
override string toString() {
204+
exists(EnumElementDecl d, int pos | d.getParam(pos) = p | result = d.toString() + ":" + pos)
205+
}
206+
}
174207
}
175208

176209
/**
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
private import codeql.swift.generated.decl.ConstructorDecl
22
private import codeql.swift.elements.decl.MethodDecl
3+
private import codeql.swift.elements.type.FunctionType
4+
private import codeql.swift.elements.type.OptionalType
35

46
/**
57
* An initializer of a class, struct, enum or protocol.
68
*/
79
class ConstructorDecl extends Generated::ConstructorDecl, MethodDecl {
810
override string toString() { result = this.getSelfParam().getType() + "." + super.toString() }
11+
12+
/** Holds if this initializer returns an optional type. Failable initializers are written as `init?`. */
13+
predicate isFailable() {
14+
this.getInterfaceType().(FunctionType).getResult().(FunctionType).getResult() instanceof
15+
OptionalType
16+
}
917
}

0 commit comments

Comments
 (0)