mirror of
https://github.com/github/codeql.git
synced 2026-04-30 03:05:15 +02:00
Merge pull request #7806 from erik-krogh/pyDef
Python: Add def nodes to API graphs
This commit is contained in:
@@ -6,7 +6,8 @@
|
||||
* directed and labeled; they specify how the components represented by nodes relate to each other.
|
||||
*/
|
||||
|
||||
private import python
|
||||
// Importing python under the `py` namespace to avoid importing `CallNode` from `Flow.qll` and thereby having a naming conflict with `API::CallNode`.
|
||||
private import python as py
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
/**
|
||||
@@ -39,6 +40,30 @@ module API {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data-flow node corresponding to the right-hand side of a definition of the API
|
||||
* component represented by this node.
|
||||
*
|
||||
* For example, in the property write `foo.bar = x`, variable `x` is the the right-hand side
|
||||
* of a write to the `bar` property of `foo`.
|
||||
*
|
||||
* Note that for parameters, it is the arguments flowing into that parameter that count as
|
||||
* right-hand sides of the definition, not the declaration of the parameter itself.
|
||||
* Consequently, in :
|
||||
* ```python
|
||||
* from mypkg import foo;
|
||||
* foo.bar(x)
|
||||
* ```
|
||||
* `x` is the right-hand side of a definition of the first parameter of `bar` from the `mypkg.foo` module.
|
||||
*/
|
||||
DataFlow::Node getARhs() { Impl::rhs(this, result) }
|
||||
|
||||
/**
|
||||
* Gets a data-flow node that may interprocedurally flow to the right-hand side of a definition
|
||||
* of the API component represented by this node.
|
||||
*/
|
||||
DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.getARhs()) }
|
||||
|
||||
/**
|
||||
* Gets an immediate use of the API component represented by this node.
|
||||
*
|
||||
@@ -55,7 +80,7 @@ module API {
|
||||
/**
|
||||
* Gets a call to the function represented by this API component.
|
||||
*/
|
||||
DataFlow::CallCfgNode getACall() { result = this.getReturn().getAnImmediateUse() }
|
||||
CallNode getACall() { result = this.getReturn().getAnImmediateUse() }
|
||||
|
||||
/**
|
||||
* Gets a node representing member `m` of this API component.
|
||||
@@ -92,6 +117,29 @@ module API {
|
||||
*/
|
||||
Node getReturn() { result = this.getASuccessor(Label::return()) }
|
||||
|
||||
/**
|
||||
* Gets a node representing the `i`th parameter of the function represented by this node.
|
||||
*
|
||||
* This predicate may have multiple results when there are multiple invocations of this API component.
|
||||
* Consider using `getAnInvocation()` if there is a need to distingiush between individual calls.
|
||||
*/
|
||||
Node getParameter(int i) { result = this.getASuccessor(Label::parameter(i)) }
|
||||
|
||||
/**
|
||||
* Gets the node representing the keyword parameter `name` of the function represented by this node.
|
||||
*
|
||||
* This predicate may have multiple results when there are multiple invocations of this API component.
|
||||
* Consider using `getAnInvocation()` if there is a need to distingiush between individual calls.
|
||||
*/
|
||||
Node getKeywordParameter(string name) {
|
||||
result = this.getASuccessor(Label::keywordParameter(name))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of parameters of the function represented by this node.
|
||||
*/
|
||||
int getNumParameter() { result = max(int s | exists(this.getParameter(s))) + 1 }
|
||||
|
||||
/**
|
||||
* Gets a node representing a subclass of the class represented by this node.
|
||||
*/
|
||||
@@ -137,7 +185,7 @@ module API {
|
||||
/**
|
||||
* Gets the data-flow node that gives rise to this node, if any.
|
||||
*/
|
||||
DataFlow::Node getInducingNode() { this = Impl::MkUse(result) }
|
||||
DataFlow::Node getInducingNode() { this = Impl::MkUse(result) or this = Impl::MkDef(result) }
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
@@ -210,6 +258,17 @@ module API {
|
||||
}
|
||||
}
|
||||
|
||||
/** A node corresponding to the rhs of an API component. */
|
||||
class Def extends Node, Impl::TDef {
|
||||
override string toString() {
|
||||
exists(string type | this = Impl::MkDef(_) and type = "Def " |
|
||||
result = type + this.getPath()
|
||||
or
|
||||
not exists(this.getPath()) and result = type + "with no path"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the root node. */
|
||||
Root root() { any() }
|
||||
|
||||
@@ -225,6 +284,62 @@ module API {
|
||||
/** Gets a node corresponding to the built-in with the given name, if any. */
|
||||
Node builtin(string n) { result = moduleImport("builtins").getMember(n) }
|
||||
|
||||
/**
|
||||
* An `CallCfgNode` that is connected to the API graph.
|
||||
*
|
||||
* Can be used to reason about calls to an external API in which the correlation between
|
||||
* parameters and/or return values must be retained.
|
||||
*
|
||||
* The member predicates `getParameter`, `getKeywordParameter`, `getReturn`, and `getInstance` mimic
|
||||
* the corresponding predicates from `API::Node`. These are guaranteed to exist and be unique to this call.
|
||||
*/
|
||||
class CallNode extends DataFlow::CallCfgNode {
|
||||
API::Node callee;
|
||||
|
||||
CallNode() { this = callee.getReturn().getAnImmediateUse() }
|
||||
|
||||
/** Gets the API node for the `i`th parameter of this invocation. */
|
||||
pragma[nomagic]
|
||||
Node getParameter(int i) {
|
||||
result = callee.getParameter(i) and
|
||||
result = this.getAParameterCandidate(i)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an API node where a RHS of the node is the `i`th argument to this call.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private Node getAParameterCandidate(int i) { result.getARhs() = this.getArg(i) }
|
||||
|
||||
/** Gets the API node for a parameter of this invocation. */
|
||||
Node getAParameter() { result = this.getParameter(_) }
|
||||
|
||||
/** Gets the API node for the keyword parameter `name` of this invocation. */
|
||||
Node getKeywordParameter(string name) {
|
||||
result = callee.getKeywordParameter(name) and
|
||||
result = this.getAKeywordParameterCandidate(name)
|
||||
}
|
||||
|
||||
/** Gets the API node for the parameter that has index `i` or has keyword `name`. */
|
||||
bindingset[i, name]
|
||||
Node getParameter(int i, string name) {
|
||||
result = this.getParameter(i)
|
||||
or
|
||||
result = this.getKeywordParameter(name)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private Node getAKeywordParameterCandidate(string name) {
|
||||
result.getARhs() = this.getArgByName(name)
|
||||
}
|
||||
|
||||
/** Gets the API node for the return value of this call. */
|
||||
Node getReturn() {
|
||||
result = callee.getReturn() and
|
||||
result.getAnImmediateUse() = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the actual implementation of API graphs, cached for performance.
|
||||
*
|
||||
@@ -312,23 +427,26 @@ module API {
|
||||
/** An abstract representative for imports of the module called `name`. */
|
||||
MkModuleImport(string name) {
|
||||
// Ignore the following module name for Python 2, as we alias `__builtin__` to `builtins` elsewhere
|
||||
(name != "__builtin__" or major_version() = 3) and
|
||||
(name != "__builtin__" or py::major_version() = 3) and
|
||||
(
|
||||
imports(_, name)
|
||||
or
|
||||
// When we `import foo.bar.baz` we want to create API graph nodes also for the prefixes
|
||||
// `foo` and `foo.bar`:
|
||||
name = any(ImportExpr e | not e.isRelative()).getAnImportedModuleName()
|
||||
name = any(py::ImportExpr e | not e.isRelative()).getAnImportedModuleName()
|
||||
)
|
||||
or
|
||||
// The `builtins` module should always be implicitly available
|
||||
name = "builtins"
|
||||
} or
|
||||
/** A use of an API member at the node `nd`. */
|
||||
MkUse(DataFlow::Node nd) { use(_, _, nd) }
|
||||
MkUse(DataFlow::Node nd) { use(_, _, nd) } or
|
||||
MkDef(DataFlow::Node nd) { rhs(_, _, nd) }
|
||||
|
||||
class TUse = MkModuleImport or MkUse;
|
||||
|
||||
class TDef = MkDef;
|
||||
|
||||
/**
|
||||
* Holds if the dotted module name `sub` refers to the `member` member of `base`.
|
||||
*
|
||||
@@ -351,7 +469,7 @@ module API {
|
||||
* Ignores relative imports, such as `from ..foo.bar import baz`.
|
||||
*/
|
||||
private predicate imports(DataFlow::Node imp, string name) {
|
||||
exists(ImportExprNode iexpr |
|
||||
exists(py::ImportExprNode iexpr |
|
||||
imp.asCfgNode() = iexpr and
|
||||
not iexpr.getNode().isRelative() and
|
||||
name = iexpr.getNode().getImportedModuleName()
|
||||
@@ -374,13 +492,55 @@ module API {
|
||||
*
|
||||
* `moduleImport("foo").getMember("bar")`
|
||||
*/
|
||||
private TApiNode potential_import_star_base(Scope s) {
|
||||
private TApiNode potential_import_star_base(py::Scope s) {
|
||||
exists(DataFlow::Node n |
|
||||
n.asCfgNode() = ImportStar::potentialImportStarBase(s) and
|
||||
use(result, n)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `rhs` is the right-hand side of a definition of a node that should have an
|
||||
* incoming edge from `base` labeled `lbl` in the API graph.
|
||||
*/
|
||||
cached
|
||||
predicate rhs(TApiNode base, Label::ApiLabel lbl, DataFlow::Node rhs) {
|
||||
exists(DataFlow::Node def, DataFlow::LocalSourceNode pred |
|
||||
rhs(base, def) and pred = trackDefNode(def)
|
||||
|
|
||||
// from `x` to a definition of `x.prop`
|
||||
exists(DataFlow::AttrWrite aw | aw = pred.getAnAttributeWrite() |
|
||||
lbl = Label::memberFromRef(aw) and
|
||||
rhs = aw.getValue()
|
||||
)
|
||||
or
|
||||
// TODO: I had expected `DataFlow::AttrWrite` to contain the attribute writes from a dict, that's how JS works.
|
||||
exists(py::Dict dict, py::KeyValuePair item |
|
||||
dict = pred.asExpr() and
|
||||
dict.getItem(_) = item and
|
||||
lbl = Label::member(item.getKey().(py::StrConst).getS()) and
|
||||
rhs.asExpr() = item.getValue()
|
||||
)
|
||||
or
|
||||
exists(py::CallableExpr fn | fn = pred.asExpr() |
|
||||
not fn.getInnerScope().isAsync() and
|
||||
lbl = Label::return() and
|
||||
exists(py::Return ret |
|
||||
rhs.asExpr() = ret.getValue() and
|
||||
ret.getScope() = fn.getInnerScope()
|
||||
)
|
||||
)
|
||||
)
|
||||
or
|
||||
argumentPassing(base, lbl, rhs)
|
||||
or
|
||||
exists(DataFlow::LocalSourceNode src, DataFlow::AttrWrite aw |
|
||||
use(base, src) and aw = trackUseNode(src).getAnAttributeWrite() and rhs = aw.getValue()
|
||||
|
|
||||
lbl = Label::memberFromRef(aw)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled
|
||||
* `lbl` in the API graph.
|
||||
@@ -399,7 +559,7 @@ module API {
|
||||
|
|
||||
// Referring to an attribute on a node that is a use of `base`:
|
||||
lbl = Label::memberFromRef(ref) and
|
||||
ref = pred.getAnAttributeReference()
|
||||
ref = pred.getAnAttributeRead()
|
||||
or
|
||||
// Calling a node that is a use of `base`
|
||||
lbl = Label::return() and
|
||||
@@ -408,7 +568,7 @@ module API {
|
||||
// Subclassing a node
|
||||
lbl = Label::subclass() and
|
||||
exists(DataFlow::Node superclass | pred.flowsTo(superclass) |
|
||||
ref.asExpr().(ClassExpr).getABase() = superclass.asExpr()
|
||||
ref.asExpr().(py::ClassExpr).getABase() = superclass.asExpr()
|
||||
)
|
||||
or
|
||||
// awaiting
|
||||
@@ -419,12 +579,26 @@ module API {
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node def, py::CallableExpr fn |
|
||||
rhs(base, def) and fn = trackDefNode(def).asExpr()
|
||||
|
|
||||
exists(int i |
|
||||
lbl = Label::parameter(i) and
|
||||
ref.asExpr() = fn.getInnerScope().getArg(i)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
lbl = Label::keywordParameter(name) and
|
||||
ref.asExpr() = fn.getInnerScope().getArgByName(name)
|
||||
)
|
||||
)
|
||||
or
|
||||
// Built-ins, treated as members of the module `builtins`
|
||||
base = MkModuleImport("builtins") and
|
||||
lbl = Label::member(any(string name | ref = Builtins::likelyBuiltin(name)))
|
||||
or
|
||||
// Unknown variables that may belong to a module imported with `import *`
|
||||
exists(Scope s |
|
||||
exists(py::Scope s |
|
||||
base = potential_import_star_base(s) and
|
||||
lbl =
|
||||
Label::member(any(string name |
|
||||
@@ -444,7 +618,7 @@ module API {
|
||||
)
|
||||
or
|
||||
// Ensure the Python 2 `__builtin__` module gets the name of the Python 3 `builtins` module.
|
||||
major_version() = 2 and
|
||||
py::major_version() = 2 and
|
||||
nd = MkModuleImport("builtins") and
|
||||
imports(ref, "__builtin__")
|
||||
or
|
||||
@@ -466,6 +640,42 @@ module API {
|
||||
exists(DataFlow::TypeTracker t2 | result = trackUseNode(src, t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `arg` is passed as an argument to a use of `base`.
|
||||
*
|
||||
* `lbl` is represents which parameter of the function was passed. Either a numbered parameter, or a keyword parameter.
|
||||
*/
|
||||
private predicate argumentPassing(TApiNode base, Label::ApiLabel lbl, DataFlow::Node arg) {
|
||||
exists(DataFlow::Node use, DataFlow::LocalSourceNode pred |
|
||||
use(base, use) and pred = trackUseNode(use)
|
||||
|
|
||||
exists(int i |
|
||||
lbl = Label::parameter(i) and
|
||||
arg = pred.getACall().getArg(i)
|
||||
)
|
||||
or
|
||||
exists(string name | lbl = Label::keywordParameter(name) |
|
||||
arg = pred.getACall().getArgByName(name)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that inter-procedurally flows into `nd`, which is a definition of some node.
|
||||
*/
|
||||
cached
|
||||
DataFlow::LocalSourceNode trackDefNode(DataFlow::Node nd) {
|
||||
result = trackDefNode(nd, DataFlow::TypeBackTracker::end())
|
||||
}
|
||||
|
||||
private DataFlow::LocalSourceNode trackDefNode(DataFlow::Node nd, DataFlow::TypeBackTracker t) {
|
||||
t.start() and
|
||||
rhs(_, nd) and
|
||||
result = nd.getALocalSource()
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 | result = trackDefNode(nd, t2).backtrack(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
|
||||
*
|
||||
@@ -477,6 +687,12 @@ module API {
|
||||
not result instanceof DataFlow::ModuleVariableNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `rhs` is the right-hand side of a definition of node `nd`.
|
||||
*/
|
||||
cached
|
||||
predicate rhs(TApiNode nd, DataFlow::Node rhs) { nd = MkDef(rhs) }
|
||||
|
||||
/**
|
||||
* Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
|
||||
*/
|
||||
@@ -485,8 +701,7 @@ module API {
|
||||
/* There's an edge from the root node for each imported module. */
|
||||
exists(string m |
|
||||
pred = MkRoot() and
|
||||
lbl = Label::mod(m)
|
||||
|
|
||||
lbl = Label::mod(m) and
|
||||
succ = MkModuleImport(m) and
|
||||
// Only allow undotted names to count as base modules.
|
||||
not m.matches("%.%")
|
||||
@@ -503,6 +718,11 @@ module API {
|
||||
use(pred, lbl, ref) and
|
||||
succ = MkUse(ref)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node rhs |
|
||||
rhs(pred, lbl, rhs) and
|
||||
succ = MkDef(rhs)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -530,16 +750,27 @@ module API {
|
||||
private import semmle.python.dataflow.new.internal.ImportStar
|
||||
|
||||
newtype TLabel =
|
||||
MkLabelModule(string mod) { exists(Impl::MkModuleImport(mod)) } or
|
||||
MkLabelModule(string mod) {
|
||||
exists(Impl::MkModuleImport(mod)) and
|
||||
not mod.matches("%.%") // only top level modules count as base modules
|
||||
} or
|
||||
MkLabelMember(string member) {
|
||||
member = any(DataFlow::AttrRef pr).getAttributeName() or
|
||||
exists(Builtins::likelyBuiltin(member)) or
|
||||
ImportStar::namePossiblyDefinedInImportStar(_, member, _) or
|
||||
Impl::prefix_member(_, member, _)
|
||||
Impl::prefix_member(_, member, _) or
|
||||
member = any(py::Dict d).getAnItem().(py::KeyValuePair).getKey().(py::StrConst).getS()
|
||||
} or
|
||||
MkLabelUnknownMember() or
|
||||
MkLabelParameter(int i) {
|
||||
none() // TODO: Fill in when adding def nodes
|
||||
exists(any(DataFlow::CallCfgNode c).getArg(i))
|
||||
or
|
||||
exists(any(py::Function f).getArg(i))
|
||||
} or
|
||||
MkLabelKeywordParameter(string name) {
|
||||
exists(any(DataFlow::CallCfgNode c).getArgByName(name))
|
||||
or
|
||||
exists(any(py::Function f).getArgByName(name))
|
||||
} or
|
||||
MkLabelReturn() or
|
||||
MkLabelSubclass() or
|
||||
@@ -588,6 +819,18 @@ module API {
|
||||
int getIndex() { result = i }
|
||||
}
|
||||
|
||||
/** A label for a keyword parameter `name`. */
|
||||
class LabelKeywordParameter extends ApiLabel {
|
||||
string name;
|
||||
|
||||
LabelKeywordParameter() { this = MkLabelKeywordParameter(name) }
|
||||
|
||||
override string toString() { result = "getKeywordParameter(\"" + name + "\")" }
|
||||
|
||||
/** Gets the name of the parameter for this label. */
|
||||
string getName() { result = name }
|
||||
}
|
||||
|
||||
/** A label that gets the return value of a function. */
|
||||
class LabelReturn extends ApiLabel {
|
||||
LabelReturn() { this = MkLabelReturn() }
|
||||
@@ -620,13 +863,19 @@ module API {
|
||||
LabelUnknownMember unknownMember() { any() }
|
||||
|
||||
/** Gets the `member` edge label for the given attribute reference. */
|
||||
ApiLabel memberFromRef(DataFlow::AttrRef pr) {
|
||||
result = member(pr.getAttributeName())
|
||||
ApiLabel memberFromRef(DataFlow::AttrRef ref) {
|
||||
result = member(ref.getAttributeName())
|
||||
or
|
||||
not exists(pr.getAttributeName()) and
|
||||
not exists(ref.getAttributeName()) and
|
||||
result = unknownMember()
|
||||
}
|
||||
|
||||
/** Gets the `parameter` edge label for parameter `i`. */
|
||||
LabelParameter parameter(int i) { result.getIndex() = i }
|
||||
|
||||
/** Gets the `parameter` edge label for the keyword parameter `name`. */
|
||||
LabelKeywordParameter keywordParameter(string name) { result.getName() = name }
|
||||
|
||||
/** Gets the `return` edge label. */
|
||||
LabelReturn return() { any() }
|
||||
|
||||
|
||||
@@ -50,26 +50,10 @@ private module Aiomysql {
|
||||
* A query. Calling `execute` on a `Cursor` constructs a query.
|
||||
* See https://aiomysql.readthedocs.io/en/stable/cursors.html#Cursor.execute
|
||||
*/
|
||||
class CursorExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
|
||||
class CursorExecuteCall extends SqlConstruction::Range, API::CallNode {
|
||||
CursorExecuteCall() { this = cursor().getMember("execute").getACall() }
|
||||
|
||||
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("operation")] }
|
||||
}
|
||||
|
||||
/**
|
||||
* This is only needed to connect the argument to the execute call with the subsequnt awaiting.
|
||||
* It should be obsolete once we have `API::CallNode` available.
|
||||
*/
|
||||
private DataFlow::TypeTrackingNode cursorExecuteCall(DataFlow::TypeTracker t, DataFlow::Node sql) {
|
||||
// cursor created from connection
|
||||
t.start() and
|
||||
sql = result.(CursorExecuteCall).getSql()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = cursorExecuteCall(t2, sql).track(t2, t))
|
||||
}
|
||||
|
||||
DataFlow::Node cursorExecuteCall(DataFlow::Node sql) {
|
||||
cursorExecuteCall(DataFlow::TypeTracker::end(), sql).flowsTo(result)
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "operation").getARhs() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,11 +61,11 @@ private module Aiomysql {
|
||||
* See https://aiomysql.readthedocs.io/en/stable/cursors.html#Cursor.execute
|
||||
*/
|
||||
class AwaitedCursorExecuteCall extends SqlExecution::Range {
|
||||
DataFlow::Node sql;
|
||||
CursorExecuteCall executeCall;
|
||||
|
||||
AwaitedCursorExecuteCall() { this = awaited(cursorExecuteCall(sql)) }
|
||||
AwaitedCursorExecuteCall() { this = executeCall.getReturn().getAwaited().getAnImmediateUse() }
|
||||
|
||||
override DataFlow::Node getSql() { result = sql }
|
||||
override DataFlow::Node getSql() { result = executeCall.getSql() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,28 +91,10 @@ private module Aiomysql {
|
||||
* A query. Calling `execute` on a `SAConnection` constructs a query.
|
||||
* See https://aiomysql.readthedocs.io/en/stable/sa.html#aiomysql.sa.SAConnection.execute
|
||||
*/
|
||||
class SAConnectionExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
|
||||
class SAConnectionExecuteCall extends SqlConstruction::Range, API::CallNode {
|
||||
SAConnectionExecuteCall() { this = saConnection().getMember("execute").getACall() }
|
||||
|
||||
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
|
||||
}
|
||||
|
||||
/**
|
||||
* This is only needed to connect the argument to the execute call with the subsequnt awaiting.
|
||||
* It should be obsolete once we have `API::CallNode` available.
|
||||
*/
|
||||
private DataFlow::TypeTrackingNode saConnectionExecuteCall(
|
||||
DataFlow::TypeTracker t, DataFlow::Node sql
|
||||
) {
|
||||
// saConnection created from engine
|
||||
t.start() and
|
||||
sql = result.(SAConnectionExecuteCall).getSql()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = saConnectionExecuteCall(t2, sql).track(t2, t))
|
||||
}
|
||||
|
||||
DataFlow::Node saConnectionExecuteCall(DataFlow::Node sql) {
|
||||
saConnectionExecuteCall(DataFlow::TypeTracker::end(), sql).flowsTo(result)
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,10 +102,10 @@ private module Aiomysql {
|
||||
* See https://aiomysql.readthedocs.io/en/stable/sa.html#aiomysql.sa.SAConnection.execute
|
||||
*/
|
||||
class AwaitedSAConnectionExecuteCall extends SqlExecution::Range {
|
||||
DataFlow::Node sql;
|
||||
SAConnectionExecuteCall execute;
|
||||
|
||||
AwaitedSAConnectionExecuteCall() { this = awaited(saConnectionExecuteCall(sql)) }
|
||||
AwaitedSAConnectionExecuteCall() { this = execute.getReturn().getAwaited().getAnImmediateUse() }
|
||||
|
||||
override DataFlow::Node getSql() { result = sql }
|
||||
override DataFlow::Node getSql() { result = execute.getSql() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,26 +50,10 @@ private module Aiopg {
|
||||
* A query. Calling `execute` on a `Cursor` constructs a query.
|
||||
* See https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Cursor.execute
|
||||
*/
|
||||
class CursorExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
|
||||
class CursorExecuteCall extends SqlConstruction::Range, API::CallNode {
|
||||
CursorExecuteCall() { this = cursor().getMember("execute").getACall() }
|
||||
|
||||
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("operation")] }
|
||||
}
|
||||
|
||||
/**
|
||||
* This is only needed to connect the argument to the execute call with the subsequnt awaiting.
|
||||
* It should be obsolete once we have `API::CallNode` available.
|
||||
*/
|
||||
private DataFlow::TypeTrackingNode cursorExecuteCall(DataFlow::TypeTracker t, DataFlow::Node sql) {
|
||||
// cursor created from connection
|
||||
t.start() and
|
||||
sql = result.(CursorExecuteCall).getSql()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = cursorExecuteCall(t2, sql).track(t2, t))
|
||||
}
|
||||
|
||||
DataFlow::Node cursorExecuteCall(DataFlow::Node sql) {
|
||||
cursorExecuteCall(DataFlow::TypeTracker::end(), sql).flowsTo(result)
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "operation").getARhs() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,11 +61,11 @@ private module Aiopg {
|
||||
* See https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Cursor.execute
|
||||
*/
|
||||
class AwaitedCursorExecuteCall extends SqlExecution::Range {
|
||||
DataFlow::Node sql;
|
||||
CursorExecuteCall execute;
|
||||
|
||||
AwaitedCursorExecuteCall() { this = awaited(cursorExecuteCall(sql)) }
|
||||
AwaitedCursorExecuteCall() { this = execute.getReturn().getAwaited().getAnImmediateUse() }
|
||||
|
||||
override DataFlow::Node getSql() { result = sql }
|
||||
override DataFlow::Node getSql() { result = execute.getSql() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,28 +87,10 @@ private module Aiopg {
|
||||
* A query. Calling `execute` on a `SAConnection` constructs a query.
|
||||
* See https://aiopg.readthedocs.io/en/stable/sa.html#aiopg.sa.SAConnection.execute
|
||||
*/
|
||||
class SAConnectionExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
|
||||
class SAConnectionExecuteCall extends SqlConstruction::Range, API::CallNode {
|
||||
SAConnectionExecuteCall() { this = saConnection().getMember("execute").getACall() }
|
||||
|
||||
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
|
||||
}
|
||||
|
||||
/**
|
||||
* This is only needed to connect the argument to the execute call with the subsequnt awaiting.
|
||||
* It should be obsolete once we have `API::CallNode` available.
|
||||
*/
|
||||
private DataFlow::TypeTrackingNode saConnectionExecuteCall(
|
||||
DataFlow::TypeTracker t, DataFlow::Node sql
|
||||
) {
|
||||
// saConnection created from engine
|
||||
t.start() and
|
||||
sql = result.(SAConnectionExecuteCall).getSql()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = saConnectionExecuteCall(t2, sql).track(t2, t))
|
||||
}
|
||||
|
||||
DataFlow::Node saConnectionExecuteCall(DataFlow::Node sql) {
|
||||
saConnectionExecuteCall(DataFlow::TypeTracker::end(), sql).flowsTo(result)
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,10 +98,10 @@ private module Aiopg {
|
||||
* See https://aiopg.readthedocs.io/en/stable/sa.html#aiopg.sa.SAConnection.execute
|
||||
*/
|
||||
class AwaitedSAConnectionExecuteCall extends SqlExecution::Range {
|
||||
DataFlow::Node sql;
|
||||
SAConnectionExecuteCall excute;
|
||||
|
||||
AwaitedSAConnectionExecuteCall() { this = awaited(saConnectionExecuteCall(sql)) }
|
||||
AwaitedSAConnectionExecuteCall() { this = excute.getReturn().getAwaited().getAnImmediateUse() }
|
||||
|
||||
override DataFlow::Node getSql() { result = sql }
|
||||
override DataFlow::Node getSql() { result = excute.getSql() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,48 +71,27 @@ private module Asyncpg {
|
||||
* The result of calling `prepare(query)` is a `PreparedStatementFactory` and the argument, `query` needs to
|
||||
* be tracked to the place where a `PreparedStatement` is created and then futher to any executing methods.
|
||||
* Hence the two type trackers.
|
||||
*
|
||||
* TODO: Rewrite this, once we have `API::CallNode` available.
|
||||
*/
|
||||
module PreparedStatement {
|
||||
class PreparedStatementConstruction extends SqlConstruction::Range, DataFlow::CallCfgNode {
|
||||
class PreparedStatementConstruction extends SqlConstruction::Range, API::CallNode {
|
||||
PreparedStatementConstruction() { this = connection().getMember("prepare").getACall() }
|
||||
|
||||
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() }
|
||||
}
|
||||
|
||||
private DataFlow::TypeTrackingNode preparedStatementFactory(
|
||||
DataFlow::TypeTracker t, DataFlow::Node sql
|
||||
) {
|
||||
t.start() and
|
||||
sql = result.(PreparedStatementConstruction).getSql()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = preparedStatementFactory(t2, sql).track(t2, t))
|
||||
}
|
||||
|
||||
DataFlow::Node preparedStatementFactory(DataFlow::Node sql) {
|
||||
preparedStatementFactory(DataFlow::TypeTracker::end(), sql).flowsTo(result)
|
||||
}
|
||||
|
||||
private DataFlow::TypeTrackingNode preparedStatement(DataFlow::TypeTracker t, DataFlow::Node sql) {
|
||||
t.start() and
|
||||
result = awaited(preparedStatementFactory(sql))
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = preparedStatement(t2, sql).track(t2, t))
|
||||
}
|
||||
|
||||
DataFlow::Node preparedStatement(DataFlow::Node sql) {
|
||||
preparedStatement(DataFlow::TypeTracker::end(), sql).flowsTo(result)
|
||||
}
|
||||
|
||||
class PreparedStatementExecution extends SqlExecution::Range, DataFlow::MethodCallNode {
|
||||
DataFlow::Node sql;
|
||||
class PreparedStatementExecution extends SqlExecution::Range, API::CallNode {
|
||||
PreparedStatementConstruction prepareCall;
|
||||
|
||||
PreparedStatementExecution() {
|
||||
this.calls(preparedStatement(sql), ["executemany", "fetch", "fetchrow", "fetchval"])
|
||||
this =
|
||||
prepareCall
|
||||
.getReturn()
|
||||
.getAwaited()
|
||||
.getMember(["executemany", "fetch", "fetchrow", "fetchval"])
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = sql }
|
||||
override DataFlow::Node getSql() { result = prepareCall.getSql() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,37 +103,36 @@ private module Asyncpg {
|
||||
* The result of calling `cursor` in either case is a `CursorFactory` and the argument, `query` needs to
|
||||
* be tracked to the place where a `Cursor` is created, hence the type tracker.
|
||||
* The creation of the `Cursor` executes the query.
|
||||
*
|
||||
* TODO: Rewrite this, once we have `API::CallNode` available.
|
||||
*/
|
||||
module Cursor {
|
||||
class CursorConstruction extends SqlConstruction::Range, DataFlow::CallCfgNode {
|
||||
class CursorConstruction extends SqlConstruction::Range, API::CallNode {
|
||||
CursorConstruction() { this = connection().getMember("cursor").getACall() }
|
||||
|
||||
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
|
||||
}
|
||||
|
||||
private DataFlow::TypeTrackingNode cursorFactory(DataFlow::TypeTracker t, DataFlow::Node sql) {
|
||||
// cursor created from connection
|
||||
t.start() and
|
||||
sql = result.(CursorConstruction).getSql()
|
||||
or
|
||||
// cursor created from prepared statement
|
||||
t.start() and
|
||||
result.(DataFlow::MethodCallNode).calls(PreparedStatement::preparedStatement(sql), "cursor")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = cursorFactory(t2, sql).track(t2, t))
|
||||
}
|
||||
|
||||
DataFlow::Node cursorFactory(DataFlow::Node sql) {
|
||||
cursorFactory(DataFlow::TypeTracker::end(), sql).flowsTo(result)
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() }
|
||||
}
|
||||
|
||||
/** The creation of a `Cursor` executes the associated query. */
|
||||
class CursorCreation extends SqlExecution::Range {
|
||||
DataFlow::Node sql;
|
||||
|
||||
CursorCreation() { this = awaited(cursorFactory(sql)) }
|
||||
CursorCreation() {
|
||||
exists(CursorConstruction c |
|
||||
sql = c.getSql() and
|
||||
this = c.getReturn().getAwaited().getAnImmediateUse()
|
||||
)
|
||||
or
|
||||
exists(PreparedStatement::PreparedStatementConstruction prepareCall |
|
||||
sql = prepareCall.getSql() and
|
||||
this =
|
||||
prepareCall
|
||||
.getReturn()
|
||||
.getAwaited()
|
||||
.getMember("cursor")
|
||||
.getReturn()
|
||||
.getAwaited()
|
||||
.getAnImmediateUse()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = sql }
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ private import semmle.python.frameworks.Stdlib
|
||||
* - https://docs.python-requests.org/en/latest/
|
||||
*/
|
||||
private module Requests {
|
||||
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
private class OutgoingRequestCall extends HTTP::Client::Request::Range, API::CallNode {
|
||||
string methodName;
|
||||
|
||||
OutgoingRequestCall() {
|
||||
@@ -54,14 +54,11 @@ private module Requests {
|
||||
result = this.getArg(1)
|
||||
}
|
||||
|
||||
/** Gets the `verify` argument to this outgoing requests call. */
|
||||
DataFlow::Node getVerifyArg() { result = this.getArgByName("verify") }
|
||||
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
disablingNode = this.getVerifyArg() and
|
||||
argumentOrigin = verifyArgBacktracker(disablingNode) and
|
||||
disablingNode = this.getKeywordParameter("verify").getARhs() and
|
||||
argumentOrigin = this.getKeywordParameter("verify").getAValueReachingRhs() and
|
||||
argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and
|
||||
not argumentOrigin.asExpr() instanceof None
|
||||
}
|
||||
@@ -79,22 +76,6 @@ private module Requests {
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a back-reference to the verify argument `arg`. */
|
||||
private DataFlow::TypeTrackingNode verifyArgBacktracker(
|
||||
DataFlow::TypeBackTracker t, DataFlow::Node arg
|
||||
) {
|
||||
t.start() and
|
||||
arg = any(OutgoingRequestCall c).getVerifyArg() and
|
||||
result = arg.getALocalSource()
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 | result = verifyArgBacktracker(t2, arg).backtrack(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a back-reference to the verify argument `arg`. */
|
||||
private DataFlow::LocalSourceNode verifyArgBacktracker(DataFlow::Node arg) {
|
||||
result = verifyArgBacktracker(DataFlow::TypeBackTracker::end(), arg)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Response
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -2595,81 +2595,37 @@ private module StdlibPrivate {
|
||||
// ---------------------------------------------------------------------------
|
||||
// hashlib
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a back-reference to the hashname argument `arg` that was used in a call to `hashlib.new`. */
|
||||
private DataFlow::TypeTrackingNode hashlibNewCallNameBacktracker(
|
||||
DataFlow::TypeBackTracker t, DataFlow::Node arg
|
||||
) {
|
||||
t.start() and
|
||||
hashlibNewCallImpl(_, arg) and
|
||||
result = arg.getALocalSource()
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 |
|
||||
result = hashlibNewCallNameBacktracker(t2, arg).backtrack(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a back-reference to the hashname argument `arg` that was used in a call to `hashlib.new`. */
|
||||
private DataFlow::LocalSourceNode hashlibNewCallNameBacktracker(DataFlow::Node arg) {
|
||||
result = hashlibNewCallNameBacktracker(DataFlow::TypeBackTracker::end(), arg)
|
||||
}
|
||||
|
||||
/** Holds when `call` is a call to `hashlib.new` with `nameArg` as the first argument. */
|
||||
private predicate hashlibNewCallImpl(DataFlow::CallCfgNode call, DataFlow::Node nameArg) {
|
||||
call = API::moduleImport("hashlib").getMember("new").getACall() and
|
||||
nameArg in [call.getArg(0), call.getArgByName("name")]
|
||||
}
|
||||
|
||||
/** Gets a call to `hashlib.new` with `algorithmName` as the first argument. */
|
||||
private DataFlow::CallCfgNode hashlibNewCall(string algorithmName) {
|
||||
exists(DataFlow::Node origin, DataFlow::Node nameArg |
|
||||
origin = hashlibNewCallNameBacktracker(nameArg) and
|
||||
algorithmName = origin.asExpr().(StrConst).getText() and
|
||||
hashlibNewCallImpl(result, nameArg)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a reference to the result of calling `hashlib.new` with `algorithmName` as the first argument. */
|
||||
private DataFlow::TypeTrackingNode hashlibNewResult(DataFlow::TypeTracker t, string algorithmName) {
|
||||
t.start() and
|
||||
result = hashlibNewCall(algorithmName)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = hashlibNewResult(t2, algorithmName).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the result of calling `hashlib.new` with `algorithmName` as the first argument. */
|
||||
DataFlow::Node hashlibNewResult(string algorithmName) {
|
||||
hashlibNewResult(DataFlow::TypeTracker::end(), algorithmName).flowsTo(result)
|
||||
private API::CallNode hashlibNewCall(string algorithmName) {
|
||||
algorithmName =
|
||||
result.getParameter(0, "name").getAValueReachingRhs().asExpr().(StrConst).getText() and
|
||||
result = API::moduleImport("hashlib").getMember("new").getACall()
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing operation by supplying initial data when calling the `hashlib.new` function.
|
||||
*/
|
||||
class HashlibNewCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode {
|
||||
class HashlibNewCall extends Cryptography::CryptographicOperation::Range, API::CallNode {
|
||||
string hashName;
|
||||
|
||||
HashlibNewCall() {
|
||||
this = hashlibNewCall(hashName) and
|
||||
exists([this.getArg(1), this.getArgByName("data")])
|
||||
exists(this.getParameter(1, "data"))
|
||||
}
|
||||
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
|
||||
|
||||
override DataFlow::Node getAnInput() { result in [this.getArg(1), this.getArgByName("data")] }
|
||||
override DataFlow::Node getAnInput() { result = this.getParameter(1, "data").getARhs() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing operation by using the `update` method on the result of calling the `hashlib.new` function.
|
||||
*/
|
||||
class HashlibNewUpdateCall extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
class HashlibNewUpdateCall extends Cryptography::CryptographicOperation::Range, API::CallNode {
|
||||
string hashName;
|
||||
|
||||
HashlibNewUpdateCall() {
|
||||
exists(DataFlow::AttrRead attr |
|
||||
attr.getObject() = hashlibNewResult(hashName) and
|
||||
this.getFunction() = attr and
|
||||
attr.getAttributeName() = "update"
|
||||
)
|
||||
this = hashlibNewCall(hashName).getReturn().getMember("update").getACall()
|
||||
}
|
||||
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
|
||||
|
||||
@@ -15,41 +15,11 @@ private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Gets a call to a method that makes an outgoing request using the `requests` module,
|
||||
* such as `requests.get` or `requests.put`, with the specified HTTP verb `verb`
|
||||
*/
|
||||
DataFlow::CallCfgNode outgoingRequestCall(string verb) {
|
||||
verb = HTTP::httpVerbLower() and
|
||||
result = API::moduleImport("requests").getMember(verb).getACall()
|
||||
}
|
||||
|
||||
/** Gets the "verfiy" argument to a outgoingRequestCall. */
|
||||
DataFlow::Node verifyArg(DataFlow::CallCfgNode call) {
|
||||
call = outgoingRequestCall(_) and
|
||||
result = call.getArgByName("verify")
|
||||
}
|
||||
|
||||
/** Gets a back-reference to the verify argument `arg`. */
|
||||
private DataFlow::TypeTrackingNode verifyArgBacktracker(
|
||||
DataFlow::TypeBackTracker t, DataFlow::Node arg
|
||||
) {
|
||||
t.start() and
|
||||
arg = verifyArg(_) and
|
||||
result = arg.getALocalSource()
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 | result = verifyArgBacktracker(t2, arg).backtrack(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a back-reference to the verify argument `arg`. */
|
||||
DataFlow::LocalSourceNode verifyArgBacktracker(DataFlow::Node arg) {
|
||||
result = verifyArgBacktracker(DataFlow::TypeBackTracker::end(), arg)
|
||||
}
|
||||
|
||||
from DataFlow::CallCfgNode call, DataFlow::Node falseyOrigin, string verb
|
||||
from API::CallNode call, DataFlow::Node falseyOrigin, string verb
|
||||
where
|
||||
call = outgoingRequestCall(verb) and
|
||||
falseyOrigin = verifyArgBacktracker(verifyArg(call)) and
|
||||
verb = HTTP::httpVerbLower() and
|
||||
call = API::moduleImport("requests").getMember(verb).getACall() and
|
||||
falseyOrigin = call.getKeywordParameter("verify").getAValueReachingRhs() and
|
||||
// requests treats `None` as the default and all other "falsey" values as `False`.
|
||||
falseyOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and
|
||||
not falseyOrigin.asExpr() instanceof None
|
||||
|
||||
132
python/ql/test/TestUtilities/VerifyApiGraphs.qll
Normal file
132
python/ql/test/TestUtilities/VerifyApiGraphs.qll
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* A test query that verifies assertions about the API graph embedded in source-code comments.
|
||||
*
|
||||
* An assertion is a comment of the form `def=<path>` or `use=<path>`, and asserts that
|
||||
* there is a def/use feature reachable from the root along the given path, and its
|
||||
* associated data-flow node must start on the same line as the comment.
|
||||
*
|
||||
* The query only produces output for failed assertions, meaning that it should have no output
|
||||
* under normal circumstances.
|
||||
*
|
||||
* The syntax is made to look exactly like inline expectation tests, so that the tests
|
||||
* can remain consistent with other Python tests.
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
private DataFlow::Node getNode(API::Node nd, string kind) {
|
||||
kind = "def" and
|
||||
result = nd.getARhs()
|
||||
or
|
||||
kind = "use" and
|
||||
result = nd.getAUse()
|
||||
}
|
||||
|
||||
private string getLocStr(Location loc) {
|
||||
exists(string filepath, int startline |
|
||||
loc.hasLocationInfo(filepath, startline, _, _, _) and
|
||||
result = filepath + ":" + startline
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An assertion matching a data-flow node against an API-graph feature.
|
||||
*/
|
||||
class Assertion extends Comment {
|
||||
string expectedKind;
|
||||
string expectedLoc;
|
||||
string path;
|
||||
string polarity;
|
||||
|
||||
Assertion() {
|
||||
exists(string txt, string rex |
|
||||
txt = this.getText().trim() and
|
||||
rex = "#\\$.*?((?:MISSING: )?)(def|use)=([\\w\\(\\)\"\\.]*).*"
|
||||
|
|
||||
polarity = txt.regexpCapture(rex, 1) and
|
||||
expectedKind = txt.regexpCapture(rex, 2) and
|
||||
path = txt.regexpCapture(rex, 3) and
|
||||
expectedLoc = getLocStr(this.getLocation())
|
||||
)
|
||||
}
|
||||
|
||||
string getEdgeLabel(int i) {
|
||||
// matches a single edge. E.g. `getParameter(1)` or `getMember("foo")`.
|
||||
// The lookbehind/lookahead ensure that the boundary is correct, that is
|
||||
// either the edge is next to a ".", or it's the end of the string.
|
||||
result = path.regexpFind("(?<=\\.|^)([\\w\\(\\)\"]+)(?=\\.|$)", i, _).trim()
|
||||
}
|
||||
|
||||
int getPathLength() { result = max(int i | exists(this.getEdgeLabel(i))) + 1 }
|
||||
|
||||
predicate isNegative() { polarity = "MISSING: " }
|
||||
|
||||
API::Node lookup(int i) {
|
||||
i = 0 and
|
||||
result = API::root()
|
||||
or
|
||||
result =
|
||||
this.lookup(i - 1)
|
||||
.getASuccessor(any(API::Label::ApiLabel label |
|
||||
label.toString() = this.getEdgeLabel(i - 1)
|
||||
))
|
||||
}
|
||||
|
||||
API::Node lookup() { result = this.lookup(this.getPathLength()) }
|
||||
|
||||
predicate holds() { getLocStr(getNode(this.lookup(), expectedKind).getLocation()) = expectedLoc }
|
||||
|
||||
string tryExplainFailure() {
|
||||
exists(int i, API::Node nd, string prefix, string suffix |
|
||||
nd = this.lookup(i) and
|
||||
i < getPathLength() and
|
||||
not exists(this.lookup([i + 1 .. getPathLength()])) and
|
||||
prefix = nd + " has no outgoing edge labelled " + this.getEdgeLabel(i) + ";" and
|
||||
if exists(nd.getASuccessor())
|
||||
then
|
||||
suffix =
|
||||
"it does have outgoing edges labelled " +
|
||||
concat(string lbl |
|
||||
exists(nd.getASuccessor(any(API::Label::ApiLabel label | label.toString() = lbl)))
|
||||
|
|
||||
lbl, ", "
|
||||
) + "."
|
||||
else suffix = "it has no outgoing edges at all."
|
||||
|
|
||||
result = prefix + " " + suffix
|
||||
)
|
||||
or
|
||||
exists(API::Node nd, string kind | nd = this.lookup() |
|
||||
exists(getNode(nd, kind)) and
|
||||
not exists(getNode(nd, expectedKind)) and
|
||||
result = "Expected " + expectedKind + " node, but found " + kind + " node."
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node nd | nd = getNode(this.lookup(), expectedKind) |
|
||||
not getLocStr(nd.getLocation()) = expectedLoc and
|
||||
result =
|
||||
"Node not found on this line (but there is one on line " + min(getLocStr(nd.getLocation())) +
|
||||
")."
|
||||
)
|
||||
}
|
||||
|
||||
string explainFailure() {
|
||||
if this.isNegative()
|
||||
then (
|
||||
this.holds() and
|
||||
result = "Negative assertion failed."
|
||||
) else (
|
||||
not this.holds() and
|
||||
(
|
||||
result = this.tryExplainFailure()
|
||||
or
|
||||
not exists(this.tryExplainFailure()) and
|
||||
result = "Positive assertion failed for unknown reasons."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
query predicate failed(Assertion a, string explanation) { explanation = a.explainFailure() }
|
||||
@@ -1,26 +0,0 @@
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
class AwaitedTest extends InlineExpectationsTest {
|
||||
AwaitedTest() { this = "AwaitedTest" }
|
||||
|
||||
override string getARelevantTag() { result = "awaited" }
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(API::Node awaited, DataFlow::Node use, API::Node pred |
|
||||
awaited = pred.getAwaited() and
|
||||
use = awaited.getAUse() and
|
||||
location = use.getLocation() and
|
||||
// Module variable nodes have no suitable location, so it's best to simply exclude them entirely
|
||||
// from the inline tests.
|
||||
not use instanceof DataFlow::ModuleVariableNode and
|
||||
exists(location.getFile().getRelativePath())
|
||||
|
|
||||
tag = "awaited" and
|
||||
value = pred.getPath() and
|
||||
element = use.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
foo = 42
|
||||
@@ -1 +0,0 @@
|
||||
pass
|
||||
@@ -1 +0,0 @@
|
||||
pass
|
||||
@@ -1,35 +0,0 @@
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
class ApiUseTest extends InlineExpectationsTest {
|
||||
ApiUseTest() { this = "ApiUseTest" }
|
||||
|
||||
override string getARelevantTag() { result = "use" }
|
||||
|
||||
private predicate relevant_node(API::Node a, DataFlow::Node n, Location l) {
|
||||
n = a.getAUse() and
|
||||
l = n.getLocation() and
|
||||
// Module variable nodes have no suitable location, so it's best to simply exclude them entirely
|
||||
// from the inline tests.
|
||||
not n instanceof DataFlow::ModuleVariableNode and
|
||||
exists(l.getFile().getRelativePath())
|
||||
}
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(API::Node a, DataFlow::Node n | relevant_node(a, n, location) |
|
||||
tag = "use" and
|
||||
// Only report the longest path on this line:
|
||||
value =
|
||||
max(API::Node a2, Location l2 |
|
||||
relevant_node(a2, _, l2) and
|
||||
l2.getFile() = location.getFile() and
|
||||
l2.getStartLine() = location.getStartLine()
|
||||
|
|
||||
a2.getPath()
|
||||
) and
|
||||
element = n.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import TestUtilities.VerifyApiGraphs
|
||||
61
python/ql/test/library-tests/ApiGraphs/py3/deftest1.py
Normal file
61
python/ql/test/library-tests/ApiGraphs/py3/deftest1.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from mypkg import foo #$ use=moduleImport("mypkg").getMember("foo")
|
||||
|
||||
def callback(x): #$ use=moduleImport("mypkg").getMember("foo").getMember("bar").getParameter(0).getParameter(0)
|
||||
x.baz() #$ use=moduleImport("mypkg").getMember("foo").getMember("bar").getParameter(0).getParameter(0).getMember("baz").getReturn()
|
||||
|
||||
foo.bar(callback) #$ def=moduleImport("mypkg").getMember("foo").getMember("bar").getParameter(0) use=moduleImport("mypkg").getMember("foo").getMember("bar").getReturn()
|
||||
|
||||
def callback2(x): #$ use=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("c").getParameter(0)
|
||||
x.baz2() #$ use=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("c").getParameter(0).getMember("baz2").getReturn()
|
||||
|
||||
mydict = {
|
||||
"c": callback2, #$ def=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("c")
|
||||
"other": "whatever" #$ def=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("other")
|
||||
}
|
||||
|
||||
foo.baz(mydict) #$ def=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0) use=moduleImport("mypkg").getMember("foo").getMember("baz").getReturn()
|
||||
|
||||
def callback3(x): #$ use=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("third").getParameter(0)
|
||||
x.baz3() #$ use=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("third").getParameter(0).getMember("baz3").getReturn()
|
||||
|
||||
mydict.third = callback3 #$ def=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("third")
|
||||
|
||||
foo.blab(mydict) #$ def=moduleImport("mypkg").getMember("foo").getMember("blab").getParameter(0) use=moduleImport("mypkg").getMember("foo").getMember("blab").getReturn()
|
||||
|
||||
def callback4(x): #$ use=moduleImport("mypkg").getMember("foo").getMember("quack").getParameter(0).getParameter(0)
|
||||
x.baz4() #$ use=moduleImport("mypkg").getMember("foo").getMember("quack").getParameter(0).getParameter(0).getMember("baz4").getReturn()
|
||||
|
||||
otherDict = {
|
||||
# TODO: Backtracking through a property set using a dict doesn't work, but I can backtrack through explicit property writes, e.g. the `otherDict.fourth` below.
|
||||
# TODO: There is a related TODO in ApiGraphs.qll
|
||||
"blab": "whatever"
|
||||
}
|
||||
otherDict.fourth = callback4
|
||||
|
||||
foo.quack(otherDict.fourth) #$ def=moduleImport("mypkg").getMember("foo").getMember("quack").getParameter(0) use=moduleImport("mypkg").getMember("foo").getMember("quack").getReturn()
|
||||
|
||||
def namedCallback(myName, otherName):
|
||||
# Using named parameters:
|
||||
myName() #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getParameter(0).getKeywordParameter("myName").getReturn()
|
||||
otherName() #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getParameter(0).getKeywordParameter("otherName").getReturn()
|
||||
# Using numbered parameters:
|
||||
myName() #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getParameter(0).getParameter(0).getReturn()
|
||||
otherName() #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getParameter(0).getParameter(1).getReturn()
|
||||
|
||||
foo.blob(namedCallback) #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getReturn()
|
||||
|
||||
foo.named(myName = 2) #$ def=moduleImport("mypkg").getMember("foo").getMember("named").getKeywordParameter("myName")
|
||||
|
||||
|
||||
def recusisionCallback(x):
|
||||
x.recursion() #$ use=moduleImport("mypkg").getMember("foo").getMember("rec").getParameter(0).getMember("callback").getParameter(0).getMember("recursion").getReturn()
|
||||
x.recursion() #$ use=moduleImport("mypkg").getMember("foo").getMember("rec").getParameter(0).getMember("rec1").getMember("callback").getParameter(0).getMember("recursion").getReturn()
|
||||
x.recursion() #$ use=moduleImport("mypkg").getMember("foo").getMember("rec").getParameter(0).getMember("rec1").getMember("rec2").getMember("callback").getParameter(0).getMember("recursion").getReturn()
|
||||
x.recursion() #$ use=moduleImport("mypkg").getMember("foo").getMember("rec").getParameter(0).getMember("rec1").getMember("rec2").getMember("rec1").getMember("callback").getParameter(0).getMember("recursion").getReturn()
|
||||
|
||||
recursiveDict = {};
|
||||
recursiveDict.callback = recusisionCallback;
|
||||
recursiveDict.rec1 = recursiveDict;
|
||||
recursiveDict.rec2 = recursiveDict;
|
||||
|
||||
foo.rec(recursiveDict); #$ def=moduleImport("mypkg").getMember("foo").getMember("rec").getParameter(0)
|
||||
19
python/ql/test/library-tests/ApiGraphs/py3/deftest2.py
Normal file
19
python/ql/test/library-tests/ApiGraphs/py3/deftest2.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Subclasses
|
||||
|
||||
from flask.views import View #$ use=moduleImport("flask").getMember("views").getMember("View")
|
||||
|
||||
class MyView(View): #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass()
|
||||
myvar = 45 #$ def=moduleImport("flask").getMember("views").getMember("View").getASubclass().getMember("myvar")
|
||||
def my_method(self): #$ def=moduleImport("flask").getMember("views").getMember("View").getASubclass().getMember("my_method")
|
||||
return 3 #$ def=moduleImport("flask").getMember("views").getMember("View").getASubclass().getMember("my_method").getReturn()
|
||||
|
||||
instance = MyView() #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass().getReturn()
|
||||
|
||||
def internal():
|
||||
from pflask.views import View #$ use=moduleImport("pflask").getMember("views").getMember("View")
|
||||
class IntMyView(View): #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass()
|
||||
my_internal_var = 35 #$ def=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getMember("my_internal_var")
|
||||
def my_internal_method(self): #$ def=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getMember("my_internal_method")
|
||||
pass
|
||||
|
||||
int_instance = IntMyView() #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getReturn()
|
||||
@@ -75,27 +75,6 @@ def f():
|
||||
sink(foo) #$ use=moduleImport("danger").getMember("SOURCE")
|
||||
|
||||
|
||||
# Subclasses
|
||||
|
||||
from flask.views import View #$ use=moduleImport("flask").getMember("views").getMember("View")
|
||||
|
||||
class MyView(View): #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass()
|
||||
myvar = 45 #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass().getMember("myvar")
|
||||
def my_method(self): #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass().getMember("my_method")
|
||||
pass
|
||||
|
||||
instance = MyView() #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass().getReturn()
|
||||
|
||||
def internal():
|
||||
from pflask.views import View #$ use=moduleImport("pflask").getMember("views").getMember("View")
|
||||
class IntMyView(View): #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass()
|
||||
my_internal_var = 35 #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getMember("my_internal_var")
|
||||
def my_internal_method(self): #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getMember("my_internal_method")
|
||||
pass
|
||||
|
||||
int_instance = IntMyView() #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getReturn()
|
||||
|
||||
|
||||
# Built-ins
|
||||
|
||||
def use_of_builtins():
|
||||
@@ -0,0 +1 @@
|
||||
import TestUtilities.VerifyApiGraphs
|
||||
Reference in New Issue
Block a user