Skip to content

Commit f46ea32

Browse files
committed
Swift: Add dataflow through key-path expressios by modeling them as lambdas that perform a sequence of read steps.
1 parent 21b0392 commit f46ea32

File tree

5 files changed

+206
-13
lines changed

5 files changed

+206
-13
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,15 @@ class ApplyExprCfgNode extends ExprCfgNode {
179179
class CallExprCfgNode extends ApplyExprCfgNode {
180180
override CallExpr e;
181181
}
182+
183+
class KeyPathApplicationExprCfgNode extends ExprCfgNode {
184+
override KeyPathApplicationExpr e;
185+
186+
CfgNode getKeyPath() { result.getAst() = e.getKeyPath() }
187+
188+
CfgNode getBase() { result.getAst() = e.getBase() }
189+
}
190+
191+
class KeyPathExprCfgNode extends ExprCfgNode {
192+
override KeyPathExpr e;
193+
}

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

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,39 @@ module Ssa {
2121

2222
class ExitBasicBlock = BasicBlocks::ExitBasicBlock;
2323

24-
class SourceVariable = VarDecl;
24+
private newtype TSourceVariable =
25+
TNormalSourceVariable(VarDecl v) or
26+
TKeyPathSourceVariable(EntryNode entry) { entry.getScope() instanceof KeyPathExpr }
27+
28+
abstract class SourceVariable extends TSourceVariable {
29+
abstract string toString();
30+
31+
VarDecl asVarDecl() { none() }
32+
33+
EntryNode asKeyPath() { none() }
34+
35+
DeclRefExpr getAnAccess() { result.getDecl() = this.asVarDecl() }
36+
}
37+
38+
private class NormalSourceVariable extends SourceVariable, TNormalSourceVariable {
39+
VarDecl v;
40+
41+
NormalSourceVariable() { this = TNormalSourceVariable(v) }
42+
43+
override string toString() { result = v.toString() }
44+
45+
override VarDecl asVarDecl() { result = v }
46+
}
47+
48+
private class KeyPathSourceVariable extends SourceVariable, TKeyPathSourceVariable {
49+
EntryNode enter;
50+
51+
KeyPathSourceVariable() { this = TKeyPathSourceVariable(enter) }
52+
53+
override string toString() { result = enter.toString() }
54+
55+
override EntryNode asKeyPath() { result = enter }
56+
}
2557

2658
predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) {
2759
exists(AssignExpr assign |
@@ -40,17 +72,22 @@ module Ssa {
4072
// ```
4173
exists(NamedPattern pattern |
4274
bb.getNode(i).getNode().asAstNode() = pattern and
43-
v = pattern.getVarDecl() and
75+
v.asVarDecl() = pattern.getVarDecl() and
4476
certain = true
4577
)
4678
or
47-
v instanceof ParamDecl and
48-
bb.getNode(i).getNode().asAstNode() = v and
79+
exists(ParamDecl p |
80+
p = v.asVarDecl() and
81+
bb.getNode(i).getNode().asAstNode() = p and
82+
certain = true
83+
)
84+
or
85+
bb.getNode(i) = v.asKeyPath() and
4986
certain = true
5087
or
5188
// Mark the subexpression as a write of the local variable declared in the `TapExpr`.
5289
exists(TapExpr tap |
53-
v = tap.getVar() and
90+
v.asVarDecl() = tap.getVar() and
5491
bb.getNode(i).getNode().asAstNode() = tap.getSubExpr() and
5592
certain = true
5693
)
@@ -60,7 +97,7 @@ module Ssa {
6097
exists(DeclRefExpr ref |
6198
not isLValue(ref) and
6299
bb.getNode(i).getNode().asAstNode() = ref and
63-
v = ref.getDecl() and
100+
v.asVarDecl() = ref.getDecl() and
64101
certain = true
65102
)
66103
or
@@ -71,17 +108,16 @@ module Ssa {
71108
)
72109
or
73110
exists(ExitNode exit, AbstractFunctionDecl func |
74-
func.getAParam() = v or func.getSelfParam() = v
75-
|
111+
[func.getAParam(), func.getSelfParam()] = v.asVarDecl() and
76112
bb.getNode(i) = exit and
77-
modifiableParam(v) and
113+
modifiableParam(v.asVarDecl()) and
78114
bb.getScope() = func and
79115
certain = true
80116
)
81117
or
82118
// Mark the `TapExpr` as a read of the of the local variable.
83119
exists(TapExpr tap |
84-
v = tap.getVar() and
120+
v.asVarDecl() = tap.getVar() and
85121
bb.getNode(i).getNode().asAstNode() = tap and
86122
certain = true
87123
)
@@ -97,7 +133,7 @@ module Ssa {
97133

98134
cached
99135
ControlFlowNode getARead() {
100-
exists(VarDecl v, SsaInput::BasicBlock bb, int i |
136+
exists(SsaInput::SourceVariable v, SsaInput::BasicBlock bb, int i |
101137
SsaImpl::ssaDefReachesRead(v, this, bb, i) and
102138
SsaInput::variableRead(bb, i, v, true) and
103139
result = bb.getNode(i)

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ newtype TDataFlowCall =
7474
TPropertyGetterCall(PropertyGetterCfgNode getter) or
7575
TPropertySetterCall(PropertySetterCfgNode setter) or
7676
TPropertyObserverCall(PropertyObserverCfgNode observer) or
77+
TKeyPathCall(KeyPathApplicationExprCfgNode keyPathApplication) or
7778
TSummaryCall(FlowSummaryImpl::Public::SummarizedCallable c, Node receiver) {
7879
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
7980
}
@@ -89,6 +90,9 @@ class DataFlowCall extends TDataFlowCall {
8990
/** Gets the underlying source code call, if any. */
9091
ApplyExprCfgNode asCall() { none() }
9192

93+
/** Gets the underlying key-path application node, if any. */
94+
KeyPathApplicationExprCfgNode asKeyPath() { none() }
95+
9296
/**
9397
* Gets the i'th argument of call.class
9498
* The qualifier is considered to have index `-1`.
@@ -138,6 +142,25 @@ private class NormalCall extends DataFlowCall, TNormalCall {
138142
override Location getLocation() { result = apply.getLocation() }
139143
}
140144

145+
private class KeyPathCall extends DataFlowCall, TKeyPathCall {
146+
private KeyPathApplicationExprCfgNode apply;
147+
148+
KeyPathCall() { this = TKeyPathCall(apply) }
149+
150+
override KeyPathApplicationExprCfgNode asKeyPath() { result = apply }
151+
152+
override CfgNode getArgument(int i) {
153+
i = -1 and
154+
result = apply.getBase()
155+
}
156+
157+
override DataFlowCallable getEnclosingCallable() { result = TDataFlowFunc(apply.getScope()) }
158+
159+
override string toString() { result = apply.toString() }
160+
161+
override Location getLocation() { result = apply.getLocation() }
162+
}
163+
141164
class PropertyGetterCall extends DataFlowCall, TPropertyGetterCall {
142165
private PropertyGetterCfgNode getter;
143166

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

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

43+
private class KeyPathComponentNodeImpl extends TKeyPathComponentNode, NodeImpl {
44+
KeyPathComponent component;
45+
46+
KeyPathComponentNodeImpl() { this = TKeyPathComponentNode(component) }
47+
48+
override Location getLocationImpl() { result = component.getLocation() }
49+
50+
override string toStringImpl() { result = component.toString() }
51+
52+
override DataFlowCallable getEnclosingCallable() {
53+
result.asSourceCallable() = component.getKeyPathExpr()
54+
}
55+
56+
KeyPathComponent getComponent() { result = component }
57+
}
58+
4359
private class PatternNodeImpl extends PatternNode, NodeImpl {
4460
override Location getLocationImpl() { result = pattern.getLocation() }
4561

@@ -78,6 +94,9 @@ private module Cached {
7894
FlowSummaryImpl::Private::summaryNodeRange(c, state)
7995
} or
8096
TSourceParameterNode(ParamDecl param) or
97+
TKeyPathParameterNode(EntryNode entry) { entry.getScope() instanceof KeyPathExpr } or
98+
TKeyPathReturnNode(ExitNode entry) { entry.getScope() instanceof KeyPathExpr } or
99+
TKeyPathComponentNode(KeyPathComponent component) or
81100
TSummaryParameterNode(FlowSummary::SummarizedCallable c, ParameterPosition pos) {
82101
FlowSummaryImpl::Private::summaryParameterNodeRange(c, pos)
83102
} or
@@ -105,7 +124,7 @@ private module Cached {
105124
(
106125
nodeTo instanceof InoutReturnNode
107126
implies
108-
nodeTo.(InoutReturnNode).getParameter() = def.getSourceVariable()
127+
nodeTo.(InoutReturnNode).getParameter() = def.getSourceVariable().asVarDecl()
109128
)
110129
}
111130

@@ -135,7 +154,7 @@ private module Cached {
135154
(
136155
nodeTo instanceof InoutReturnNode
137156
implies
138-
nodeTo.(InoutReturnNode).getParameter() = def.getSourceVariable()
157+
nodeTo.(InoutReturnNode).getParameter() = def.getSourceVariable().asVarDecl()
139158
)
140159
or
141160
// use-use flow
@@ -198,6 +217,11 @@ private module Cached {
198217
nodeFrom.asPattern().(TypedPattern).getSubPattern()
199218
]
200219
or
220+
// Flow from the unique parameter of a key path expression to
221+
// the first component in the chain.
222+
nodeTo.(KeyPathComponentNodeImpl).getComponent() =
223+
nodeFrom.(KeyPathParameterNode).getComponent(0)
224+
or
201225
// flow through a flow summary (extension of `SummaryModelCsv`)
202226
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
203227
}
@@ -311,6 +335,28 @@ private module ParameterNodes {
311335

312336
override DataFlowCallable getEnclosingCallable() { this.isParameterOf(result, _) }
313337
}
338+
339+
class KeyPathParameterNode extends ParameterNodeImpl, TKeyPathParameterNode {
340+
private EntryNode entry;
341+
342+
KeyPathParameterNode() { this = TKeyPathParameterNode(entry) }
343+
344+
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
345+
c.asSourceCallable() = entry.getScope() and pos = TThisParameter()
346+
}
347+
348+
override Location getLocationImpl() { result = entry.getLocation() }
349+
350+
override string toStringImpl() { result = entry.toString() }
351+
352+
override DataFlowCallable getEnclosingCallable() { this.isParameterOf(result, _) }
353+
354+
KeyPathComponent getComponent(int i) { result = entry.getScope().(KeyPathExpr).getComponent(i) }
355+
356+
KeyPathComponent getAComponent() { result = this.getComponent(_) }
357+
358+
KeyPathExpr getKeyPathExpr() { result = entry.getScope() }
359+
}
314360
}
315361

316362
import ParameterNodes
@@ -412,6 +458,17 @@ private module ArgumentNodes {
412458
FlowSummaryImpl::Private::summaryArgumentNode(call, this, pos)
413459
}
414460
}
461+
462+
class KeyPathArgumentNode extends ExprNode, ArgumentNode {
463+
private KeyPathApplicationExprCfgNode keyPath;
464+
465+
KeyPathArgumentNode() { keyPath.getBase() = this.getCfgNode() }
466+
467+
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
468+
call.asKeyPath() = keyPath and
469+
pos = TThisArgument()
470+
}
471+
}
415472
}
416473

417474
import ArgumentNodes
@@ -474,6 +531,24 @@ private module ReturnNodes {
474531

475532
override ReturnKind getKind() { result = rk }
476533
}
534+
535+
class KeyPathReturnNodeImpl extends ReturnNode, TKeyPathReturnNode, NodeImpl {
536+
ExitNode exit;
537+
538+
KeyPathReturnNodeImpl() { this = TKeyPathReturnNode(exit) }
539+
540+
override ReturnKind getKind() { result instanceof NormalReturnKind }
541+
542+
override ControlFlowNode getCfgNode() { result = exit }
543+
544+
override DataFlowCallable getEnclosingCallable() { result.asSourceCallable() = exit.getScope() }
545+
546+
override Location getLocationImpl() { result = exit.getLocation() }
547+
548+
override string toStringImpl() { result = exit.toString() }
549+
550+
KeyPathExpr getKeyPathExpr() { result = exit.getScope() }
551+
}
477552
}
478553

479554
import ReturnNodes
@@ -495,6 +570,16 @@ private module OutNodes {
495570
}
496571
}
497572

573+
class KeyPathOutNode extends OutNode, ExprNodeImpl {
574+
KeyPathApplicationExprCfgNode keyPath;
575+
576+
KeyPathOutNode() { keyPath = this.getCfgNode() }
577+
578+
override DataFlowCall getCall(ReturnKind kind) {
579+
result.asKeyPath() = keyPath and kind instanceof NormalReturnKind
580+
}
581+
}
582+
498583
class SummaryOutNode extends OutNode, SummaryNode {
499584
SummaryOutNode() { FlowSummaryImpl::Private::summaryOutNode(_, this, _) }
500585

@@ -658,6 +743,20 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
658743
node2.asPattern() = pat.getSubPattern() and
659744
c instanceof OptionalSomeContentSet
660745
)
746+
or
747+
// read of a component in a key-path expression chain
748+
exists(KeyPathComponent component, FieldDecl f |
749+
component = node1.(KeyPathComponentNodeImpl).getComponent() and
750+
f = component.getDeclRef() and
751+
c.isSingleton(any(Content::FieldContent ct | ct.getField() = f))
752+
|
753+
// the next node is either the next element in the chain
754+
node2.(KeyPathComponentNodeImpl).getComponent() = component.getNextComponent()
755+
or
756+
// or the return node, if this is the last component in the chain
757+
not exists(component.getNextComponent()) and
758+
node2.(KeyPathReturnNodeImpl).getKeyPathExpr() = component.getKeyPathExpr()
759+
)
661760
}
662761

663762
/**
@@ -786,6 +885,9 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
786885
or
787886
kind = TLambdaCallKind() and
788887
receiver = call.(SummaryCall).getReceiver()
888+
or
889+
kind = TLambdaCallKind() and
890+
receiver.asExpr() = call.asKeyPath().getExpr().(KeyPathApplicationExpr).getKeyPath()
789891
}
790892

791893
/** Extra data-flow steps needed for lambda flow analysis. */

swift/ql/lib/codeql/swift/elements/KeyPathComponent.qll

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
private import codeql.swift.generated.KeyPathComponent
2+
private import swift
23

34
class KeyPathComponent extends Generated::KeyPathComponent {
45
/**
@@ -35,4 +36,23 @@ class KeyPathComponent extends Generated::KeyPathComponent {
3536
* Tuple indexing like `.1`.
3637
*/
3738
predicate isTupleIndexing() { getKind() = 9 }
39+
40+
/** Gets the underlying key-path expression which this is a component of. */
41+
KeyPathExpr getKeyPathExpr() { result.getAComponent() = this }
42+
43+
/** Holds if this component is the i'th component of the underling key-path expression. */
44+
predicate hasIndex(int i) { any(KeyPathExpr e).getComponent(i) = this }
45+
46+
/** Gets the next component of the underlying key-path expression. */
47+
KeyPathComponent getNextComponent() {
48+
exists(int i, KeyPathExpr e |
49+
hasKeyPathExprAndIndex(e, i, this) and
50+
hasKeyPathExprAndIndex(e, i + 1, result)
51+
)
52+
}
53+
}
54+
55+
pragma[nomagic]
56+
private predicate hasKeyPathExprAndIndex(KeyPathExpr e, int i, KeyPathComponent c) {
57+
e.getComponent(i) = c
3858
}

0 commit comments

Comments
 (0)