-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathLoopVariableCapture.ql
More file actions
90 lines (77 loc) · 2.96 KB
/
LoopVariableCapture.ql
File metadata and controls
90 lines (77 loc) · 2.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
* @name Loop variable capture
* @description Capture of a loop variable is not the same as capturing the value of a loop variable, and may be erroneous.
* @kind path-problem
* @tags correctness
* @problem.severity error
* @sub-severity low
* @precision high
* @id py/loop-variable-capture
*/
import python
import semmle.python.dataflow.new.DataFlow
abstract class Loop extends AstNode {
abstract Variable getALoopVariable();
}
class ForLoop extends Loop, For {
override Variable getALoopVariable() {
this.getTarget() = result.getAnAccess().getParentNode*() and
result.getScope() = this.getScope()
}
}
predicate capturesLoopVariable(CallableExpr capturing, Loop loop, Variable var) {
var.getAnAccess().getScope() = capturing.getInnerScope() and
capturing.getParentNode+() = loop and
var = loop.getALoopVariable()
}
module EscapingCaptureFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { capturesLoopVariable(node.asExpr(), _, _) }
predicate isSink(DataFlow::Node node) {
// Stored in a field.
// This appeared to lead to FPs through wrapper classes.
// exists(DataFlow::AttrWrite aw | aw.getObject() = node)
// or
// Stored in a dict/list.
exists(Assign assign, Subscript sub |
sub = assign.getATarget() and node.asExpr() = assign.getValue()
)
or
// Stored in a list.
exists(DataFlow::MethodCallNode mc | mc.calls(_, "append") and node = mc.getArg(0))
or
// Used in a yield statement, likely included in a collection.
// The element of comprehension expressions desugar to involve a yield statement internally.
exists(Yield y | node.asExpr() = y.getValue())
}
predicate isBarrierOut(DataFlow::Node node) { isSink(node) }
predicate isBarrier(DataFlow::Node node) {
// Incorrect virtual dispatch to __call__ methods is a source of FPs.
exists(Function call |
call.getName() = "__call__" and
call.getArg(0) = node.(DataFlow::ParameterNode).getParameter()
)
}
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet cs) {
isSink(node) and
exists(cs)
}
}
module EscapingCaptureFlow = DataFlow::Global<EscapingCaptureFlowConfig>;
import EscapingCaptureFlow::PathGraph
predicate escapingCapture(
CallableExpr capturing, Loop loop, Variable var, EscapingCaptureFlow::PathNode source,
EscapingCaptureFlow::PathNode sink
) {
capturesLoopVariable(capturing, loop, var) and
capturing = source.getNode().asExpr() and
EscapingCaptureFlow::flowPath(source, sink)
}
from
CallableExpr capturing, AstNode loop, Variable var, string descr,
EscapingCaptureFlow::PathNode source, EscapingCaptureFlow::PathNode sink
where
escapingCapture(capturing, loop, var, source, sink) and
if capturing instanceof Lambda then descr = "lambda" else descr = "function"
select capturing, source, sink,
"This " + descr + " captures the loop variable $@, and may escape the loop by being stored at $@.",
loop, var.getId(), sink, "this location"