-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Swift: Add variable-capture flow #14577
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 17 commits
050b8e6
3253c04
95a7d65
c04654d
21a369d
4e1b44a
8115774
af49a3a
5418d39
9de3cc7
9dbf7e8
310ebe4
1c298e6
3d5098a
56b49a4
6f37d7c
862de15
78e08cf
951b6be
11194e5
2465cc2
2ad121a
96a37f3
784bb72
63525a9
9e2dd09
93234c0
65e13aa
b41ec37
a5a7d27
9b150e4
68999f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ private import codeql.swift.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl | |
private import codeql.swift.frameworks.StandardLibrary.PointerTypes | ||
private import codeql.swift.frameworks.StandardLibrary.Array | ||
private import codeql.swift.frameworks.StandardLibrary.Dictionary | ||
private import codeql.dataflow.VariableCapture as VariableCapture | ||
|
||
/** Gets the callable in which this node occurs. */ | ||
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.(NodeImpl).getEnclosingCallable() } | ||
|
@@ -98,6 +99,16 @@ private class SsaDefinitionNodeImpl extends SsaDefinitionNode, NodeImpl { | |
} | ||
} | ||
|
||
private class CaptureNodeImpl extends CaptureNode, NodeImpl { | ||
override Location getLocationImpl() { result = this.getSynthesizedCaptureNode().getLocation() } | ||
|
||
override string toStringImpl() { result = this.getSynthesizedCaptureNode().toString() } | ||
|
||
override DataFlowCallable getEnclosingCallable() { | ||
result.asSourceCallable() = this.getSynthesizedCaptureNode().getEnclosingCallable() | ||
} | ||
} | ||
|
||
private predicate localFlowSsaInput(Node nodeFrom, Ssa::Definition def, Ssa::Definition next) { | ||
exists(BasicBlock bb, int i | def.lastRefRedef(bb, i, next) | | ||
def.definesAt(_, bb, i) and | ||
|
@@ -140,12 +151,14 @@ private module Cached { | |
[ | ||
any(Argument arg | modifiable(arg)).getExpr(), any(MemberRefExpr ref).getBase(), | ||
any(ApplyExpr apply).getQualifier(), any(TupleElementExpr te).getSubExpr(), | ||
any(SubscriptExpr se).getBase() | ||
any(SubscriptExpr se).getBase(), | ||
any(ApplyExpr apply | not exists(apply.getStaticTarget())).getFunction() | ||
]) | ||
} or | ||
TDictionarySubscriptNode(SubscriptExpr e) { | ||
e.getBase().getType().getCanonicalType() instanceof CanonicalDictionaryType | ||
} | ||
} or | ||
TCaptureNode(CaptureFlow::SynthesizedCaptureNode cn) | ||
|
||
private predicate localSsaFlowStepUseUse(Ssa::Definition def, Node nodeFrom, Node nodeTo) { | ||
def.adjacentReadPair(nodeFrom.getCfgNode(), nodeTo.getCfgNode()) and | ||
|
@@ -279,6 +292,9 @@ private module Cached { | |
// flow through a flow summary (extension of `SummaryModelCsv`) | ||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(), | ||
nodeTo.(FlowSummaryNode).getSummaryNode(), true) | ||
or | ||
// flow step according to the closure capture library | ||
captureValueStep(nodeFrom, nodeTo) | ||
} | ||
|
||
/** | ||
|
@@ -305,7 +321,8 @@ private module Cached { | |
TFieldContent(FieldDecl f) or | ||
TTupleContent(int index) { exists(any(TupleExpr te).getElement(index)) } or | ||
TEnumContent(ParamDecl f) { exists(EnumElementDecl d | d.getAParam() = f) } or | ||
TCollectionContent() | ||
TCollectionContent() or | ||
TCapturedVariableContent(CapturedVariable v) | ||
} | ||
|
||
/** | ||
|
@@ -366,12 +383,18 @@ private class DictionarySubscriptNode extends NodeImpl, TDictionarySubscriptNode | |
SubscriptExpr getExpr() { result = expr } | ||
} | ||
|
||
private class ClosureSelfReferenceNode extends ExprNodeImpl { | ||
override ClosureExpr expr; | ||
|
||
ClosureExpr getClosure() { result = expr } | ||
} | ||
|
||
private module ParameterNodes { | ||
abstract class ParameterNodeImpl extends NodeImpl { | ||
predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { none() } | ||
|
||
/** Gets the parameter associated with this node, if any. */ | ||
ParamDecl getParameter() { none() } | ||
override ParamDecl getParameter() { none() } | ||
} | ||
|
||
class SourceParameterNode extends ParameterNodeImpl, TSourceParameterNode { | ||
|
@@ -396,6 +419,13 @@ private module ParameterNodes { | |
override ParamDecl getParameter() { result = param } | ||
} | ||
|
||
class ClosureSelfParameterNode extends ParameterNodeImpl, ClosureSelfReferenceNode { | ||
MathiasVP marked this conversation as resolved.
Show resolved
Hide resolved
|
||
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { | ||
c.asSourceCallable() = this.getClosure() and | ||
pos instanceof ClosureSelfParameter | ||
} | ||
} | ||
|
||
class SummaryParameterNode extends ParameterNodeImpl, FlowSummaryNode { | ||
SummaryParameterNode() { | ||
FlowSummaryImpl::Private::summaryParameterNode(this.getSummaryNode(), _) | ||
|
@@ -610,6 +640,18 @@ private module ArgumentNodes { | |
pos = TPositionalArgument(0) | ||
} | ||
} | ||
|
||
class SelfClosureArgumentNode extends ExprNode, ArgumentNode { | ||
ApplyExprCfgNode apply; | ||
|
||
SelfClosureArgumentNode() { n = apply.getFunction() } | ||
|
||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) { | ||
apply = call.asCall() and | ||
not exists(apply.getStaticTarget()) and | ||
pos instanceof ClosureSelfArgument | ||
} | ||
} | ||
} | ||
|
||
import ArgumentNodes | ||
|
@@ -658,7 +700,7 @@ private module ReturnNodes { | |
result = TDataFlowFunc(param.getDeclaringFunction()) | ||
} | ||
|
||
ParamDecl getParameter() { result = param } | ||
override ParamDecl getParameter() { result = param } | ||
|
||
override Location getLocationImpl() { result = exit.getLocation() } | ||
|
||
|
@@ -785,6 +827,156 @@ private module OutNodes { | |
|
||
import OutNodes | ||
|
||
/** | ||
* Holds if there is a data flow step from `e1` to `e2` that only steps from | ||
* child to parent in the AST. | ||
*/ | ||
private predicate simpleAstFlowStep(Expr e1, Expr e2) { | ||
e2.(IfExpr).getBranch(_) = e1 | ||
or | ||
e2.(AssignExpr).getSource() = e1 | ||
or | ||
e2.(ArrayExpr).getAnElement() = e1 | ||
} | ||
|
||
private predicate closureFlowStep(CaptureInput::Expr e1, CaptureInput::Expr e2) { | ||
simpleAstFlowStep(e1, e2) | ||
or | ||
exists(Ssa::WriteDefinition def | | ||
def.getARead().getNode().asAstNode() = e2 and | ||
def.assigns(any(CfgNode cfg | cfg.getNode().asAstNode() = e1)) | ||
) | ||
or | ||
e2.(Pattern).getImmediateMatchingExpr() = e1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the declared type of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's why compilation doesn't fail. I'm asking whether There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'll leave this to another PR. I've discovered that the var x = 0 the |
||
} | ||
|
||
private module CaptureInput implements VariableCapture::InputSig { | ||
private import swift as S | ||
private import codeql.swift.controlflow.BasicBlocks as B | ||
|
||
class Location = S::Location; | ||
|
||
class BasicBlock instanceof B::BasicBlock { | ||
string toString() { result = super.toString() } | ||
|
||
Callable getEnclosingCallable() { result = super.getScope() } | ||
|
||
Location getLocation() { result = super.getLocation() } | ||
} | ||
|
||
BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { | ||
result.(B::BasicBlock).immediatelyDominates(bb) | ||
} | ||
|
||
BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.(B::BasicBlock).getASuccessor() } | ||
|
||
//TODO: support capture of `this` in lambdas | ||
MathiasVP marked this conversation as resolved.
Show resolved
Hide resolved
|
||
class CapturedVariable instanceof S::VarDecl { | ||
CapturedVariable() { | ||
any(S::CapturedDecl capturedDecl).getDecl() = this and | ||
exists(this.getEnclosingCallable()) | ||
} | ||
|
||
string toString() { result = super.toString() } | ||
|
||
Callable getCallable() { result = super.getEnclosingCallable() } | ||
|
||
Location getLocation() { result = super.getLocation() } | ||
} | ||
|
||
class CapturedParameter extends CapturedVariable instanceof S::ParamDecl { } | ||
|
||
class Expr instanceof S::AstNode { | ||
string toString() { result = super.toString() } | ||
|
||
Location getLocation() { result = super.getLocation() } | ||
|
||
predicate hasCfgNode(BasicBlock bb, int i) { | ||
this = bb.(B::BasicBlock).getNode(i).getNode().asAstNode() | ||
} | ||
} | ||
|
||
class VariableWrite extends Expr { | ||
CapturedVariable variable; | ||
Expr source; | ||
|
||
VariableWrite() { | ||
exists(S::Assignment a | this = a | | ||
a.getDest().(DeclRefExpr).getDecl() = variable and | ||
source = a.getSource() | ||
) | ||
or | ||
exists(S::NamedPattern np | this = np | | ||
variable = np.getVarDecl() and | ||
source = np.getMatchingExpr() | ||
) | ||
// TODO: support multiple variables in LHS of =, in both of above cases. | ||
MathiasVP marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
CapturedVariable getVariable() { result = variable } | ||
|
||
Expr getSource() { result = source } | ||
} | ||
|
||
class VariableRead extends Expr instanceof S::DeclRefExpr { | ||
CapturedVariable v; | ||
|
||
VariableRead() { this.getDecl() = v /* TODO: this should be an R-value only. */ } | ||
|
||
CapturedVariable getVariable() { result = v } | ||
} | ||
|
||
class ClosureExpr extends Expr instanceof S::Callable { | ||
ClosureExpr() { any(S::CapturedDecl c).getScope() = this } | ||
|
||
predicate hasBody(Callable body) { this = body } | ||
|
||
predicate hasAliasedAccess(Expr f) { closureFlowStep+(this, f) and not closureFlowStep(f, _) } | ||
} | ||
|
||
class Callable extends S::Callable { | ||
predicate isConstructor() { this instanceof S::Initializer } | ||
} | ||
} | ||
|
||
class CapturedVariable = CaptureInput::CapturedVariable; | ||
|
||
class CapturedParameter = CaptureInput::CapturedParameter; | ||
|
||
module CaptureFlow = VariableCapture::Flow<CaptureInput>; | ||
|
||
private CaptureFlow::ClosureNode asClosureNode(Node n) { | ||
result = n.(CaptureNode).getSynthesizedCaptureNode() | ||
or | ||
result.(CaptureFlow::ExprNode).getExpr() = n.asExpr() | ||
or | ||
result.(CaptureFlow::ExprPostUpdateNode).getExpr() = | ||
n.(PostUpdateNode).getPreUpdateNode().asExpr() | ||
or | ||
result.(CaptureFlow::ParameterNode).getParameter() = n.getParameter() | ||
or | ||
result.(CaptureFlow::ThisParameterNode).getCallable() = n.(ClosureSelfParameterNode).getClosure() | ||
or | ||
result.(CaptureFlow::MallocNode).getClosureExpr() = n.getCfgNode().getNode().asAstNode() // TODO: figure out why the java version had PostUpdateNode logic here | ||
MathiasVP marked this conversation as resolved.
Show resolved
Hide resolved
|
||
or | ||
exists(CaptureInput::VariableWrite write | | ||
result.(CaptureFlow::VariableWriteSourceNode).getVariableWrite() = write and | ||
n.asExpr() = write.getSource() | ||
) | ||
} | ||
|
||
private predicate captureStoreStep(Node node1, Content::CapturedVariableContent c, Node node2) { | ||
CaptureFlow::storeStep(asClosureNode(node1), c.getVariable(), asClosureNode(node2)) | ||
} | ||
|
||
private predicate captureReadStep(Node node1, Content::CapturedVariableContent c, Node node2) { | ||
CaptureFlow::readStep(asClosureNode(node1), c.getVariable(), asClosureNode(node2)) | ||
} | ||
|
||
predicate captureValueStep(Node node1, Node node2) { | ||
CaptureFlow::localFlowStep(asClosureNode(node1), asClosureNode(node2)) | ||
} | ||
|
||
predicate jumpStep(Node pred, Node succ) { | ||
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(), | ||
succ.(FlowSummaryNode).getSummaryNode()) | ||
|
@@ -897,6 +1089,8 @@ predicate storeStep(Node node1, ContentSet c, Node node2) { | |
or | ||
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c, | ||
node2.(FlowSummaryNode).getSummaryNode()) | ||
or | ||
captureStoreStep(node1, any(Content::CapturedVariableContent cvc | c.isSingleton(cvc)), node2) | ||
} | ||
|
||
predicate isLValue(Expr e) { any(AssignExpr assign).getDest() = e } | ||
|
@@ -1001,6 +1195,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) { | |
or | ||
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c, | ||
node2.(FlowSummaryNode).getSummaryNode()) | ||
or | ||
captureReadStep(node1, any(Content::CapturedVariableContent cvc | c.isSingleton(cvc)), node2) | ||
} | ||
|
||
/** | ||
|
@@ -1094,6 +1290,17 @@ private module PostUpdateNodes { | |
result.(FlowSummaryNode).getSummaryNode()) | ||
} | ||
} | ||
|
||
class CapturePostUpdateNode extends PostUpdateNodeImpl, CaptureNode { | ||
private CaptureNode pre; | ||
|
||
CapturePostUpdateNode() { | ||
CaptureFlow::capturePostUpdateNode(this.getSynthesizedCaptureNode(), | ||
pre.getSynthesizedCaptureNode()) | ||
} | ||
|
||
override Node getPreUpdateNode() { result = pre } | ||
} | ||
} | ||
|
||
private import PostUpdateNodes | ||
|
Uh oh!
There was an error while loading. Please reload this page.