mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
Update tests to inline expectations
This commit is contained in:
@@ -10,80 +10,10 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import LoopVariableCaptureQuery
|
||||
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
|
||||
(
|
||||
cs instanceof DataFlow::TupleElementContent or
|
||||
cs instanceof DataFlow::ListElementContent or
|
||||
cs instanceof DataFlow::SetElementContent or
|
||||
cs instanceof DataFlow::DictionaryElementAnyContent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/** Definitions for reasoning about loop variable capture issues. */
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
/** A looping construct. */
|
||||
abstract class Loop extends AstNode {
|
||||
/**
|
||||
* Gets a loop variable of this loop.
|
||||
* For example, `x` and `y` in `for x,y in pairs: print(x+y)`
|
||||
*/
|
||||
abstract Variable getALoopVariable();
|
||||
}
|
||||
|
||||
/** A `for` loop. */
|
||||
private class ForLoop extends Loop, For {
|
||||
override Variable getALoopVariable() {
|
||||
this.getTarget() = result.getAnAccess().getParentNode*() and
|
||||
result.getScope() = this.getScope()
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if the callable `capturing` captures the variable `var` from the loop `loop`. */
|
||||
predicate capturesLoopVariable(CallableExpr capturing, Loop loop, Variable var) {
|
||||
var.getAnAccess().getScope() = capturing.getInnerScope() and
|
||||
capturing.getParentNode+() = loop and
|
||||
var = loop.getALoopVariable()
|
||||
}
|
||||
|
||||
/** Dataflow configuration for reasoning about callables that capture a loop variable and then may escape from the loop. */
|
||||
module EscapingCaptureFlowConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) { capturesLoopVariable(node.asExpr(), _, _) }
|
||||
|
||||
predicate isSink(DataFlow::Node node) {
|
||||
// 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())
|
||||
// Checks for storing in a field leads to false positives, so are omitted.
|
||||
}
|
||||
|
||||
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
|
||||
(
|
||||
cs instanceof DataFlow::TupleElementContent or
|
||||
cs instanceof DataFlow::ListElementContent or
|
||||
cs instanceof DataFlow::SetElementContent or
|
||||
cs instanceof DataFlow::DictionaryElementAnyContent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Dataflow for reasoning about callables that capture a loop variable and then escape from the loop. */
|
||||
module EscapingCaptureFlow = DataFlow::Global<EscapingCaptureFlowConfig>;
|
||||
|
||||
/** 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`. */
|
||||
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)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
edges
|
||||
| test.py:5:9:5:20 | ControlFlowNode for FunctionExpr | test.py:5:13:5:17 | ControlFlowNode for inner | provenance | |
|
||||
| test.py:5:13:5:17 | ControlFlowNode for inner | test.py:7:20:7:24 | ControlFlowNode for inner | provenance | |
|
||||
| test.py:49:8:49:16 | ControlFlowNode for Lambda | test.py:49:6:49:16 | ControlFlowNode for Tuple | provenance | |
|
||||
nodes
|
||||
| test.py:5:9:5:20 | ControlFlowNode for FunctionExpr | semmle.label | ControlFlowNode for FunctionExpr |
|
||||
| test.py:5:13:5:17 | ControlFlowNode for inner | semmle.label | ControlFlowNode for inner |
|
||||
| test.py:7:20:7:24 | ControlFlowNode for inner | semmle.label | ControlFlowNode for inner |
|
||||
| test.py:10:6:10:14 | ControlFlowNode for Lambda | semmle.label | ControlFlowNode for Lambda |
|
||||
| test.py:42:6:42:14 | ControlFlowNode for Lambda | semmle.label | ControlFlowNode for Lambda |
|
||||
| test.py:43:6:43:14 | ControlFlowNode for Lambda | semmle.label | ControlFlowNode for Lambda |
|
||||
| test.py:45:6:45:14 | ControlFlowNode for Lambda | semmle.label | ControlFlowNode for Lambda |
|
||||
| test.py:49:6:49:16 | ControlFlowNode for Tuple | semmle.label | ControlFlowNode for Tuple |
|
||||
| test.py:49:8:49:16 | ControlFlowNode for Lambda | semmle.label | ControlFlowNode for Lambda |
|
||||
| test.py:57:6:57:14 | ControlFlowNode for Lambda | semmle.label | ControlFlowNode for Lambda |
|
||||
| test.py:62:10:62:18 | ControlFlowNode for Lambda | semmle.label | ControlFlowNode for Lambda |
|
||||
subpaths
|
||||
#select
|
||||
| test.py:5:9:5:20 | FunctionExpr | test.py:5:9:5:20 | ControlFlowNode for FunctionExpr | test.py:7:20:7:24 | ControlFlowNode for inner | This function captures the loop variable $@, and may escape the loop by being stored at $@. | test.py:4:5:4:23 | For | x | test.py:7:20:7:24 | ControlFlowNode for inner | this location |
|
||||
| test.py:10:6:10:14 | Lambda | test.py:10:6:10:14 | ControlFlowNode for Lambda | test.py:10:6:10:14 | ControlFlowNode for Lambda | This lambda captures the loop variable $@, and may escape the loop by being stored at $@. | test.py:10:5:10:36 | For | i | test.py:10:6:10:14 | ControlFlowNode for Lambda | this location |
|
||||
| test.py:42:6:42:14 | Lambda | test.py:42:6:42:14 | ControlFlowNode for Lambda | test.py:42:6:42:14 | ControlFlowNode for Lambda | This lambda captures the loop variable $@, and may escape the loop by being stored at $@. | test.py:42:5:42:56 | For | i | test.py:42:6:42:14 | ControlFlowNode for Lambda | this location |
|
||||
| test.py:43:6:43:14 | Lambda | test.py:43:6:43:14 | ControlFlowNode for Lambda | test.py:43:6:43:14 | ControlFlowNode for Lambda | This lambda captures the loop variable $@, and may escape the loop by being stored at $@. | test.py:43:5:43:56 | For | j | test.py:43:6:43:14 | ControlFlowNode for Lambda | this location |
|
||||
| test.py:45:6:45:14 | Lambda | test.py:45:6:45:14 | ControlFlowNode for Lambda | test.py:45:6:45:14 | ControlFlowNode for Lambda | This lambda captures the loop variable $@, and may escape the loop by being stored at $@. | test.py:45:5:45:36 | For | i | test.py:45:6:45:14 | ControlFlowNode for Lambda | this location |
|
||||
| test.py:49:8:49:16 | Lambda | test.py:49:8:49:16 | ControlFlowNode for Lambda | test.py:49:6:49:16 | ControlFlowNode for Tuple | This lambda captures the loop variable $@, and may escape the loop by being stored at $@. | test.py:49:5:49:38 | For | i | test.py:49:6:49:16 | ControlFlowNode for Tuple | this location |
|
||||
| test.py:57:6:57:14 | Lambda | test.py:57:6:57:14 | ControlFlowNode for Lambda | test.py:57:6:57:14 | ControlFlowNode for Lambda | This lambda captures the loop variable $@, and may escape the loop by being stored at $@. | test.py:57:6:57:35 | For | i | test.py:57:6:57:14 | ControlFlowNode for Lambda | this location |
|
||||
| test.py:62:10:62:18 | Lambda | test.py:62:10:62:18 | ControlFlowNode for Lambda | test.py:62:10:62:18 | ControlFlowNode for Lambda | This lambda captures the loop variable $@, and may escape the loop by being stored at $@. | test.py:62:10:62:39 | For | i | test.py:62:10:62:18 | ControlFlowNode for Lambda | this location |
|
||||
@@ -1 +0,0 @@
|
||||
Variables/LoopVariableCapture/LoopVariableCapture.ql
|
||||
@@ -0,0 +1,19 @@
|
||||
import python
|
||||
import Variables.LoopVariableCapture.LoopVariableCaptureQuery
|
||||
import utils.test.InlineExpectationsTest
|
||||
|
||||
module MethodArgTest implements TestSig {
|
||||
string getARelevantTag() { result = ["capturedVar"] }
|
||||
|
||||
predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(CallableExpr capturing, AstNode loop, Variable var |
|
||||
escapingCapture(capturing, loop, var, _, _) and
|
||||
element = capturing.toString() and
|
||||
location = capturing.getLocation() and
|
||||
tag = "capturedVar" and
|
||||
value = var.getId()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import MakeTest<MethodArgTest>
|
||||
@@ -4,10 +4,10 @@ def bad1():
|
||||
for x in range(10):
|
||||
def inner(): # $capturedVar=x
|
||||
return x
|
||||
results.append(inner)
|
||||
results.append(inner)
|
||||
return results
|
||||
|
||||
a = [lambda: i for i in range(1, 4)] # $capturedVar=a
|
||||
a = [lambda: i for i in range(1, 4)] # $capturedVar=i
|
||||
for f in a:
|
||||
print(f())
|
||||
|
||||
@@ -18,7 +18,14 @@ def good1():
|
||||
for y in range(10):
|
||||
def inner(y=y):
|
||||
return y
|
||||
results.append(inner)
|
||||
results.append(inner)
|
||||
return results
|
||||
|
||||
# OK: Using default argument.
|
||||
def good2():
|
||||
results = []
|
||||
for y in range(10):
|
||||
results.append(lambda y=y: y)
|
||||
return results
|
||||
|
||||
#Factory function
|
||||
@@ -46,7 +53,7 @@ s = {lambda: i for i in range(1, 4)} # $capturedVar=i
|
||||
for f in s:
|
||||
print(f())
|
||||
|
||||
d = {i:lambda: i for i in range(1, 4)} # $capturedVar=d
|
||||
d = {i:lambda: i for i in range(1, 4)} # $capturedVar=i
|
||||
for k, f in d.items():
|
||||
print(k, f())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user