Skip to content

Commit 72530a8

Browse files
committed
Python: use synthetic node for comprehension capture argument
We used to use the CfgNode for the comprehension itself. In cases where that is also an argument, say ```python ",".join([x for x in l]) ``` that would be an argument to two different calls causing a dataflow consistency violation.
1 parent 294092b commit 72530a8

File tree

3 files changed

+22
-5
lines changed

3 files changed

+22
-5
lines changed

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1729,24 +1729,34 @@ class CapturedVariablesArgumentNode extends CfgNode, ArgumentNode {
17291729
}
17301730
}
17311731

1732-
class ComprehensionCapturedVariablesArgumentNode extends CfgNode, ArgumentNode {
1732+
class ComprehensionCapturedVariablesArgumentNode extends Node, ArgumentNode {
17331733
Comp comp;
17341734

17351735
ComprehensionCapturedVariablesArgumentNode() {
1736-
node.getNode() = comp and
1736+
this = TSynthCompCapturedVariablesArgumentNode(comp) and
17371737
exists(Function target | target = comp.getFunction() |
17381738
target = any(VariableCapture::CapturedVariable v).getACapturingScope()
17391739
)
17401740
}
17411741

1742-
override string toString() { result = "Capturing closure argument (comp)" }
1743-
17441742
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
17451743
call.(ComprehensionCall).getComprehension() = comp and
17461744
pos.isLambdaSelf()
17471745
}
17481746
}
17491747

1748+
class SynthCompCapturedVariablesArgumentNode extends Node, TSynthCompCapturedVariablesArgumentNode {
1749+
Comp comp;
1750+
1751+
SynthCompCapturedVariablesArgumentNode() { this = TSynthCompCapturedVariablesArgumentNode(comp) }
1752+
1753+
override string toString() { result = "Capturing closure argument (comp)" }
1754+
1755+
override Scope getScope() { result = comp.getFunction() }
1756+
1757+
override Location getLocation() { result = comp.getLocation() }
1758+
}
1759+
17501760
/** Gets a viable run-time target for the call `call`. */
17511761
DataFlowCallable viableCallable(DataFlowCall call) {
17521762
call instanceof ExtractedDataFlowCall and

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,11 @@ newtype TNode =
124124
/** A synthetic node representing the heap of a function. Used for variable capture. */
125125
TSynthCapturedVariablesParameterNode(Function f) {
126126
f = any(VariableCapture::CapturedVariable v).getACapturingScope() and
127-
// TODO: Remove this restriction when adding proper support for captured variables in the body of the function we generate for comprehensions
128127
exists(TFunction(f))
129128
} or
129+
TSynthCompCapturedVariablesArgumentNode(Comp comp) {
130+
comp.getFunction() = any(VariableCapture::CapturedVariable v).getACapturingScope()
131+
} or
130132
/** An empty, unused node type that exists to prevent unwanted dependencies on data flow nodes. */
131133
TForbiddenRecursionGuard() {
132134
none() and

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ module Flow = Shared::Flow<Location, CaptureInput>;
127127
private Flow::ClosureNode asClosureNode(Node n) {
128128
result = n.(SynthCaptureNode).getSynthesizedCaptureNode()
129129
or
130+
exists(Comp comp | n = TSynthCompCapturedVariablesArgumentNode(comp) |
131+
result.(Flow::ExprNode).getExpr().getNode() = comp
132+
)
133+
or
134+
// TODO: Should the `Comp`s above be excluded here?
130135
result.(Flow::ExprNode).getExpr() = n.(CfgNode).getNode()
131136
or
132137
result.(Flow::VariableWriteSourceNode).getVariableWrite() = n.(CfgNode).getNode()

0 commit comments

Comments
 (0)