Skip to content

Commit 9fb1c31

Browse files
Update tests to inline expectations
1 parent adfe89f commit 9fb1c31

File tree

7 files changed

+112
-102
lines changed

7 files changed

+112
-102
lines changed

Diff for: python/ql/src/Variables/LoopVariableCapture/LoopVariableCapture.ql

+1-71
Original file line numberDiff line numberDiff line change
@@ -10,80 +10,10 @@
1010
*/
1111

1212
import python
13+
import LoopVariableCaptureQuery
1314
import semmle.python.dataflow.new.DataFlow
14-
15-
abstract class Loop extends AstNode {
16-
abstract Variable getALoopVariable();
17-
}
18-
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) {
27-
var.getAnAccess().getScope() = capturing.getInnerScope() and
28-
capturing.getParentNode+() = loop and
29-
var = loop.getALoopVariable()
30-
}
31-
32-
module EscapingCaptureFlowConfig 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-
// This appeared to lead to FPs through wrapper classes.
38-
// exists(DataFlow::AttrWrite aw | aw.getObject() = node)
39-
// or
40-
// Stored in a dict/list.
41-
exists(Assign assign, Subscript sub |
42-
sub = assign.getATarget() and node.asExpr() = assign.getValue()
43-
)
44-
or
45-
// Stored in a list.
46-
exists(DataFlow::MethodCallNode mc | mc.calls(_, "append") and node = mc.getArg(0))
47-
or
48-
// Used in a yield statement, likely included in a collection.
49-
// The element of comprehension expressions desugar to involve a yield statement internally.
50-
exists(Yield y | node.asExpr() = y.getValue())
51-
}
52-
53-
predicate isBarrierOut(DataFlow::Node node) { isSink(node) }
54-
55-
predicate isBarrier(DataFlow::Node node) {
56-
// Incorrect virtual dispatch to __call__ methods is a source of FPs.
57-
exists(Function call |
58-
call.getName() = "__call__" and
59-
call.getArg(0) = node.(DataFlow::ParameterNode).getParameter()
60-
)
61-
}
62-
63-
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet cs) {
64-
isSink(node) and
65-
(
66-
cs instanceof DataFlow::TupleElementContent or
67-
cs instanceof DataFlow::ListElementContent or
68-
cs instanceof DataFlow::SetElementContent or
69-
cs instanceof DataFlow::DictionaryElementAnyContent
70-
)
71-
}
72-
}
73-
74-
module EscapingCaptureFlow = DataFlow::Global<EscapingCaptureFlowConfig>;
75-
7615
import EscapingCaptureFlow::PathGraph
7716

78-
predicate escapingCapture(
79-
CallableExpr capturing, Loop loop, Variable var, EscapingCaptureFlow::PathNode source,
80-
EscapingCaptureFlow::PathNode sink
81-
) {
82-
capturesLoopVariable(capturing, loop, var) and
83-
capturing = source.getNode().asExpr() and
84-
EscapingCaptureFlow::flowPath(source, sink)
85-
}
86-
8717
from
8818
CallableExpr capturing, AstNode loop, Variable var, string descr,
8919
EscapingCaptureFlow::PathNode source, EscapingCaptureFlow::PathNode sink
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/** Definitions for reasoning about loop variable capture issues. */
2+
3+
import python
4+
import semmle.python.dataflow.new.DataFlow
5+
6+
/** A looping construct. */
7+
abstract class Loop extends AstNode {
8+
/**
9+
* Gets a loop variable of this loop.
10+
* For example, `x` and `y` in `for x,y in pairs: print(x+y)`
11+
*/
12+
abstract Variable getALoopVariable();
13+
}
14+
15+
/** A `for` loop. */
16+
private class ForLoop extends Loop, For {
17+
override Variable getALoopVariable() {
18+
this.getTarget() = result.getAnAccess().getParentNode*() and
19+
result.getScope() = this.getScope()
20+
}
21+
}
22+
23+
/** Holds if the callable `capturing` captures the variable `var` from the loop `loop`. */
24+
predicate capturesLoopVariable(CallableExpr capturing, Loop loop, Variable var) {
25+
var.getAnAccess().getScope() = capturing.getInnerScope() and
26+
capturing.getParentNode+() = loop and
27+
var = loop.getALoopVariable()
28+
}
29+
30+
/** Dataflow configuration for reasoning about callables that capture a loop variable and then may escape from the loop. */
31+
module EscapingCaptureFlowConfig implements DataFlow::ConfigSig {
32+
predicate isSource(DataFlow::Node node) { capturesLoopVariable(node.asExpr(), _, _) }
33+
34+
predicate isSink(DataFlow::Node node) {
35+
// Stored in a dict/list.
36+
exists(Assign assign, Subscript sub |
37+
sub = assign.getATarget() and node.asExpr() = assign.getValue()
38+
)
39+
or
40+
// Stored in a list.
41+
exists(DataFlow::MethodCallNode mc | mc.calls(_, "append") and node = mc.getArg(0))
42+
or
43+
// Used in a yield statement, likely included in a collection.
44+
// The element of comprehension expressions desugar to involve a yield statement internally.
45+
exists(Yield y | node.asExpr() = y.getValue())
46+
// Checks for storing in a field leads to false positives, so are omitted.
47+
}
48+
49+
predicate isBarrierOut(DataFlow::Node node) { isSink(node) }
50+
51+
predicate isBarrier(DataFlow::Node node) {
52+
// Incorrect virtual dispatch to __call__ methods is a source of FPs.
53+
exists(Function call |
54+
call.getName() = "__call__" and
55+
call.getArg(0) = node.(DataFlow::ParameterNode).getParameter()
56+
)
57+
}
58+
59+
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet cs) {
60+
isSink(node) and
61+
(
62+
cs instanceof DataFlow::TupleElementContent or
63+
cs instanceof DataFlow::ListElementContent or
64+
cs instanceof DataFlow::SetElementContent or
65+
cs instanceof DataFlow::DictionaryElementAnyContent
66+
)
67+
}
68+
}
69+
70+
/** Dataflow for reasoning about callables that capture a loop variable and then escape from the loop. */
71+
module EscapingCaptureFlow = DataFlow::Global<EscapingCaptureFlowConfig>;
72+
73+
/** Holds if `capturing` is a callable that captures the variable `var` of the loop `loop`, and then may escape the loop via a flow path from `source` to `sink`. */
74+
predicate escapingCapture(
75+
CallableExpr capturing, Loop loop, Variable var, EscapingCaptureFlow::PathNode source,
76+
EscapingCaptureFlow::PathNode sink
77+
) {
78+
capturesLoopVariable(capturing, loop, var) and
79+
capturing = source.getNode().asExpr() and
80+
EscapingCaptureFlow::flowPath(source, sink)
81+
}

Diff for: python/ql/test/query-tests/Variables/capture/LoopVariableCapture.expected

-26
This file was deleted.

Diff for: python/ql/test/query-tests/Variables/capture/LoopVariableCapture.qlref

-1
This file was deleted.

Diff for: python/ql/test/query-tests/Variables/capture/LoopVariableCaptureTest.expected

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import python
2+
import Variables.LoopVariableCapture.LoopVariableCaptureQuery
3+
import utils.test.InlineExpectationsTest
4+
5+
module MethodArgTest implements TestSig {
6+
string getARelevantTag() { result = ["capturedVar"] }
7+
8+
predicate hasActualResult(Location location, string element, string tag, string value) {
9+
exists(CallableExpr capturing, AstNode loop, Variable var |
10+
escapingCapture(capturing, loop, var, _, _) and
11+
element = capturing.toString() and
12+
location = capturing.getLocation() and
13+
tag = "capturedVar" and
14+
value = var.getId()
15+
)
16+
}
17+
}
18+
19+
import MakeTest<MethodArgTest>

Diff for: python/ql/test/query-tests/Variables/capture/test.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ def bad1():
44
for x in range(10):
55
def inner(): # $capturedVar=x
66
return x
7-
results.append(inner)
7+
results.append(inner)
88
return results
99

10-
a = [lambda: i for i in range(1, 4)] # $capturedVar=a
10+
a = [lambda: i for i in range(1, 4)] # $capturedVar=i
1111
for f in a:
1212
print(f())
1313

@@ -18,7 +18,14 @@ def good1():
1818
for y in range(10):
1919
def inner(y=y):
2020
return y
21-
results.append(inner)
21+
results.append(inner)
22+
return results
23+
24+
# OK: Using default argument.
25+
def good2():
26+
results = []
27+
for y in range(10):
28+
results.append(lambda y=y: y)
2229
return results
2330

2431
#Factory function
@@ -46,7 +53,7 @@ def inner():
4653
for f in s:
4754
print(f())
4855

49-
d = {i:lambda: i for i in range(1, 4)} # $capturedVar=d
56+
d = {i:lambda: i for i in range(1, 4)} # $capturedVar=i
5057
for k, f in d.items():
5158
print(k, f())
5259

0 commit comments

Comments
 (0)