Skip to content

Commit fc2dc28

Browse files
committed
python: capture flow through comprehensions
- add comprehension functions as `DataFlowCallable`s - add comprehension call as `DataFlowCall` - create capture argument node for comprehension calls
1 parent 4dbb15d commit fc2dc28

File tree

2 files changed

+47
-1
lines changed

2 files changed

+47
-1
lines changed

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,9 @@ newtype TDataFlowCallable =
320320
// same to keep things easy to reason about (and therefore exclude things that do
321321
// not have a definition)
322322
exists(func.getDefinition())
323+
or
324+
// ...scratch that, variable capture requires a callable
325+
exists(Comp c | c.getFunction() = func)
323326
} or
324327
/** see QLDoc for `DataFlowModuleScope` for why we need this. */
325328
TModule(Module m) or
@@ -1382,6 +1385,7 @@ private predicate sameEnclosingCallable(Node node1, Node node2) {
13821385
// =============================================================================
13831386
newtype TDataFlowCall =
13841387
TNormalCall(CallNode call, Function target, CallType type) { resolveCall(call, target, type) } or
1388+
TComprehensionCall(Comp c) or
13851389
TPotentialLibraryCall(CallNode call) or
13861390
/** A synthesized call inside a summarized callable */
13871391
TSummaryCall(
@@ -1468,6 +1472,30 @@ class NormalCall extends ExtractedDataFlowCall, TNormalCall {
14681472
CallType getCallType() { result = type }
14691473
}
14701474

1475+
class ComprehensionCall extends ExtractedDataFlowCall, TComprehensionCall {
1476+
Comp c;
1477+
Function target;
1478+
1479+
ComprehensionCall() {
1480+
this = TComprehensionCall(c) and
1481+
target = c.getFunction()
1482+
}
1483+
1484+
Comp getComprehension() { result = c }
1485+
1486+
override string toString() { result = "comprehension call" }
1487+
1488+
override ControlFlowNode getNode() { result.getNode() = c }
1489+
1490+
override Scope getScope() { result = c.getScope() }
1491+
1492+
override DataFlowCallable getCallable() { result.(DataFlowFunction).getScope() = target }
1493+
1494+
override ArgumentNode getArgument(ArgumentPosition apos) { none() }
1495+
1496+
override Location getLocation() { result = c.getLocation() }
1497+
}
1498+
14711499
/**
14721500
* A potential call to a summarized callable, a `LibraryCallable`.
14731501
*
@@ -1698,6 +1726,24 @@ class CapturedVariablesArgumentNode extends CfgNode, ArgumentNode {
16981726
}
16991727
}
17001728

1729+
class ComprehensionCapturedVariablesArgumentNode extends CfgNode, ArgumentNode {
1730+
Comp comp;
1731+
1732+
ComprehensionCapturedVariablesArgumentNode() {
1733+
node.getNode() = comp and
1734+
exists(Function target | target = comp.getFunction() |
1735+
target = any(VariableCapture::CapturedVariable v).getACapturingScope()
1736+
)
1737+
}
1738+
1739+
override string toString() { result = "Capturing closure argument (comp)" }
1740+
1741+
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
1742+
call.(ComprehensionCall).getComprehension() = comp and
1743+
pos.isLambdaSelf()
1744+
}
1745+
}
1746+
17011747
/** Gets a viable run-time target for the call `call`. */
17021748
DataFlowCallable viableCallable(DataFlowCall call) {
17031749
call instanceof ExtractedDataFlowCall and

python/ql/test/library-tests/dataflow/coverage/test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def test_list_comprehension_with_tuple_result():
137137
s = SOURCE
138138
ns = NONSOURCE
139139
l3 = [(s, ns) for _ in [1]]
140-
SINK(l3[0][0]) # $ MISSING: flow="SOURCE, l:-3 -> l3[0][0]"
140+
SINK(l3[0][0]) # $ flow="SOURCE, l:-3 -> l3[0][0]"
141141
SINK_F(l3[0][1])
142142

143143

0 commit comments

Comments
 (0)