Skip to content

Commit 46c56a4

Browse files
committed
Python: Make capturing closure arguments synthetic and non-global
Uses the same trick as for `ExtractedArgumentNode`, wherein we postpone the global restriction on the charpred to instead be in the `argumentOf` predicate (which is global anyway). In addition to this, we also converted `CapturedVariablesArgumentNode` into a proper synthetic node, and added an explicit post-update node for it. These nodes just act as wrappers for the function part of call nodes. Thus, to make them work with the variable capture machinery, we simply map them to the closure node for the corresponding control-flow or post-update node.
1 parent 3c2c735 commit 46c56a4

File tree

4 files changed

+79
-20
lines changed

4 files changed

+79
-20
lines changed

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,36 +1714,58 @@ private class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNodeImpl
17141714
* This is also known as the environment part of a closure.
17151715
*
17161716
* This is used for tracking flow through captured variables.
1717-
*
1718-
* TODO:
1719-
* We might want a synthetic node here, but currently that incurs problems
1720-
* with non-monotonic recursion, because of the use of `resolveCall` in the
1721-
* char pred. This may be solvable by using
1722-
* `CallGraphConstruction::Make` in stead of
1723-
* `CallGraphConstruction::Simple::Make` appropriately.
17241717
*/
1725-
class CapturedVariablesArgumentNode extends CfgNode, ArgumentNode {
1726-
CallNode callNode;
1718+
class SynthCapturedVariablesArgumentNode extends Node, TSynthCapturedVariablesArgumentNode {
1719+
ControlFlowNode callable;
17271720

1728-
CapturedVariablesArgumentNode() {
1729-
node = callNode.getFunction() and
1730-
exists(Function target | resolveCall(callNode, target, _) |
1731-
target = any(VariableCapture::CapturedVariable v).getACapturingScope()
1732-
)
1733-
}
1721+
SynthCapturedVariablesArgumentNode() { this = TSynthCapturedVariablesArgumentNode(callable) }
1722+
1723+
/** Gets the `CallNode` corresponding to this captured variables argument node. */
1724+
CallNode getCallNode() { result.getFunction() = callable }
1725+
1726+
override Scope getScope() { result = callable.getScope() }
1727+
1728+
override Location getLocation() { result = callable.getLocation() }
17341729

17351730
override string toString() { result = "Capturing closure argument" }
1731+
}
17361732

1733+
/** A captured variables argument node viewed as an argument node. Needed because `argumentOf` is a global predicate. */
1734+
class CapturedVariablesArgumentNodeAsArgumentNode extends ArgumentNode,
1735+
SynthCapturedVariablesArgumentNode
1736+
{
17371737
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
1738-
callNode = call.getNode() and
1739-
pos.isLambdaSelf()
1738+
exists(CallNode callNode | callNode = this.getCallNode() |
1739+
callNode = call.getNode() and
1740+
exists(Function target | resolveCall(callNode, target, _) |
1741+
target = any(VariableCapture::CapturedVariable v).getACapturingScope()
1742+
) and
1743+
pos.isLambdaSelf()
1744+
)
17401745
}
17411746
}
17421747

1743-
/** A synthetic node representing the values of variables captured by a comprehension. */
1744-
class SynthCompCapturedVariablesArgumentNode extends Node, TSynthCompCapturedVariablesArgumentNode,
1745-
ArgumentNode
1748+
/** A synthetic node representing the values of captured variables after the output has been computed. */
1749+
class SynthCapturedVariablesArgumentPostUpdateNode extends PostUpdateNodeImpl,
1750+
TSynthCapturedVariablesArgumentPostUpdateNode
17461751
{
1752+
ControlFlowNode callable;
1753+
1754+
SynthCapturedVariablesArgumentPostUpdateNode() {
1755+
this = TSynthCapturedVariablesArgumentPostUpdateNode(callable)
1756+
}
1757+
1758+
override string toString() { result = "[post] Capturing closure argument" }
1759+
1760+
override Scope getScope() { result = callable.getScope() }
1761+
1762+
override Location getLocation() { result = callable.getLocation() }
1763+
1764+
override Node getPreUpdateNode() { result = TSynthCapturedVariablesArgumentNode(callable) }
1765+
}
1766+
1767+
/** A synthetic node representing the values of variables captured by a comprehension. */
1768+
class SynthCompCapturedVariablesArgumentNode extends Node, TSynthCompCapturedVariablesArgumentNode {
17471769
Comp comp;
17481770

17491771
SynthCompCapturedVariablesArgumentNode() { this = TSynthCompCapturedVariablesArgumentNode(comp) }
@@ -1755,7 +1777,11 @@ class SynthCompCapturedVariablesArgumentNode extends Node, TSynthCompCapturedVar
17551777
override Location getLocation() { result = comp.getLocation() }
17561778

17571779
Comp getComprehension() { result = comp }
1780+
}
17581781

1782+
class SynthCompCapturedVariablesArgumentNodeAsArgumentNode extends SynthCompCapturedVariablesArgumentNode,
1783+
ArgumentNode
1784+
{
17591785
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
17601786
call.(ComprehensionCall).getComprehension() = comp and
17611787
pos.isLambdaSelf()

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,14 @@ predicate nodeIsHidden(Node n) {
11281128
n instanceof SynthCaptureNode
11291129
or
11301130
n instanceof SynthCapturedVariablesParameterNode
1131+
or
1132+
n instanceof SynthCapturedVariablesArgumentNode
1133+
or
1134+
n instanceof SynthCapturedVariablesArgumentPostUpdateNode
1135+
or
1136+
n instanceof SynthCompCapturedVariablesArgumentNode
1137+
or
1138+
n instanceof SynthCompCapturedVariablesArgumentPostUpdateNode
11311139
}
11321140

11331141
class LambdaCallKind = Unit;

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ newtype TNode =
121121
f = any(VariableCapture::CapturedVariable v).getACapturingScope() and
122122
exists(TFunction(f))
123123
} or
124+
/**
125+
* A synthetic node representing the values of the variables captured
126+
* by the callable being called.
127+
*/
128+
TSynthCapturedVariablesArgumentNode(ControlFlowNode callable) {
129+
callable = any(CallNode c).getFunction()
130+
} or
131+
/**
132+
* A synthetic node representing the values of the variables captured
133+
* by the callable being called, after the output has been computed.
134+
*/
135+
TSynthCapturedVariablesArgumentPostUpdateNode(ControlFlowNode callable) {
136+
callable = any(CallNode c).getFunction()
137+
} or
124138
/** A synthetic node representing the values of variables captured by a comprehension. */
125139
TSynthCompCapturedVariablesArgumentNode(Comp comp) {
126140
comp.getFunction() = any(VariableCapture::CapturedVariable v).getACapturingScope()

python/ql/lib/semmle/python/dataflow/new/internal/VariableCapture.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,17 @@ private Flow::ClosureNode asClosureNode(Node n) {
114114
result.(Flow::ExprNode).getExpr().getNode() = comp
115115
)
116116
or
117+
exists(ControlFlowNode function |
118+
// The synthetic captured variables argument nodes are just representatives for the CFG nodes
119+
// they wrap, so we find the corresponding CFG node and return what it maps to.
120+
n = TSynthCapturedVariablesArgumentNode(function) and
121+
result = asClosureNode(any(Node n2 | n2.asCfgNode() = function))
122+
or
123+
// and likewise for the post-update node
124+
n = TSynthCapturedVariablesArgumentPostUpdateNode(function) and
125+
result = asClosureNode((any(PostUpdateNode n2 | n2.getPreUpdateNode().asCfgNode() = function)))
126+
)
127+
or
117128
// TODO: Should the `Comp`s above be excluded here?
118129
result.(Flow::ExprNode).getExpr() = n.(CfgNode).getNode()
119130
or

0 commit comments

Comments
 (0)