Skip to content

Commit 3f71812

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 6113d4b commit 3f71812

File tree

4 files changed

+82
-20
lines changed

4 files changed

+82
-20
lines changed

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

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,36 +1714,66 @@ 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+
/** Gets the `CfgNode` that corresponds to this synthetic node. */
1727+
CfgNode getUnderlyingNode() { result.asCfgNode() = callable }
1728+
1729+
override Scope getScope() { result = callable.getScope() }
1730+
1731+
override Location getLocation() { result = callable.getLocation() }
17341732

17351733
override string toString() { result = "Capturing closure argument" }
1734+
}
17361735

1736+
/** A captured variables argument node viewed as an argument node. Needed because `argumentOf` is a global predicate. */
1737+
class CapturedVariablesArgumentNodeAsArgumentNode extends ArgumentNode,
1738+
SynthCapturedVariablesArgumentNode
1739+
{
17371740
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
1738-
callNode = call.getNode() and
1739-
pos.isLambdaSelf()
1741+
exists(CallNode callNode | callNode = this.getCallNode() |
1742+
callNode = call.getNode() and
1743+
exists(Function target | resolveCall(callNode, target, _) |
1744+
target = any(VariableCapture::CapturedVariable v).getACapturingScope()
1745+
) and
1746+
pos.isLambdaSelf()
1747+
)
17401748
}
17411749
}
17421750

1743-
/** A synthetic node representing the values of variables captured by a comprehension. */
1744-
class SynthCompCapturedVariablesArgumentNode extends Node, TSynthCompCapturedVariablesArgumentNode,
1745-
ArgumentNode
1751+
/** A synthetic node representing the values of captured variables after the output has been computed. */
1752+
class SynthCapturedVariablesArgumentPostUpdateNode extends PostUpdateNodeImpl,
1753+
TSynthCapturedVariablesArgumentPostUpdateNode
17461754
{
1755+
ControlFlowNode callable;
1756+
1757+
SynthCapturedVariablesArgumentPostUpdateNode() {
1758+
this = TSynthCapturedVariablesArgumentPostUpdateNode(callable)
1759+
}
1760+
1761+
/** Gets the `PostUpdateNode` (for a `CfgNode`) that corresponds to this synthetic node. */
1762+
PostUpdateNode getUnderlyingNode() { result.getPreUpdateNode().asCfgNode() = callable }
1763+
1764+
override string toString() { result = "[post] Capturing closure argument" }
1765+
1766+
override Scope getScope() { result = callable.getScope() }
1767+
1768+
override Location getLocation() { result = callable.getLocation() }
1769+
1770+
override SynthCapturedVariablesArgumentNode getPreUpdateNode() {
1771+
result = TSynthCapturedVariablesArgumentNode(callable)
1772+
}
1773+
}
1774+
1775+
/** A synthetic node representing the values of variables captured by a comprehension. */
1776+
class SynthCompCapturedVariablesArgumentNode extends Node, TSynthCompCapturedVariablesArgumentNode {
17471777
Comp comp;
17481778

17491779
SynthCompCapturedVariablesArgumentNode() { this = TSynthCompCapturedVariablesArgumentNode(comp) }
@@ -1755,7 +1785,11 @@ class SynthCompCapturedVariablesArgumentNode extends Node, TSynthCompCapturedVar
17551785
override Location getLocation() { result = comp.getLocation() }
17561786

17571787
Comp getComprehension() { result = comp }
1788+
}
17581789

1790+
class SynthCompCapturedVariablesArgumentNodeAsArgumentNode extends SynthCompCapturedVariablesArgumentNode,
1791+
ArgumentNode
1792+
{
17591793
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
17601794
call.(ComprehensionCall).getComprehension() = comp and
17611795
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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ private Flow::ClosureNode asClosureNode(Node n) {
114114
result.(Flow::ExprNode).getExpr().getNode() = comp
115115
)
116116
or
117+
// For captured variable argument nodes (and their post-update variants), we use the closure node
118+
// for the underlying node.
119+
result = asClosureNode(n.(SynthCapturedVariablesArgumentNode).getUnderlyingNode())
120+
or
121+
result = asClosureNode(n.(SynthCapturedVariablesArgumentPostUpdateNode).getUnderlyingNode())
122+
or
117123
// TODO: Should the `Comp`s above be excluded here?
118124
result.(Flow::ExprNode).getExpr() = n.(CfgNode).getNode()
119125
or

0 commit comments

Comments
 (0)