Skip to content

Commit 4aed638

Browse files
authored
Merge pull request github#14577 from MathiasVP/capture-flow-swift
Swift: Add variable-capture flow
2 parents c1a1ebf + 68999f3 commit 4aed638

File tree

19 files changed

+641
-29
lines changed

19 files changed

+641
-29
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Improved support for flow through captured variables that properly adheres to inter-procedural control flow.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ class ApplyExprCfgNode extends ExprCfgNode {
173173

174174
Callable getStaticTarget() { result = e.getStaticTarget() }
175175

176-
Expr getFunction() { result = e.getFunction() }
176+
CfgNode getFunction() { result.getAst() = e.getFunction() }
177177
}
178178

179179
class CallExprCfgNode extends ApplyExprCfgNode {

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

Lines changed: 250 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ private import codeql.swift.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
1010
private import codeql.swift.frameworks.StandardLibrary.PointerTypes
1111
private import codeql.swift.frameworks.StandardLibrary.Array
1212
private import codeql.swift.frameworks.StandardLibrary.Dictionary
13+
private import codeql.dataflow.VariableCapture as VariableCapture
1314

1415
/** Gets the callable in which this node occurs. */
1516
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.(NodeImpl).getEnclosingCallable() }
@@ -98,6 +99,16 @@ private class SsaDefinitionNodeImpl extends SsaDefinitionNode, NodeImpl {
9899
}
99100
}
100101

102+
private class CaptureNodeImpl extends CaptureNode, NodeImpl {
103+
override Location getLocationImpl() { result = this.getSynthesizedCaptureNode().getLocation() }
104+
105+
override string toStringImpl() { result = this.getSynthesizedCaptureNode().toString() }
106+
107+
override DataFlowCallable getEnclosingCallable() {
108+
result.asSourceCallable() = this.getSynthesizedCaptureNode().getEnclosingCallable()
109+
}
110+
}
111+
101112
private predicate localFlowSsaInput(Node nodeFrom, Ssa::Definition def, Ssa::Definition next) {
102113
exists(BasicBlock bb, int i | def.lastRefRedef(bb, i, next) |
103114
def.definesAt(_, bb, i) and
@@ -140,19 +151,22 @@ private module Cached {
140151
[
141152
any(Argument arg | modifiable(arg)).getExpr(), any(MemberRefExpr ref).getBase(),
142153
any(ApplyExpr apply).getQualifier(), any(TupleElementExpr te).getSubExpr(),
143-
any(SubscriptExpr se).getBase()
154+
any(SubscriptExpr se).getBase(),
155+
any(ApplyExpr apply | not exists(apply.getStaticTarget())).getFunction()
144156
])
145157
} or
146158
TDictionarySubscriptNode(SubscriptExpr e) {
147159
e.getBase().getType().getCanonicalType() instanceof CanonicalDictionaryType
148-
}
160+
} or
161+
TCaptureNode(CaptureFlow::SynthesizedCaptureNode cn) or
162+
TClosureSelfParameterNode(ClosureExpr closure)
149163

150164
private predicate localSsaFlowStepUseUse(Ssa::Definition def, Node nodeFrom, Node nodeTo) {
151165
def.adjacentReadPair(nodeFrom.getCfgNode(), nodeTo.getCfgNode()) and
152166
(
153-
nodeTo instanceof InoutReturnNode
167+
nodeTo instanceof InoutReturnNodeImpl
154168
implies
155-
nodeTo.(InoutReturnNode).getParameter() = def.getSourceVariable().asVarDecl()
169+
nodeTo.(InoutReturnNodeImpl).getParameter() = def.getSourceVariable().asVarDecl()
156170
)
157171
}
158172

@@ -167,7 +181,7 @@ private module Cached {
167181
* Holds if `nodeFrom` is a parameter node, and `nodeTo` is a corresponding SSA node.
168182
*/
169183
private predicate localFlowSsaParamInput(Node nodeFrom, Node nodeTo) {
170-
nodeTo = getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
184+
nodeTo = getParameterDefNode(nodeFrom.asParameter())
171185
}
172186

173187
private predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
@@ -180,9 +194,9 @@ private module Cached {
180194
nodeFrom.asDefinition() = def and
181195
nodeTo.getCfgNode() = def.getAFirstRead() and
182196
(
183-
nodeTo instanceof InoutReturnNode
197+
nodeTo instanceof InoutReturnNodeImpl
184198
implies
185-
nodeTo.(InoutReturnNode).getParameter() = def.getSourceVariable().asVarDecl()
199+
nodeTo.(InoutReturnNodeImpl).getParameter() = def.getSourceVariable().asVarDecl()
186200
)
187201
or
188202
// use-use flow
@@ -279,6 +293,9 @@ private module Cached {
279293
// flow through a flow summary (extension of `SummaryModelCsv`)
280294
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
281295
nodeTo.(FlowSummaryNode).getSummaryNode(), true)
296+
or
297+
// flow step according to the closure capture library
298+
captureValueStep(nodeFrom, nodeTo)
282299
}
283300

284301
/**
@@ -305,7 +322,8 @@ private module Cached {
305322
TFieldContent(FieldDecl f) or
306323
TTupleContent(int index) { exists(any(TupleExpr te).getElement(index)) } or
307324
TEnumContent(ParamDecl f) { exists(EnumElementDecl d | d.getAParam() = f) } or
308-
TCollectionContent()
325+
TCollectionContent() or
326+
TCapturedVariableContent(CapturedVariable v)
309327
}
310328

311329
/**
@@ -342,7 +360,9 @@ private predicate hasPatternNode(PatternCfgNode n, Pattern p) {
342360
import Cached
343361

344362
/** Holds if `n` should be hidden from path explanations. */
345-
predicate nodeIsHidden(Node n) { n instanceof FlowSummaryNode }
363+
predicate nodeIsHidden(Node n) {
364+
n instanceof FlowSummaryNode or n instanceof ClosureSelfParameterNode
365+
}
346366

347367
/**
348368
* The intermediate node for a dictionary subscript operation `dict[key]`. In a write, this is used
@@ -396,6 +416,25 @@ private module ParameterNodes {
396416
override ParamDecl getParameter() { result = param }
397417
}
398418

419+
class ClosureSelfParameterNode extends ParameterNodeImpl, TClosureSelfParameterNode {
420+
ClosureExpr closure;
421+
422+
ClosureSelfParameterNode() { this = TClosureSelfParameterNode(closure) }
423+
424+
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
425+
c.asSourceCallable() = closure and
426+
pos instanceof TThisParameter
427+
}
428+
429+
override Location getLocationImpl() { result = closure.getLocation() }
430+
431+
override string toStringImpl() { result = "closure self parameter" }
432+
433+
override DataFlowCallable getEnclosingCallable() { this.isParameterOf(result, _) }
434+
435+
ClosureExpr getClosure() { result = closure }
436+
}
437+
399438
class SummaryParameterNode extends ParameterNodeImpl, FlowSummaryNode {
400439
SummaryParameterNode() {
401440
FlowSummaryImpl::Private::summaryParameterNode(this.getSummaryNode(), _)
@@ -610,6 +649,18 @@ private module ArgumentNodes {
610649
pos = TPositionalArgument(0)
611650
}
612651
}
652+
653+
class SelfClosureArgumentNode extends ExprNode, ArgumentNode {
654+
ApplyExprCfgNode apply;
655+
656+
SelfClosureArgumentNode() { n = apply.getFunction() }
657+
658+
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
659+
apply = call.asCall() and
660+
not exists(apply.getStaticTarget()) and
661+
pos instanceof ThisArgumentPosition
662+
}
663+
}
613664
}
614665

615666
import ArgumentNodes
@@ -785,6 +836,175 @@ private module OutNodes {
785836

786837
import OutNodes
787838

839+
/**
840+
* Holds if there is a data flow step from `e1` to `e2` that only steps from
841+
* child to parent in the AST.
842+
*/
843+
private predicate simpleAstFlowStep(Expr e1, Expr e2) {
844+
e2.(IfExpr).getBranch(_) = e1
845+
or
846+
e2.(AssignExpr).getSource() = e1
847+
or
848+
e2.(ArrayExpr).getAnElement() = e1
849+
}
850+
851+
private predicate closureFlowStep(CaptureInput::Expr e1, CaptureInput::Expr e2) {
852+
simpleAstFlowStep(e1, e2)
853+
or
854+
exists(Ssa::WriteDefinition def |
855+
def.getARead().getNode().asAstNode() = e2 and
856+
def.assigns(any(CfgNode cfg | cfg.getNode().asAstNode() = e1))
857+
)
858+
or
859+
e2.(Pattern).getImmediateMatchingExpr() = e1
860+
}
861+
862+
private module CaptureInput implements VariableCapture::InputSig {
863+
private import swift as S
864+
private import codeql.swift.controlflow.BasicBlocks as B
865+
866+
class Location = S::Location;
867+
868+
class BasicBlock instanceof B::BasicBlock {
869+
string toString() { result = super.toString() }
870+
871+
Callable getEnclosingCallable() { result = super.getScope() }
872+
873+
Location getLocation() { result = super.getLocation() }
874+
}
875+
876+
BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) {
877+
result.(B::BasicBlock).immediatelyDominates(bb)
878+
}
879+
880+
BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.(B::BasicBlock).getASuccessor() }
881+
882+
class CapturedVariable instanceof S::VarDecl {
883+
CapturedVariable() {
884+
any(S::CapturedDecl capturedDecl).getDecl() = this and
885+
exists(this.getEnclosingCallable())
886+
}
887+
888+
string toString() { result = super.toString() }
889+
890+
Callable getCallable() { result = super.getEnclosingCallable() }
891+
892+
Location getLocation() { result = super.getLocation() }
893+
}
894+
895+
class CapturedParameter extends CapturedVariable instanceof S::ParamDecl { }
896+
897+
class Expr instanceof S::AstNode {
898+
string toString() { result = super.toString() }
899+
900+
Location getLocation() { result = super.getLocation() }
901+
902+
predicate hasCfgNode(BasicBlock bb, int i) {
903+
this = bb.(B::BasicBlock).getNode(i).getNode().asAstNode()
904+
}
905+
}
906+
907+
class VariableWrite extends Expr {
908+
CapturedVariable variable;
909+
Expr source;
910+
911+
VariableWrite() {
912+
exists(S::Assignment a | this = a |
913+
a.getDest().(DeclRefExpr).getDecl() = variable and
914+
source = a.getSource()
915+
)
916+
or
917+
exists(S::NamedPattern np | this = np |
918+
variable = np.getVarDecl() and
919+
source = np.getMatchingExpr()
920+
)
921+
}
922+
923+
CapturedVariable getVariable() { result = variable }
924+
925+
Expr getSource() { result = source }
926+
}
927+
928+
class VariableRead extends Expr instanceof S::DeclRefExpr {
929+
CapturedVariable v;
930+
931+
VariableRead() { this.getDecl() = v and not isLValue(this) }
932+
933+
CapturedVariable getVariable() { result = v }
934+
}
935+
936+
class ClosureExpr extends Expr instanceof S::Callable {
937+
ClosureExpr() { any(S::CapturedDecl c).getScope() = this }
938+
939+
predicate hasBody(Callable body) { this = body }
940+
941+
predicate hasAliasedAccess(Expr f) { closureFlowStep+(this, f) and not closureFlowStep(f, _) }
942+
}
943+
944+
class Callable extends S::Callable {
945+
predicate isConstructor() {
946+
// A class declaration cannot capture a variable in Swift. Consider this hypothetical example:
947+
// ```
948+
// protocol Interface { }
949+
// func foo() -> Interface {
950+
// let y = 42
951+
// class Impl : Interface {
952+
// let x : Int
953+
// init() {
954+
// x = y
955+
// }
956+
// }
957+
// let object = Impl()
958+
// return object
959+
// }
960+
// ```
961+
// The Swift compiler will reject this with an error message such as
962+
// ```
963+
// error: class declaration cannot close over value 'y' defined in outer scope
964+
// x = y
965+
// ^
966+
// ```
967+
none()
968+
}
969+
}
970+
}
971+
972+
class CapturedVariable = CaptureInput::CapturedVariable;
973+
974+
class CapturedParameter = CaptureInput::CapturedParameter;
975+
976+
module CaptureFlow = VariableCapture::Flow<CaptureInput>;
977+
978+
private CaptureFlow::ClosureNode asClosureNode(Node n) {
979+
result = n.(CaptureNode).getSynthesizedCaptureNode()
980+
or
981+
result.(CaptureFlow::ExprNode).getExpr() = n.asExpr()
982+
or
983+
result.(CaptureFlow::ExprPostUpdateNode).getExpr() =
984+
n.(PostUpdateNode).getPreUpdateNode().asExpr()
985+
or
986+
result.(CaptureFlow::ParameterNode).getParameter() = n.asParameter()
987+
or
988+
result.(CaptureFlow::ThisParameterNode).getCallable() = n.(ClosureSelfParameterNode).getClosure()
989+
or
990+
exists(CaptureInput::VariableWrite write |
991+
result.(CaptureFlow::VariableWriteSourceNode).getVariableWrite() = write and
992+
n.asExpr() = write.getSource()
993+
)
994+
}
995+
996+
private predicate captureStoreStep(Node node1, Content::CapturedVariableContent c, Node node2) {
997+
CaptureFlow::storeStep(asClosureNode(node1), c.getVariable(), asClosureNode(node2))
998+
}
999+
1000+
private predicate captureReadStep(Node node1, Content::CapturedVariableContent c, Node node2) {
1001+
CaptureFlow::readStep(asClosureNode(node1), c.getVariable(), asClosureNode(node2))
1002+
}
1003+
1004+
predicate captureValueStep(Node node1, Node node2) {
1005+
CaptureFlow::localFlowStep(asClosureNode(node1), asClosureNode(node2))
1006+
}
1007+
7881008
predicate jumpStep(Node pred, Node succ) {
7891009
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(),
7901010
succ.(FlowSummaryNode).getSummaryNode())
@@ -897,6 +1117,8 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
8971117
or
8981118
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
8991119
node2.(FlowSummaryNode).getSummaryNode())
1120+
or
1121+
captureStoreStep(node1, any(Content::CapturedVariableContent cvc | c.isSingleton(cvc)), node2)
9001122
}
9011123

9021124
predicate isLValue(Expr e) { any(AssignExpr assign).getDest() = e }
@@ -1001,6 +1223,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
10011223
or
10021224
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c,
10031225
node2.(FlowSummaryNode).getSummaryNode())
1226+
or
1227+
captureReadStep(node1, any(Content::CapturedVariableContent cvc | c.isSingleton(cvc)), node2)
10041228
}
10051229

10061230
/**
@@ -1094,6 +1318,17 @@ private module PostUpdateNodes {
10941318
result.(FlowSummaryNode).getSummaryNode())
10951319
}
10961320
}
1321+
1322+
class CapturePostUpdateNode extends PostUpdateNodeImpl, CaptureNode {
1323+
private CaptureNode pre;
1324+
1325+
CapturePostUpdateNode() {
1326+
CaptureFlow::capturePostUpdateNode(this.getSynthesizedCaptureNode(),
1327+
pre.getSynthesizedCaptureNode())
1328+
}
1329+
1330+
override Node getPreUpdateNode() { result = pre }
1331+
}
10971332
}
10981333

10991334
private import PostUpdateNodes
@@ -1152,7 +1387,12 @@ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preserves
11521387
* One example would be to allow flow like `p.foo = p.bar;`, which is disallowed
11531388
* by default as a heuristic.
11541389
*/
1155-
predicate allowParameterReturnInSelf(ParameterNode p) { none() }
1390+
predicate allowParameterReturnInSelf(ParameterNode p) {
1391+
exists(Callable c |
1392+
c = p.(ParameterNodeImpl).getEnclosingCallable().asSourceCallable() and
1393+
CaptureFlow::heuristicAllowInstanceParameterReturnInSelf(c)
1394+
)
1395+
}
11561396

11571397
/** An approximated `Content`. */
11581398
class ContentApprox = Unit;

0 commit comments

Comments
 (0)