Skip to content

Commit efdb4a6

Browse files
Use global dataflow for loop variable capture
1 parent 3652d6f commit efdb4a6

File tree

1 file changed

+44
-22
lines changed

1 file changed

+44
-22
lines changed

python/ql/src/Variables/LoopVariableCapture.ql

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,38 +10,60 @@
1010
*/
1111

1212
import python
13+
import semmle.python.dataflow.new.DataFlow
1314

14-
// Gets the scope of the iteration variable of the looping scope
15-
Scope iteration_variable_scope(AstNode loop) {
16-
result = loop.(For).getScope()
17-
or
18-
result = loop.(Comp).getFunction()
15+
abstract class Loop extends AstNode {
16+
abstract Variable getALoopVariable();
1917
}
2018

21-
predicate capturing_looping_construct(CallableExpr capturing, AstNode loop, Variable var) {
22-
var.getScope() = iteration_variable_scope(loop) and
19+
class ForLoop extends Loop, For {
20+
override Variable getALoopVariable() {
21+
this.getTarget() = result.getAnAccess().getParentNode*() and
22+
result.getScope() = this.getScope()
23+
}
24+
}
25+
26+
predicate capturesLoopVariable(CallableExpr capturing, Loop loop, Variable var) {
2327
var.getAnAccess().getScope() = capturing.getInnerScope() and
2428
capturing.getParentNode+() = loop and
25-
(
26-
loop.(For).getTarget() = var.getAnAccess()
27-
or
28-
var = loop.(Comp).getAnIterationVariable()
29-
)
29+
var = loop.getALoopVariable()
3030
}
3131

32-
predicate escaping_capturing_looping_construct(CallableExpr capturing, AstNode loop, Variable var) {
33-
capturing_looping_construct(capturing, loop, var) and
34-
// Escapes if used out side of for loop or is a lambda in a comprehension
35-
(
36-
loop instanceof For and
37-
exists(Expr e | e.pointsTo(_, _, capturing) | not loop.contains(e))
32+
module EscapingCaptureFlowSig implements DataFlow::ConfigSig {
33+
predicate isSource(DataFlow::Node node) { capturesLoopVariable(node.asExpr(), _, _) }
34+
35+
predicate isSink(DataFlow::Node node) {
36+
// Stored in a field.
37+
exists(DataFlow::AttrWrite aw | aw.getObject() = node)
38+
or
39+
// Stored in a dict/list.
40+
exists(Assign assign, Subscript sub |
41+
sub = assign.getATarget() and node.asExpr() = assign.getValue()
42+
)
3843
or
39-
loop.(Comp).getElt() = capturing
44+
// Stored in a list.
45+
exists(DataFlow::MethodCallNode mc | mc.calls(_, "append") and node = mc.getArg(0))
4046
or
41-
loop.(Comp).getElt().(Tuple).getAnElt() = capturing
42-
)
47+
// Used in a yeild statement, likely included in a collection.
48+
// The element of comprehension expressions desugar to involve a yield statement internally.
49+
exists(Yield y | node.asExpr() = y.getValue())
50+
}
51+
52+
predicate isBarrierOut(DataFlow::Node node) { isSink(node) }
53+
54+
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet cs) {
55+
isSink(node) and
56+
exists(cs)
57+
}
58+
}
59+
60+
module EscapingCaptureFlow = DataFlow::Global<EscapingCaptureFlowSig>;
61+
62+
predicate escapingCapture(CallableExpr capturing, Loop loop, Variable var) {
63+
capturesLoopVariable(capturing, loop, var) and
64+
EscapingCaptureFlow::flow(DataFlow::exprNode(capturing), _)
4365
}
4466

4567
from CallableExpr capturing, AstNode loop, Variable var
46-
where escaping_capturing_looping_construct(capturing, loop, var)
68+
where escapingCapture(capturing, loop, var)
4769
select capturing, "Capture of loop variable $@.", loop, var.getId()

0 commit comments

Comments
 (0)