mirror of
https://github.com/github/codeql.git
synced 2026-06-05 21:47:10 +02:00
Compare commits
37 Commits
codeql-cli
...
tausbn/pyt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28eec77cd8 | ||
|
|
09fc9f0bf2 | ||
|
|
330dba6ed7 | ||
|
|
6b64443c49 | ||
|
|
970349bc1f | ||
|
|
47421a63a4 | ||
|
|
603d37cd60 | ||
|
|
e2eb69ce8d | ||
|
|
c4ec331e96 | ||
|
|
4606d904ce | ||
|
|
54af9dd10b | ||
|
|
853df14468 | ||
|
|
156d2c09a0 | ||
|
|
7d8b4aca8b | ||
|
|
f5361f43dc | ||
|
|
f6cd63f508 | ||
|
|
5954287f89 | ||
|
|
c837e1491a | ||
|
|
d9693e70de | ||
|
|
0509ec6f0b | ||
|
|
0094271966 | ||
|
|
6c56882a75 | ||
|
|
7ceb6a5748 | ||
|
|
c4a4e20be0 | ||
|
|
e2dcfae3ee | ||
|
|
8fe680c716 | ||
|
|
8f154f6374 | ||
|
|
f4f217c993 | ||
|
|
c1beca80e6 | ||
|
|
793ecb6416 | ||
|
|
8ffcdfeb05 | ||
|
|
918e5e25ec | ||
|
|
485e949467 | ||
|
|
1159d20375 | ||
|
|
62d5cac6e0 | ||
|
|
452e189bbc | ||
|
|
5d5060b02b |
@@ -433,38 +433,6 @@ private predicate exits_early(BasicBlock b) {
|
|||||||
|
|
||||||
/** The metrics for a function that require points-to analysis */
|
/** The metrics for a function that require points-to analysis */
|
||||||
class FunctionMetricsWithPointsTo extends FunctionMetrics {
|
class FunctionMetricsWithPointsTo extends FunctionMetrics {
|
||||||
/**
|
|
||||||
* Gets the cyclomatic complexity of the function:
|
|
||||||
* The number of linearly independent paths through the source code.
|
|
||||||
* Computed as E - N + 2P,
|
|
||||||
* where
|
|
||||||
* E = the number of edges of the graph.
|
|
||||||
* N = the number of nodes of the graph.
|
|
||||||
* P = the number of connected components, which for a single function is 1.
|
|
||||||
*/
|
|
||||||
int getCyclomaticComplexity() {
|
|
||||||
exists(int e, int n |
|
|
||||||
n = count(BasicBlockWithPointsTo b | b = this.getABasicBlock() and b.likelyReachable()) and
|
|
||||||
e =
|
|
||||||
count(BasicBlockWithPointsTo b1, BasicBlockWithPointsTo b2 |
|
|
||||||
b1 = this.getABasicBlock() and
|
|
||||||
b1.likelyReachable() and
|
|
||||||
b2 = this.getABasicBlock() and
|
|
||||||
b2.likelyReachable() and
|
|
||||||
b2 = b1.getASuccessor() and
|
|
||||||
not b1.unlikelySuccessor(b2)
|
|
||||||
)
|
|
||||||
|
|
|
||||||
result = e - n + 2
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private BasicBlock getABasicBlock() {
|
|
||||||
result = this.getEntryNode().getBasicBlock()
|
|
||||||
or
|
|
||||||
exists(BasicBlock mid | mid = this.getABasicBlock() and result = mid.getASuccessor())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dependency of Callables
|
* Dependency of Callables
|
||||||
* One callable "this" depends on another callable "result"
|
* One callable "this" depends on another callable "result"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import python
|
import python
|
||||||
private import semmle.python.SelfAttribute
|
private import semmle.python.SelfAttribute
|
||||||
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
/** The metrics for a function */
|
/** The metrics for a function */
|
||||||
class FunctionMetrics extends Function {
|
class FunctionMetrics extends Function {
|
||||||
@@ -27,6 +28,32 @@ class FunctionMetrics extends Function {
|
|||||||
int getStatementNestingDepth() { result = max(Stmt s | s.getScope() = this | getNestingDepth(s)) }
|
int getStatementNestingDepth() { result = max(Stmt s | s.getScope() = this | getNestingDepth(s)) }
|
||||||
|
|
||||||
int getNumberOfCalls() { result = count(Call c | c.getScope() = this) }
|
int getNumberOfCalls() { result = count(Call c | c.getScope() = this) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the cyclomatic complexity of the function:
|
||||||
|
* The number of linearly independent paths through the source code.
|
||||||
|
* Computed as E - N + 2P,
|
||||||
|
* where
|
||||||
|
* E = the number of edges of the graph.
|
||||||
|
* N = the number of nodes of the graph.
|
||||||
|
* P = the number of connected components, which for a single function is 1.
|
||||||
|
*/
|
||||||
|
int getCyclomaticComplexity() {
|
||||||
|
exists(int n, int e |
|
||||||
|
n = count(BasicBlock b | b.getScope() = this and Reachability::likelyReachable(b)) and
|
||||||
|
e =
|
||||||
|
count(BasicBlock b1, BasicBlock b2 |
|
||||||
|
b1.getScope() = this and
|
||||||
|
Reachability::likelyReachable(b1) and
|
||||||
|
b2.getScope() = this and
|
||||||
|
Reachability::likelyReachable(b2) and
|
||||||
|
b2 = b1.getASuccessor() and
|
||||||
|
not Reachability::unlikelySuccessor(b1.getLastNode(), b2.firstNode())
|
||||||
|
)
|
||||||
|
|
|
||||||
|
result = e - n + 2
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The metrics for a class */
|
/** The metrics for a class */
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ module Builtins {
|
|||||||
"UnicodeDecodeError", "UnicodeEncodeError", "UnicodeError", "UnicodeTranslateError",
|
"UnicodeDecodeError", "UnicodeEncodeError", "UnicodeError", "UnicodeTranslateError",
|
||||||
"UnicodeWarning", "UserWarning", "ValueError", "Warning", "ZeroDivisionError",
|
"UnicodeWarning", "UserWarning", "ValueError", "Warning", "ZeroDivisionError",
|
||||||
// Added for compatibility
|
// Added for compatibility
|
||||||
"exec"
|
"exec",
|
||||||
|
// Added by the `site` module (available by default unless `-S` is used)
|
||||||
|
"copyright", "credits", "exit", "quit"
|
||||||
]
|
]
|
||||||
or
|
or
|
||||||
// Built-in constants shared between Python 2 and 3
|
// Built-in constants shared between Python 2 and 3
|
||||||
@@ -51,8 +53,8 @@ module Builtins {
|
|||||||
or
|
or
|
||||||
// Python 2 only
|
// Python 2 only
|
||||||
result in [
|
result in [
|
||||||
"basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload", "unichr",
|
"apply", "basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload",
|
||||||
"unicode", "xrange"
|
"unichr", "unicode", "xrange"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1977,3 +1977,507 @@ private module OutNodes {
|
|||||||
* `kind`.
|
* `kind`.
|
||||||
*/
|
*/
|
||||||
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
|
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides predicates for approximating type properties of user-defined classes
|
||||||
|
* based on their structure (method declarations, base classes).
|
||||||
|
*
|
||||||
|
* This module should _not_ be used in the call graph computation itself, as parts of it may depend
|
||||||
|
* on layers that themselves build upon the call graph (e.g. API graphs).
|
||||||
|
*/
|
||||||
|
module DuckTyping {
|
||||||
|
private import semmle.python.ApiGraphs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `cls` or any of its resolved superclasses declares a method with the given `name`.
|
||||||
|
*/
|
||||||
|
predicate hasMethod(Class cls, string name) {
|
||||||
|
cls.getAMethod().getName() = name
|
||||||
|
or
|
||||||
|
hasMethod(getADirectSuperclass(cls), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `cls` has a base class that cannot be resolved to a user-defined class
|
||||||
|
* and is not just `object`, meaning it may inherit methods from an unknown class.
|
||||||
|
*/
|
||||||
|
predicate hasUnresolvedBase(Class cls) {
|
||||||
|
exists(Expr base | base = cls.getABase() |
|
||||||
|
not base = classTracker(_).asExpr() and
|
||||||
|
not base = API::builtin("object").getAValueReachableFromSource().asExpr()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `cls` supports the container protocol, i.e. it declares
|
||||||
|
* `__contains__`, `__iter__`, or `__getitem__`.
|
||||||
|
*/
|
||||||
|
predicate isContainer(Class cls) {
|
||||||
|
hasMethod(cls, "__contains__") or
|
||||||
|
hasMethod(cls, "__iter__") or
|
||||||
|
hasMethod(cls, "__getitem__")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `cls` supports the iterable protocol, i.e. it declares
|
||||||
|
* `__iter__` or `__getitem__`.
|
||||||
|
*/
|
||||||
|
predicate isIterable(Class cls) {
|
||||||
|
hasMethod(cls, "__iter__") or
|
||||||
|
hasMethod(cls, "__getitem__")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `cls` supports the iterator protocol, i.e. it declares
|
||||||
|
* both `__iter__` and `__next__`.
|
||||||
|
*/
|
||||||
|
predicate isIterator(Class cls) {
|
||||||
|
hasMethod(cls, "__iter__") and
|
||||||
|
hasMethod(cls, "__next__")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `cls` supports the context manager protocol, i.e. it declares
|
||||||
|
* both `__enter__` and `__exit__`.
|
||||||
|
*/
|
||||||
|
predicate isContextManager(Class cls) {
|
||||||
|
hasMethod(cls, "__enter__") and
|
||||||
|
hasMethod(cls, "__exit__")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `cls` supports the descriptor protocol, i.e. it declares
|
||||||
|
* `__get__`, `__set__`, or `__delete__`.
|
||||||
|
*/
|
||||||
|
predicate isDescriptor(Class cls) {
|
||||||
|
hasMethod(cls, "__get__") or
|
||||||
|
hasMethod(cls, "__set__") or
|
||||||
|
hasMethod(cls, "__delete__")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `cls` directly assigns to an attribute named `name` in its class body.
|
||||||
|
* This covers attribute assignments like `x = value`, but not method definitions.
|
||||||
|
*/
|
||||||
|
predicate declaresAttribute(Class cls, string name) { exists(getAnAttributeValue(cls, name)) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value expression assigned to attribute `name` directly in the class body of `cls`.
|
||||||
|
*/
|
||||||
|
Expr getAnAttributeValue(Class cls, string name) {
|
||||||
|
exists(Assign a |
|
||||||
|
a.getScope() = cls and
|
||||||
|
a.getATarget().(Name).getId() = name and
|
||||||
|
result = a.getValue()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `cls` is callable, i.e. it declares `__call__`.
|
||||||
|
*/
|
||||||
|
predicate isCallable(Class cls) { hasMethod(cls, "__call__") }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `cls` supports the mapping protocol, i.e. it declares
|
||||||
|
* `__getitem__` and `keys`, or `__getitem__` and `__iter__`.
|
||||||
|
*/
|
||||||
|
predicate isMapping(Class cls) {
|
||||||
|
hasMethod(cls, "__getitem__") and
|
||||||
|
(hasMethod(cls, "keys") or hasMethod(cls, "__iter__"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `cls` is a new-style class. In Python 3, all classes are new-style.
|
||||||
|
* In Python 2, a class is new-style if it (transitively) inherits from `object`,
|
||||||
|
* or has a declared `__metaclass__`, or has an unresolved base class.
|
||||||
|
*/
|
||||||
|
predicate isNewStyle(Class cls) {
|
||||||
|
major_version() = 3
|
||||||
|
or
|
||||||
|
major_version() = 2 and
|
||||||
|
(
|
||||||
|
cls.getABase() = API::builtin("object").getAValueReachableFromSource().asExpr()
|
||||||
|
or
|
||||||
|
isNewStyle(getADirectSuperclass(cls))
|
||||||
|
or
|
||||||
|
hasUnresolvedBase(cls)
|
||||||
|
or
|
||||||
|
exists(cls.getMetaClass())
|
||||||
|
or
|
||||||
|
// Module-level __metaclass__ = type makes all classes in the module new-style
|
||||||
|
exists(Assign a |
|
||||||
|
a.getScope() = cls.getEnclosingModule() and
|
||||||
|
a.getATarget().(Name).getId() = "__metaclass__"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the `__init__` function that will be invoked when `cls` is constructed,
|
||||||
|
* resolved according to the MRO.
|
||||||
|
*/
|
||||||
|
Function getInit(Class cls) { result = invokedFunctionFromClassConstruction(cls, "__init__") }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `f` overrides a method in a superclass with the same name.
|
||||||
|
*/
|
||||||
|
predicate overridesMethod(Function f) {
|
||||||
|
exists(Class cls | f.getScope() = cls | hasMethod(getADirectSuperclass(cls), f.getName()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `f` is a property accessor (decorated with `@property`, `@name.setter`,
|
||||||
|
* or `@name.deleter`).
|
||||||
|
*/
|
||||||
|
predicate isPropertyAccessor(Function f) {
|
||||||
|
exists(Attribute a | a = f.getADecorator() | a.getName() = "setter" or a.getName() = "deleter")
|
||||||
|
or
|
||||||
|
f.getADecorator().(Name).getId() = "property"
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the name of the builtin class of the immutable literal `lit`. */
|
||||||
|
string getClassName(ImmutableLiteral lit) {
|
||||||
|
lit instanceof IntegerLiteral and result = "int"
|
||||||
|
or
|
||||||
|
lit instanceof FloatLiteral and result = "float"
|
||||||
|
or
|
||||||
|
lit instanceof ImaginaryLiteral and result = "complex"
|
||||||
|
or
|
||||||
|
lit instanceof NegativeIntegerLiteral and result = "int"
|
||||||
|
or
|
||||||
|
lit instanceof StringLiteral and result = "str"
|
||||||
|
or
|
||||||
|
lit instanceof BooleanLiteral and result = "bool"
|
||||||
|
or
|
||||||
|
lit instanceof None and result = "NoneType"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a class hierarchy for exception types, covering both builtin
|
||||||
|
* exceptions (from typeshed models) and user-defined exception classes.
|
||||||
|
*/
|
||||||
|
module ExceptionTypes {
|
||||||
|
private import semmle.python.ApiGraphs
|
||||||
|
private import semmle.python.frameworks.data.internal.ApiGraphModels
|
||||||
|
|
||||||
|
/** Holds if `name` is a builtin exception class name. */
|
||||||
|
predicate builtinException(string name) {
|
||||||
|
typeModel("builtins.BaseException~Subclass", "builtins." + name, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if builtin exception `sub` is a direct subclass of builtin exception `base`. */
|
||||||
|
private predicate builtinExceptionSubclass(string base, string sub) {
|
||||||
|
typeModel("builtins." + base + "~Subclass", "builtins." + sub, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An exception type, either a builtin exception or a user-defined exception class. */
|
||||||
|
newtype TExceptType =
|
||||||
|
/** A user-defined exception class. */
|
||||||
|
TUserExceptType(Class c) or
|
||||||
|
/** A builtin exception class, identified by name. */
|
||||||
|
TBuiltinExceptType(string name) { builtinException(name) }
|
||||||
|
|
||||||
|
/** An exception type, either a builtin exception or a user-defined exception class. */
|
||||||
|
class ExceptType extends TExceptType {
|
||||||
|
/** Gets the name of this exception type. */
|
||||||
|
string getName() { none() }
|
||||||
|
|
||||||
|
/** Gets a data-flow node that refers to this exception type. */
|
||||||
|
DataFlow::Node getAUse() { none() }
|
||||||
|
|
||||||
|
/** Gets a direct superclass of this exception type. */
|
||||||
|
ExceptType getADirectSuperclass() { none() }
|
||||||
|
|
||||||
|
/** Gets a string representation of this exception type. */
|
||||||
|
string toString() { result = this.getName() }
|
||||||
|
|
||||||
|
/** Gets a data-flow node that refers to an instance of this exception type. */
|
||||||
|
DataFlow::Node getAnInstance() { none() }
|
||||||
|
|
||||||
|
/** Holds if this is a legal exception type (a subclass of `BaseException`). */
|
||||||
|
predicate isLegalExceptionType() { this.getADirectSuperclass*() instanceof BaseException }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this exception type is raised by `r`, either as a class reference
|
||||||
|
* (e.g. `raise ValueError`) or as an instantiation (e.g. `raise ValueError("msg")`).
|
||||||
|
*/
|
||||||
|
predicate isRaisedBy(Raise r) {
|
||||||
|
exists(Expr raised | raised = r.getRaised() |
|
||||||
|
this.getAUse().asExpr() in [raised, raised.(Call).getFunc()]
|
||||||
|
or
|
||||||
|
this.getAnInstance().asExpr() = raised
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if this exception type may be raised at control flow node `r`. */
|
||||||
|
predicate isRaisedAt(ControlFlowNode r) {
|
||||||
|
this.isRaisedBy(r.getNode())
|
||||||
|
or
|
||||||
|
exists(Function callee |
|
||||||
|
resolveCall(r, callee, _) and
|
||||||
|
this.isRaisedIn(callee)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this exception type may be raised in function `f`, either
|
||||||
|
* directly via `raise` statements or transitively through calls to other functions.
|
||||||
|
*/
|
||||||
|
predicate isRaisedIn(Function f) { this.isRaisedAt(any(ControlFlowNode r | r.getScope() = f)) }
|
||||||
|
|
||||||
|
/** Holds if this exception type is handled by the `except` clause at `handler`. */
|
||||||
|
predicate isHandledAt(ExceptFlowNode handler) {
|
||||||
|
exists(ExceptStmt ex, Expr typeExpr | ex = handler.getNode() |
|
||||||
|
(
|
||||||
|
typeExpr = ex.getType()
|
||||||
|
or
|
||||||
|
typeExpr = ex.getType().(Tuple).getAnElt()
|
||||||
|
) and
|
||||||
|
this.getAUse().asExpr() = typeExpr
|
||||||
|
)
|
||||||
|
or
|
||||||
|
// A bare `except:` handles everything
|
||||||
|
not exists(handler.getNode().(ExceptStmt).getType()) and
|
||||||
|
this instanceof BaseException
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this element is at the specified location.
|
||||||
|
* The location spans column `startColumn` of line `startLine` to
|
||||||
|
* column `endColumn` of line `endLine` in file `filepath`.
|
||||||
|
* For more information, see
|
||||||
|
* [Providing locations in CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||||
|
*/
|
||||||
|
predicate hasLocationInfo(
|
||||||
|
string filePath, int startLine, int startColumn, int endLine, int endColumn
|
||||||
|
) {
|
||||||
|
none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A user-defined exception class. */
|
||||||
|
class UserExceptType extends ExceptType, TUserExceptType {
|
||||||
|
Class cls;
|
||||||
|
|
||||||
|
UserExceptType() { this = TUserExceptType(cls) }
|
||||||
|
|
||||||
|
/** Gets the underlying class. */
|
||||||
|
Class asClass() { result = cls }
|
||||||
|
|
||||||
|
override string getName() { result = cls.getName() }
|
||||||
|
|
||||||
|
override DataFlow::Node getAUse() { result = classTracker(cls) }
|
||||||
|
|
||||||
|
override DataFlow::Node getAnInstance() { result = classInstanceTracker(cls) }
|
||||||
|
|
||||||
|
override ExceptType getADirectSuperclass() {
|
||||||
|
result.(UserExceptType).asClass() = getADirectSuperclass(cls)
|
||||||
|
or
|
||||||
|
result.(BuiltinExceptType).getAUse().asExpr() = cls.getABase()
|
||||||
|
}
|
||||||
|
|
||||||
|
override predicate hasLocationInfo(
|
||||||
|
string filePath, int startLine, int startColumn, int endLine, int endColumn
|
||||||
|
) {
|
||||||
|
cls.getLocation().hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A builtin exception class, identified by name. */
|
||||||
|
class BuiltinExceptType extends ExceptType, TBuiltinExceptType {
|
||||||
|
string name;
|
||||||
|
|
||||||
|
BuiltinExceptType() { this = TBuiltinExceptType(name) }
|
||||||
|
|
||||||
|
/** Gets the builtin name. */
|
||||||
|
string asBuiltinName() { result = name }
|
||||||
|
|
||||||
|
override string getName() { result = name }
|
||||||
|
|
||||||
|
override DataFlow::Node getAUse() { result = API::builtin(name).getAValueReachableFromSource() }
|
||||||
|
|
||||||
|
override DataFlow::Node getAnInstance() {
|
||||||
|
result = API::builtin(name).getAnInstance().getAValueReachableFromSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
override ExceptType getADirectSuperclass() {
|
||||||
|
builtinExceptionSubclass(result.(BuiltinExceptType).asBuiltinName(), name) and
|
||||||
|
result != this
|
||||||
|
}
|
||||||
|
|
||||||
|
override predicate hasLocationInfo(
|
||||||
|
string filePath, int startLine, int startColumn, int endLine, int endColumn
|
||||||
|
) {
|
||||||
|
filePath = "" and
|
||||||
|
startLine = 0 and
|
||||||
|
startColumn = 0 and
|
||||||
|
endLine = 0 and
|
||||||
|
endColumn = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The builtin `BaseException` type. */
|
||||||
|
class BaseException extends BuiltinExceptType {
|
||||||
|
BaseException() { name = "BaseException" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if the exception edge from `r` to `handler` is unlikely because
|
||||||
|
* none of the exception types that `r` may raise are handled by `handler`.
|
||||||
|
*/
|
||||||
|
predicate unlikelyExceptionEdge(ControlFlowNode r, ExceptFlowNode handler) {
|
||||||
|
handler = r.getAnExceptionalSuccessor() and
|
||||||
|
// We can determine at least one raised type
|
||||||
|
exists(ExceptType t | t.isRaisedAt(r)) and
|
||||||
|
// But none of them are handled by this handler
|
||||||
|
not exists(ExceptType raised, ExceptType handled |
|
||||||
|
raised.isRaisedAt(r) and
|
||||||
|
handled.isHandledAt(handler) and
|
||||||
|
raised.getADirectSuperclass*() = handled
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides predicates for reasoning about the reachability of control flow nodes
|
||||||
|
* and basic blocks.
|
||||||
|
*/
|
||||||
|
module Reachability {
|
||||||
|
private import semmle.python.ApiGraphs
|
||||||
|
import ExceptionTypes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `call` is a call to a function that is known to never return normally
|
||||||
|
* (e.g. `sys.exit()`, `os._exit()`, `os.abort()`).
|
||||||
|
*/
|
||||||
|
predicate isCallToNeverReturningFunction(CallNode call) {
|
||||||
|
// Known never-returning builtins/stdlib functions via API graphs
|
||||||
|
call = API::builtin("exit").getACall().asCfgNode()
|
||||||
|
or
|
||||||
|
call = API::builtin("quit").getACall().asCfgNode()
|
||||||
|
or
|
||||||
|
call = API::moduleImport("sys").getMember("exit").getACall().asCfgNode()
|
||||||
|
or
|
||||||
|
call = API::moduleImport("os").getMember("_exit").getACall().asCfgNode()
|
||||||
|
or
|
||||||
|
call = API::moduleImport("os").getMember("abort").getACall().asCfgNode()
|
||||||
|
or
|
||||||
|
// User-defined functions that only contain raise statements (no normal returns)
|
||||||
|
exists(Function target |
|
||||||
|
resolveCall(call, target, _) and
|
||||||
|
neverReturns(target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if function `f` never returns normally, because every normal exit
|
||||||
|
* is dominated by a call to a never-returning function or an unconditional raise.
|
||||||
|
*/
|
||||||
|
predicate neverReturns(Function f) {
|
||||||
|
exists(f.getANormalExit()) and
|
||||||
|
forall(BasicBlock exit | exit = f.getANormalExit().getBasicBlock() |
|
||||||
|
exists(BasicBlock raising |
|
||||||
|
raising.dominates(exit) and
|
||||||
|
(
|
||||||
|
isCallToNeverReturningFunction(raising.getLastNode())
|
||||||
|
or
|
||||||
|
raising.getLastNode().getNode() instanceof Raise
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `node` is unlikely to raise an exception. This includes entry nodes
|
||||||
|
* and simple name lookups.
|
||||||
|
*/
|
||||||
|
private predicate unlikelyToRaise(ControlFlowNode node) {
|
||||||
|
exists(node.getAnExceptionalSuccessor()) and
|
||||||
|
(
|
||||||
|
node.getNode() instanceof Name
|
||||||
|
or
|
||||||
|
exists(Scope s | s.getEntryNode() = node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if it is highly unlikely for control to flow from `node` to `succ`.
|
||||||
|
*/
|
||||||
|
predicate unlikelySuccessor(ControlFlowNode node, ControlFlowNode succ) {
|
||||||
|
// Exceptional edge where the raised type doesn't match the handler
|
||||||
|
unlikelyExceptionEdge(node, succ)
|
||||||
|
or
|
||||||
|
// Normal successor of a never-returning call
|
||||||
|
isCallToNeverReturningFunction(node) and
|
||||||
|
succ = node.getASuccessor() and
|
||||||
|
not succ = node.getAnExceptionalSuccessor() and
|
||||||
|
not succ.getNode() instanceof Yield
|
||||||
|
or
|
||||||
|
// Exception edge from a node that is unlikely to raise
|
||||||
|
unlikelyToRaise(node) and
|
||||||
|
succ = node.getAnExceptionalSuccessor()
|
||||||
|
or
|
||||||
|
// True branch of `if False:` or `if TYPE_CHECKING:`
|
||||||
|
isAlwaysFalseGuard(node) and
|
||||||
|
succ = node.getATrueSuccessor()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `node` is a condition that is always `False` at runtime.
|
||||||
|
* This covers `if False:` and `if typing.TYPE_CHECKING:`.
|
||||||
|
*/
|
||||||
|
private predicate isAlwaysFalseGuard(ControlFlowNode node) {
|
||||||
|
node.getNode() instanceof False
|
||||||
|
or
|
||||||
|
node =
|
||||||
|
API::moduleImport("typing")
|
||||||
|
.getMember("TYPE_CHECKING")
|
||||||
|
.getAValueReachableFromSource()
|
||||||
|
.asCfgNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
private predicate startBbLikelyReachable(BasicBlock b) {
|
||||||
|
exists(Scope s | s.getEntryNode() = b.getNode(_))
|
||||||
|
or
|
||||||
|
exists(BasicBlock pred |
|
||||||
|
pred = b.getAPredecessor() and
|
||||||
|
endBbLikelyReachable(pred) and
|
||||||
|
not unlikelySuccessor(pred.getLastNode(), b)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private predicate endBbLikelyReachable(BasicBlock b) {
|
||||||
|
startBbLikelyReachable(b) and
|
||||||
|
not exists(ControlFlowNode p, ControlFlowNode s |
|
||||||
|
unlikelySuccessor(p, s) and
|
||||||
|
p = b.getNode(_) and
|
||||||
|
s = b.getNode(_) and
|
||||||
|
not p = b.getLastNode()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if basic block `b` is likely to be reachable from the entry of its
|
||||||
|
* enclosing scope.
|
||||||
|
*/
|
||||||
|
predicate likelyReachable(BasicBlock b) { startBbLikelyReachable(b) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if it is unlikely that `node` can be reached during execution.
|
||||||
|
*/
|
||||||
|
predicate unlikelyReachable(ControlFlowNode node) {
|
||||||
|
not startBbLikelyReachable(node.getBasicBlock())
|
||||||
|
or
|
||||||
|
exists(BasicBlock b |
|
||||||
|
startBbLikelyReachable(b) and
|
||||||
|
not endBbLikelyReachable(b) and
|
||||||
|
exists(ControlFlowNode p, int i, int j |
|
||||||
|
unlikelySuccessor(p, _) and
|
||||||
|
p = b.getNode(i) and
|
||||||
|
node = b.getNode(j) and
|
||||||
|
i < j
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -377,4 +377,30 @@ module ImportResolution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Module getModule(DataFlow::CfgNode node) { node = getModuleReference(result) }
|
Module getModule(DataFlow::CfgNode node) { node = getModuleReference(result) }
|
||||||
|
|
||||||
|
/** Holds if module `importer` directly imports module `imported`. */
|
||||||
|
predicate imports(Module importer, Module imported) {
|
||||||
|
getImmediateModuleReference(imported).getScope() = importer
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if the import statement `i` causes module `imported` to be imported.
|
||||||
|
* For `from pkg import submodule`, both `pkg` and `pkg.submodule` are considered imported.
|
||||||
|
*/
|
||||||
|
predicate importedBy(ImportingStmt i, Module imported) {
|
||||||
|
exists(Alias a | a = i.(Import).getAName() |
|
||||||
|
getImmediateModuleReference(imported).asExpr() = a.getAsname()
|
||||||
|
)
|
||||||
|
or
|
||||||
|
exists(ImportMember im | im = i.(Import).getAName().getValue() |
|
||||||
|
getImmediateModuleReference(imported).asExpr() = im.getModule()
|
||||||
|
)
|
||||||
|
or
|
||||||
|
getImmediateModuleReference(imported).asExpr() = i.(ImportStar).getModule().(ImportExpr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets a user-friendly name for module `m`, using the package name for `__init__` modules. */
|
||||||
|
string moduleName(Module m) {
|
||||||
|
if m.isPackageInit() then result = m.getPackageName() else result = m.getName()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,19 +12,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
ClassObject left_base(ClassObject type, ClassObject base) {
|
/**
|
||||||
exists(int i | i > 0 and type.getBaseType(i) = base and result = type.getBaseType(i - 1))
|
* Gets the `i`th base class of `cls`, if it can be resolved to a user-defined class.
|
||||||
|
*/
|
||||||
|
Class getBaseType(Class cls, int i) { cls.getBase(i) = classTracker(result).asExpr() }
|
||||||
|
|
||||||
|
Class left_base(Class type, Class base) {
|
||||||
|
exists(int i | i > 0 and getBaseType(type, i) = base and result = getBaseType(type, i - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate invalid_mro(ClassObject t, ClassObject left, ClassObject right) {
|
predicate invalid_mro(Class t, Class left, Class right) {
|
||||||
t.isNewStyle() and
|
DuckTyping::isNewStyle(t) and
|
||||||
left = left_base(t, right) and
|
left = left_base(t, right) and
|
||||||
left = right.getAnImproperSuperType()
|
left = getADirectSuperclass*(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
from ClassObject t, ClassObject left, ClassObject right
|
from Class t, Class left, Class right
|
||||||
where invalid_mro(t, left, right)
|
where invalid_mro(t, left, right)
|
||||||
select t,
|
select t,
|
||||||
"Construction of class " + t.getName() +
|
"Construction of class " + t.getName() +
|
||||||
|
|||||||
@@ -11,10 +11,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
from PropertyObject prop, ClassObject cls
|
from Function prop, Class cls
|
||||||
where cls.declaredAttribute(_) = prop and not cls.failedInference() and not cls.isNewStyle()
|
where
|
||||||
|
prop.getScope() = cls and
|
||||||
|
prop.getADecorator().(Name).getId() = "property" and
|
||||||
|
not DuckTyping::isNewStyle(cls)
|
||||||
select prop,
|
select prop,
|
||||||
"Property " + prop.getName() + " will not work properly, as class " + cls.getName() +
|
"Property " + prop.getName() + " will not work properly, as class " + cls.getName() +
|
||||||
" is an old-style class."
|
" is an old-style class."
|
||||||
|
|||||||
@@ -14,10 +14,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
from ClassValue c
|
from Class c
|
||||||
where not c.isBuiltin() and not c.isContextManager() and exists(c.declaredAttribute("__del__"))
|
where
|
||||||
|
not DuckTyping::isContextManager(c) and
|
||||||
|
DuckTyping::hasMethod(c, "__del__")
|
||||||
select c,
|
select c,
|
||||||
"Class " + c.getName() +
|
"Class " + c.getName() +
|
||||||
" implements __del__ (presumably to release some resource). Consider making it a context manager."
|
" implements __del__ (presumably to release some resource). Consider making it a context manager."
|
||||||
|
|||||||
@@ -12,9 +12,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
from ClassObject c
|
from Class c
|
||||||
where not c.isNewStyle() and c.declaresAttribute("__slots__") and not c.failedInference()
|
where
|
||||||
|
not DuckTyping::isNewStyle(c) and
|
||||||
|
DuckTyping::declaresAttribute(c, "__slots__")
|
||||||
select c,
|
select c,
|
||||||
"Using '__slots__' in an old style class just creates a class attribute called '__slots__'."
|
"Using '__slots__' in an old style class just creates a class attribute called '__slots__'."
|
||||||
|
|||||||
@@ -11,14 +11,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
predicate uses_of_super_in_old_style_class(Call s) {
|
predicate uses_of_super_in_old_style_class(Call s) {
|
||||||
exists(Function f, ClassObject c |
|
exists(Function f, Class c |
|
||||||
s.getScope() = f and
|
s.getScope() = f and
|
||||||
f.getScope() = c.getPyClass() and
|
f.getScope() = c and
|
||||||
not c.failedInference() and
|
not DuckTyping::isNewStyle(c) and
|
||||||
not c.isNewStyle() and
|
|
||||||
s.getFunc().(Name).getId() = "super"
|
s.getFunc().(Name).getId() = "super"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
predicate fewer_than_two_public_methods(Class cls, int methods) {
|
predicate fewer_than_two_public_methods(Class cls, int methods) {
|
||||||
(methods = 0 or methods = 1) and
|
(methods = 0 or methods = 1) and
|
||||||
@@ -25,13 +25,8 @@ predicate does_not_define_special_method(Class cls) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
predicate no_inheritance(Class c) {
|
predicate no_inheritance(Class c) {
|
||||||
not exists(ClassValue cls, ClassValue other |
|
not exists(getADirectSubclass(c)) and
|
||||||
cls.getScope() = c and
|
not exists(getADirectSuperclass(c)) and
|
||||||
other != ClassValue::object()
|
|
||||||
|
|
|
||||||
other.getABaseType() = cls or
|
|
||||||
cls.getABaseType() = other
|
|
||||||
) and
|
|
||||||
not exists(Expr base | base = c.getABase() |
|
not exists(Expr base | base = c.getABase() |
|
||||||
not base instanceof Name or base.(Name).getId() != "object"
|
not base instanceof Name or base.(Name).getId() != "object"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,12 +15,34 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
import Expressions.CallArgs
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
private import LegacyPointsTo
|
|
||||||
|
|
||||||
from Call call, ClassValue cls, string name, FunctionValue init
|
/**
|
||||||
|
* Holds if `name` is a legal argument name for calling `init`.
|
||||||
|
*/
|
||||||
|
bindingset[name]
|
||||||
|
predicate isLegalArgumentName(Function init, string name) {
|
||||||
|
exists(init.getArgByName(name))
|
||||||
|
or
|
||||||
|
init.hasKwArg()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `call` constructs class `cls` and passes a keyword argument `name`
|
||||||
|
* that does not correspond to any parameter of `cls.__init__`.
|
||||||
|
*/
|
||||||
|
predicate illegally_named_parameter(Call call, Class cls, string name) {
|
||||||
|
exists(Function init |
|
||||||
|
resolveClassCall(call.getAFlowNode(), cls) and
|
||||||
|
init = DuckTyping::getInit(cls) and
|
||||||
|
name = call.getANamedArgumentName() and
|
||||||
|
not isLegalArgumentName(init, name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
from Call call, Class cls, string name, Function init
|
||||||
where
|
where
|
||||||
illegally_named_parameter(call, cls, name) and
|
illegally_named_parameter(call, cls, name) and
|
||||||
init = get_function_or_initializer(cls)
|
init = DuckTyping::getInit(cls)
|
||||||
select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init,
|
select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init,
|
||||||
init.getQualifiedName()
|
init.getQualifiedName()
|
||||||
|
|||||||
@@ -14,10 +14,60 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
import Expressions.CallArgs
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
private import LegacyPointsTo
|
|
||||||
|
|
||||||
from Call call, ClassValue cls, string too, string should, int limit, FunctionValue init
|
/**
|
||||||
|
* Gets the number of positional arguments in `call`, including elements of any
|
||||||
|
* literal list passed as `*args`, plus keyword arguments that don't match
|
||||||
|
* keyword-only parameters (when the function doesn't accept `**kwargs`).
|
||||||
|
*/
|
||||||
|
int positional_arg_count(Call call, Class cls, Function init) {
|
||||||
|
resolveClassCall(call.getAFlowNode(), cls) and
|
||||||
|
init = DuckTyping::getInit(cls) and
|
||||||
|
exists(int positional_keywords |
|
||||||
|
if init.hasKwArg()
|
||||||
|
then positional_keywords = 0
|
||||||
|
else
|
||||||
|
positional_keywords =
|
||||||
|
count(Keyword kw |
|
||||||
|
kw = call.getAKeyword() and
|
||||||
|
not init.getAKeywordOnlyArg().getId() = kw.getArg()
|
||||||
|
)
|
||||||
|
|
|
||||||
|
result =
|
||||||
|
count(call.getAnArg()) + count(call.getStarargs().(List).getAnElt()) + positional_keywords
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `call` constructs `cls` with too many arguments, where `limit` is the maximum.
|
||||||
|
*/
|
||||||
|
predicate too_many_args(Call call, Class cls, int limit) {
|
||||||
|
exists(Function init |
|
||||||
|
not init.hasVarArg() and
|
||||||
|
// Subtract 1 from max to account for `self` parameter
|
||||||
|
limit = init.getMaxPositionalArguments() - 1 and
|
||||||
|
limit >= 0 and
|
||||||
|
positional_arg_count(call, cls, init) > limit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `call` constructs `cls` with too few arguments, where `limit` is the minimum.
|
||||||
|
*/
|
||||||
|
predicate too_few_args(Call call, Class cls, int limit) {
|
||||||
|
resolveClassCall(call.getAFlowNode(), cls) and
|
||||||
|
exists(Function init |
|
||||||
|
init = DuckTyping::getInit(cls) and
|
||||||
|
not exists(call.getStarargs()) and
|
||||||
|
not exists(call.getKwargs()) and
|
||||||
|
// Subtract 1 from min to account for `self` parameter
|
||||||
|
limit = init.getMinPositionalArguments() - 1 and
|
||||||
|
count(call.getAnArg()) + count(call.getAKeyword()) < limit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
from Call call, Class cls, string too, string should, int limit, Function init
|
||||||
where
|
where
|
||||||
(
|
(
|
||||||
too_many_args(call, cls, limit) and
|
too_many_args(call, cls, limit) and
|
||||||
@@ -28,6 +78,6 @@ where
|
|||||||
too = "too few arguments" and
|
too = "too few arguments" and
|
||||||
should = "no fewer than "
|
should = "no fewer than "
|
||||||
) and
|
) and
|
||||||
init = get_function_or_initializer(cls)
|
init = DuckTyping::getInit(cls)
|
||||||
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init,
|
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init,
|
||||||
init.getQualifiedName()
|
init.getQualifiedName()
|
||||||
|
|||||||
@@ -12,20 +12,38 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
private import ExceptionTypes
|
||||||
|
|
||||||
from ExceptFlowNodeWithPointsTo ex, Value t, ClassValue c, ControlFlowNode origin, string what
|
/**
|
||||||
|
* Gets an expression used as a handler type in the `except` clause at `ex`,
|
||||||
|
* either directly or as an element of a tuple.
|
||||||
|
*/
|
||||||
|
Expr handlerExpr(ExceptStmt ex) {
|
||||||
|
result = ex.getType() or
|
||||||
|
result = ex.getType().(Tuple).getAnElt()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an exception type used in the `except` clause at `ex`,
|
||||||
|
* where that type is not a legal exception type.
|
||||||
|
*/
|
||||||
|
ExceptType illegalHandlerType(ExceptStmt ex) {
|
||||||
|
result.getAUse().asExpr() = handlerExpr(ex) and
|
||||||
|
not result.isLegalExceptionType()
|
||||||
|
}
|
||||||
|
|
||||||
|
from ExceptStmt ex, string msg
|
||||||
where
|
where
|
||||||
ex.handledException(t, c, origin) and
|
exists(ExceptType t | t = illegalHandlerType(ex) |
|
||||||
(
|
msg =
|
||||||
exists(ClassValue x | x = t |
|
"Non-exception class '" + t.getName() +
|
||||||
not x.isLegalExceptionType() and
|
"' in exception handler which will never match raised exception."
|
||||||
not x.failedInference(_) and
|
|
||||||
what = "class '" + x.getName() + "'"
|
|
||||||
)
|
|
||||||
or
|
|
||||||
not t instanceof ClassValue and
|
|
||||||
what = "instance of '" + c.getName() + "'"
|
|
||||||
)
|
)
|
||||||
select ex.getNode(),
|
or
|
||||||
"Non-exception $@ in exception handler which will never match raised exception.", origin, what
|
exists(ImmutableLiteral lit | lit = handlerExpr(ex) and not lit instanceof None |
|
||||||
|
msg =
|
||||||
|
"Non-exception class '" + DuckTyping::getClassName(lit) +
|
||||||
|
"' in exception handler which will never match raised exception."
|
||||||
|
)
|
||||||
|
select ex, msg
|
||||||
|
|||||||
@@ -12,15 +12,48 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
import Raising
|
import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
import Exceptions.NotImplemented
|
import semmle.python.ApiGraphs
|
||||||
private import LegacyPointsTo
|
private import ExceptionTypes
|
||||||
|
|
||||||
from Raise r, ClassValue t
|
/**
|
||||||
|
* Holds if `r` raises an instance of a builtin non-exception class named `name`.
|
||||||
|
*/
|
||||||
|
private predicate raisesNonExceptionBuiltin(Raise r, string name) {
|
||||||
|
exists(Expr raised | raised = r.getRaised() |
|
||||||
|
API::builtin(name).getAValueReachableFromSource().asExpr() = raised
|
||||||
|
or
|
||||||
|
API::builtin(name).getAValueReachableFromSource().asExpr() = raised.(Call).getFunc() and
|
||||||
|
// Exclude `type` since `type(x)` returns the class of `x`, not a `type` instance
|
||||||
|
not name = "type"
|
||||||
|
) and
|
||||||
|
not builtinException(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
from Raise r, string msg
|
||||||
where
|
where
|
||||||
type_or_typeof(r, t, _) and
|
not raisesNonExceptionBuiltin(r, "NotImplemented") and
|
||||||
not t.isLegalExceptionType() and
|
(
|
||||||
not t.failedInference(_) and
|
exists(ExceptType t |
|
||||||
not use_of_not_implemented_in_raise(r, _)
|
t.isRaisedBy(r) and
|
||||||
select r,
|
not t.isLegalExceptionType() and
|
||||||
"Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead."
|
not t.getName() = "None" and
|
||||||
|
msg =
|
||||||
|
"Illegal class '" + t.getName() +
|
||||||
|
"' raised; will result in a TypeError being raised instead."
|
||||||
|
)
|
||||||
|
or
|
||||||
|
exists(ImmutableLiteral lit | lit = r.getRaised() |
|
||||||
|
msg =
|
||||||
|
"Illegal class '" + DuckTyping::getClassName(lit) +
|
||||||
|
"' raised; will result in a TypeError being raised instead."
|
||||||
|
)
|
||||||
|
or
|
||||||
|
exists(string name |
|
||||||
|
raisesNonExceptionBuiltin(r, name) and
|
||||||
|
not r.getRaised() instanceof ImmutableLiteral and
|
||||||
|
not name = "None" and
|
||||||
|
msg = "Illegal class '" + name + "' raised; will result in a TypeError being raised instead."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
select r, msg
|
||||||
|
|||||||
@@ -15,74 +15,7 @@
|
|||||||
|
|
||||||
import python
|
import python
|
||||||
import semmle.python.dataflow.new.internal.DataFlowDispatch
|
import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
import semmle.python.ApiGraphs
|
private import ExceptionTypes
|
||||||
import semmle.python.frameworks.data.internal.ApiGraphModels
|
|
||||||
|
|
||||||
predicate builtinException(string name) {
|
|
||||||
typeModel("builtins.BaseException~Subclass", "builtins." + name, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
predicate builtinExceptionSubclass(string base, string sub) {
|
|
||||||
typeModel("builtins." + base + "~Subclass", "builtins." + sub, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
newtype TExceptType =
|
|
||||||
TClass(Class c) or
|
|
||||||
TBuiltin(string name) { builtinException(name) }
|
|
||||||
|
|
||||||
class ExceptType extends TExceptType {
|
|
||||||
Class asClass() { this = TClass(result) }
|
|
||||||
|
|
||||||
string asBuiltinName() { this = TBuiltin(result) }
|
|
||||||
|
|
||||||
predicate isBuiltin() { this = TBuiltin(_) }
|
|
||||||
|
|
||||||
string getName() {
|
|
||||||
result = this.asClass().getName()
|
|
||||||
or
|
|
||||||
result = this.asBuiltinName()
|
|
||||||
}
|
|
||||||
|
|
||||||
string toString() { result = this.getName() }
|
|
||||||
|
|
||||||
DataFlow::Node getAUse() {
|
|
||||||
result = classTracker(this.asClass())
|
|
||||||
or
|
|
||||||
API::builtin(this.asBuiltinName()).asSource().flowsTo(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
ExceptType getADirectSuperclass() {
|
|
||||||
result.asClass() = getADirectSuperclass(this.asClass())
|
|
||||||
or
|
|
||||||
result.isBuiltin() and
|
|
||||||
result.getAUse().asExpr() = this.asClass().getABase()
|
|
||||||
or
|
|
||||||
builtinExceptionSubclass(result.asBuiltinName(), this.asBuiltinName()) and
|
|
||||||
this != result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds if this element is at the specified location.
|
|
||||||
* The location spans column `startColumn` of line `startLine` to
|
|
||||||
* column `endColumn` of line `endLine` in file `filepath`.
|
|
||||||
* For more information, see
|
|
||||||
* [Providing locations in CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
|
||||||
*/
|
|
||||||
predicate hasLocationInfo(
|
|
||||||
string filePath, int startLine, int startColumn, int endLine, int endColumn
|
|
||||||
) {
|
|
||||||
this.asClass()
|
|
||||||
.getLocation()
|
|
||||||
.hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn)
|
|
||||||
or
|
|
||||||
this.isBuiltin() and
|
|
||||||
filePath = "" and
|
|
||||||
startLine = 0 and
|
|
||||||
startColumn = 0 and
|
|
||||||
endLine = 0 and
|
|
||||||
endColumn = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
predicate incorrectExceptOrder(ExceptStmt ex1, ExceptType cls1, ExceptStmt ex2, ExceptType cls2) {
|
predicate incorrectExceptOrder(ExceptStmt ex1, ExceptType cls1, ExceptStmt ex2, ExceptType cls2) {
|
||||||
exists(int i, int j, Try t |
|
exists(int i, int j, Try t |
|
||||||
|
|||||||
@@ -12,25 +12,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
import semmle.python.dataflow.new.DataFlow
|
||||||
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
predicate rhs_in_expr(ControlFlowNode rhs, Compare cmp) {
|
predicate rhs_in_expr(Expr rhs, Compare cmp) {
|
||||||
exists(Cmpop op, int i | cmp.getOp(i) = op and cmp.getComparator(i) = rhs.getNode() |
|
exists(Cmpop op, int i | cmp.getOp(i) = op and cmp.getComparator(i) = rhs |
|
||||||
op instanceof In or op instanceof NotIn
|
op instanceof In or op instanceof NotIn
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
from
|
from Compare cmp, DataFlow::LocalSourceNode origin, DataFlow::Node rhs, Class cls
|
||||||
ControlFlowNodeWithPointsTo non_seq, Compare cmp, Value v, ClassValue cls, ControlFlowNode origin
|
|
||||||
where
|
where
|
||||||
rhs_in_expr(non_seq, cmp) and
|
origin = classInstanceTracker(cls) and
|
||||||
non_seq.pointsTo(_, v, origin) and
|
origin.flowsTo(rhs) and
|
||||||
v.getClass() = cls and
|
not DuckTyping::isContainer(cls) and
|
||||||
not Types::failedInference(cls, _) and
|
not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(cls)) and
|
||||||
not cls.hasAttribute("__contains__") and
|
rhs_in_expr(rhs.asExpr(), cmp)
|
||||||
not cls.hasAttribute("__iter__") and
|
|
||||||
not cls.hasAttribute("__getitem__") and
|
|
||||||
not cls = ClassValue::nonetype() and
|
|
||||||
not cls = Value::named("types.MappingProxyType")
|
|
||||||
select cmp, "This test may raise an Exception as the $@ may be of non-container class $@.", origin,
|
select cmp, "This test may raise an Exception as the $@ may be of non-container class $@.", origin,
|
||||||
"target", cls, cls.getName()
|
"target", cls, cls.getName()
|
||||||
|
|||||||
@@ -12,76 +12,97 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
import semmle.python.dataflow.new.DataFlow
|
||||||
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
private import semmle.python.ApiGraphs
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* This assumes that any indexing operation where the value is not a sequence or numpy array involves hashing.
|
* Holds if `cls` explicitly sets `__hash__` to `None`, making instances unhashable.
|
||||||
* For sequences, the index must be an int, which are hashable, so we don't need to treat them specially.
|
|
||||||
* For numpy arrays, the index may be a list, which are not hashable and needs to be treated specially.
|
|
||||||
*/
|
*/
|
||||||
|
predicate setsHashToNone(Class cls) {
|
||||||
predicate numpy_array_type(ClassValue na) {
|
DuckTyping::getAnAttributeValue(cls, "__hash__") instanceof None
|
||||||
exists(ModuleValue np | np.getName() = "numpy" or np.getName() = "numpy.core" |
|
|
||||||
na.getASuperType() = np.attr("ndarray")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate has_custom_getitem(Value v) {
|
/**
|
||||||
v.getClass().lookup("__getitem__") instanceof PythonFunctionValue
|
* Holds if `cls` is a user-defined class whose instances are unhashable.
|
||||||
|
* A new-style class without `__hash__` is unhashable, as is one that explicitly
|
||||||
|
* sets `__hash__ = None`.
|
||||||
|
*/
|
||||||
|
predicate isUnhashableUserClass(Class cls) {
|
||||||
|
DuckTyping::isNewStyle(cls) and
|
||||||
|
not DuckTyping::hasMethod(cls, "__hash__") and
|
||||||
|
not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(cls))
|
||||||
or
|
or
|
||||||
numpy_array_type(v.getClass())
|
setsHashToNone(cls)
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate explicitly_hashed(ControlFlowNode f) {
|
/**
|
||||||
exists(CallNode c, GlobalVariable hash |
|
* Gets the name of a builtin type whose instances are unhashable.
|
||||||
c.getArg(0) = f and c.getFunction().(NameNode).uses(hash) and hash.getId() = "hash"
|
*/
|
||||||
|
string getUnhashableBuiltinName() { result = ["list", "set", "dict", "bytearray"] }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `origin` is a local source node tracking an unhashable instance that
|
||||||
|
* flows to `node`, with `clsName` describing the class for the alert.
|
||||||
|
*/
|
||||||
|
predicate isUnhashable(DataFlow::LocalSourceNode origin, DataFlow::Node node, string clsName) {
|
||||||
|
exists(Class c |
|
||||||
|
isUnhashableUserClass(c) and
|
||||||
|
origin = classInstanceTracker(c) and
|
||||||
|
origin.flowsTo(node) and
|
||||||
|
clsName = c.getName()
|
||||||
|
)
|
||||||
|
or
|
||||||
|
clsName = getUnhashableBuiltinName() and
|
||||||
|
origin = API::builtin(clsName).getAnInstance().asSource() and
|
||||||
|
origin.flowsTo(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate explicitly_hashed(DataFlow::Node node) {
|
||||||
|
node = API::builtin("hash").getACall().getArg(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if the subscript object in `sub[...]` is known to use hashing for indexing,
|
||||||
|
* i.e. it does not have a custom `__getitem__` that could accept unhashable indices.
|
||||||
|
*/
|
||||||
|
predicate subscriptUsesHashing(Subscript sub) {
|
||||||
|
DataFlow::exprNode(sub.getObject()) =
|
||||||
|
API::builtin("dict").getAnInstance().getAValueReachableFromSource()
|
||||||
|
or
|
||||||
|
exists(Class cls |
|
||||||
|
classInstanceTracker(cls)
|
||||||
|
.(DataFlow::LocalSourceNode)
|
||||||
|
.flowsTo(DataFlow::exprNode(sub.getObject())) and
|
||||||
|
not DuckTyping::hasMethod(cls, "__getitem__")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate unhashable_subscript(ControlFlowNode f, ClassValue c, ControlFlowNode origin) {
|
predicate unhashable_subscript(DataFlow::LocalSourceNode origin, DataFlow::Node node, string clsName) {
|
||||||
is_unhashable(f, c, origin) and
|
exists(Subscript sub |
|
||||||
exists(SubscriptNode sub | sub.getIndex() = f |
|
node = DataFlow::exprNode(sub.getIndex()) and
|
||||||
exists(Value custom_getitem |
|
subscriptUsesHashing(sub)
|
||||||
sub.getObject().(ControlFlowNodeWithPointsTo).pointsTo(custom_getitem) and
|
|
|
||||||
not has_custom_getitem(custom_getitem)
|
isUnhashable(origin, node, clsName)
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
predicate is_unhashable(ControlFlowNodeWithPointsTo f, ClassValue cls, ControlFlowNode origin) {
|
|
||||||
exists(Value v | f.pointsTo(v, origin) and v.getClass() = cls |
|
|
||||||
not cls.hasAttribute("__hash__") and not cls.failedInference(_) and cls.isNewStyle()
|
|
||||||
or
|
|
||||||
cls.lookup("__hash__") = Value::named("None")
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if `f` is inside a `try` that catches `TypeError`. For example:
|
* Holds if `e` is inside a `try` that catches `TypeError`.
|
||||||
*
|
|
||||||
* try:
|
|
||||||
* ... f ...
|
|
||||||
* except TypeError:
|
|
||||||
* ...
|
|
||||||
*
|
|
||||||
* This predicate is used to eliminate false positive results. If `hash`
|
|
||||||
* is called on an unhashable object then a `TypeError` will be thrown.
|
|
||||||
* But this is not a bug if the code catches the `TypeError` and handles
|
|
||||||
* it.
|
|
||||||
*/
|
*/
|
||||||
predicate typeerror_is_caught(ControlFlowNode f) {
|
predicate typeerror_is_caught(Expr e) {
|
||||||
exists(Try try |
|
exists(Try try |
|
||||||
try.getBody().contains(f.getNode()) and
|
try.getBody().contains(e) and
|
||||||
try.getAHandler().getType().(ExprWithPointsTo).pointsTo(ClassValue::typeError())
|
try.getAHandler().getType() = API::builtin("TypeError").getAValueReachableFromSource().asExpr()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
from ControlFlowNode f, ClassValue c, ControlFlowNode origin
|
from DataFlow::LocalSourceNode origin, DataFlow::Node node, string clsName
|
||||||
where
|
where
|
||||||
not typeerror_is_caught(f) and
|
not typeerror_is_caught(node.asExpr()) and
|
||||||
(
|
(
|
||||||
explicitly_hashed(f) and is_unhashable(f, c, origin)
|
explicitly_hashed(node) and isUnhashable(origin, node, clsName)
|
||||||
or
|
or
|
||||||
unhashable_subscript(f, c, origin)
|
unhashable_subscript(origin, node, clsName)
|
||||||
)
|
)
|
||||||
select f.getNode(), "This $@ of $@ is unhashable.", origin, "instance", c, c.getQualifiedName()
|
select node, "This $@ of $@ is unhashable.", origin, "instance", origin, clsName
|
||||||
|
|||||||
@@ -10,9 +10,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.ApiGraphs
|
||||||
private import semmle.python.types.Builtins
|
|
||||||
|
|
||||||
from CallNode call, ControlFlowNodeWithPointsTo func
|
from CallNode call
|
||||||
where major_version() = 2 and call.getFunction() = func and func.pointsTo(Value::named("apply"))
|
where
|
||||||
|
major_version() = 2 and
|
||||||
|
call = API::builtin("apply").getACall().asCfgNode()
|
||||||
select call, "Call to the obsolete builtin function 'apply'."
|
select call, "Call to the obsolete builtin function 'apply'."
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
predicate explicitly_returns_non_none(Function func) {
|
predicate explicitly_returns_non_none(Function func) {
|
||||||
exists(Return return |
|
exists(Return return |
|
||||||
@@ -22,8 +22,8 @@ predicate explicitly_returns_non_none(Function func) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
predicate has_implicit_return(Function func) {
|
predicate has_implicit_return(Function func) {
|
||||||
exists(ControlFlowNodeWithPointsTo fallthru |
|
exists(ControlFlowNode fallthru |
|
||||||
fallthru = func.getFallthroughNode() and not fallthru.unlikelyReachable()
|
fallthru = func.getFallthroughNode() and not Reachability::unlikelyReachable(fallthru)
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
exists(Return return | return.getScope() = func and not exists(return.getValue()))
|
exists(Return return | return.getScope() = func and not exists(return.getValue()))
|
||||||
|
|||||||
@@ -10,16 +10,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
predicate slice_method_name(string name) {
|
predicate slice_method_name(string name) {
|
||||||
name = "__getslice__" or name = "__setslice__" or name = "__delslice__"
|
name = "__getslice__" or name = "__setslice__" or name = "__delslice__"
|
||||||
}
|
}
|
||||||
|
|
||||||
from PythonFunctionValue f, string meth
|
from Function f, string meth
|
||||||
where
|
where
|
||||||
f.getScope().isMethod() and
|
f.isMethod() and
|
||||||
not f.isOverridingMethod() and
|
|
||||||
slice_method_name(meth) and
|
slice_method_name(meth) and
|
||||||
f.getName() = meth
|
f.getName() = meth and
|
||||||
|
not DuckTyping::overridesMethod(f) and
|
||||||
|
not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(f.getScope()))
|
||||||
select f, meth + " method has been deprecated since Python 2.0."
|
select f, meth + " method has been deprecated since Python 2.0."
|
||||||
|
|||||||
@@ -12,12 +12,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
import semmle.python.Metrics
|
||||||
|
|
||||||
from FunctionValue method
|
from FunctionMetrics method
|
||||||
where
|
where
|
||||||
exists(ClassValue c |
|
method.getName() = "__del__" and
|
||||||
c.declaredAttribute("__del__") = method and
|
method.isMethod() and
|
||||||
method.getScope().(FunctionMetricsWithPointsTo).getCyclomaticComplexity() > 3
|
method.getCyclomaticComplexity() > 3
|
||||||
)
|
|
||||||
select method, "Overly complex '__del__' method."
|
select method, "Overly complex '__del__' method."
|
||||||
|
|||||||
@@ -12,16 +12,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
import Cyclic
|
import CyclicImports
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||||
|
|
||||||
from ModuleValue m1, ModuleValue m2, Stmt imp
|
from Module m1, Module m2, Stmt imp
|
||||||
where
|
where
|
||||||
imp.getEnclosingModule() = m1.getScope() and
|
imp.getEnclosingModule() = m1 and
|
||||||
stmt_imports(imp) = m2 and
|
stmt_imports(imp) = m2 and
|
||||||
circular_import(m1, m2) and
|
circular_import(m1, m2) and
|
||||||
m1 != m2 and
|
m1 != m2 and
|
||||||
// this query finds all cyclic imports that are *not* flagged by ModuleLevelCyclicImport
|
// this query finds all cyclic imports that are *not* flagged by ModuleLevelCyclicImport
|
||||||
not failing_import_due_to_cycle(m2, m1, _, _, _, _) and
|
not failing_import_due_to_cycle(m2, m1, _, _, _, _) and
|
||||||
not exists(If i | i.isNameEqMain() and i.contains(imp))
|
not exists(If i | i.isNameEqMain() and i.contains(imp))
|
||||||
select imp, "Import of module $@ begins an import cycle.", m2, m2.getName()
|
select imp, "Import of module $@ begins an import cycle.", m2, ImportResolution::moduleName(m2)
|
||||||
|
|||||||
135
python/ql/src/Imports/CyclicImports.qll
Normal file
135
python/ql/src/Imports/CyclicImports.qll
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import python
|
||||||
|
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||||
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
private import semmle.python.types.ImportTime
|
||||||
|
|
||||||
|
Module module_imported_by(Module m) {
|
||||||
|
exists(ImportingStmt imp |
|
||||||
|
result = stmt_imports(imp) and
|
||||||
|
imp.getEnclosingModule() = m and
|
||||||
|
// Import must reach exit to be part of a cycle
|
||||||
|
imp.getAnEntryNode().getBasicBlock().reachesExit()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Is there a circular import of 'm1' beginning with 'm2'? */
|
||||||
|
predicate circular_import(Module m1, Module m2) {
|
||||||
|
m1 != m2 and
|
||||||
|
m2 = module_imported_by(m1) and
|
||||||
|
m1 = module_imported_by+(m2)
|
||||||
|
}
|
||||||
|
|
||||||
|
Module stmt_imports(ImportingStmt s) {
|
||||||
|
(
|
||||||
|
// `import m` — the alias target refers to the imported module
|
||||||
|
exists(Alias a | a = s.(Import).getAName() |
|
||||||
|
ImportResolution::getImmediateModuleReference(result).asExpr() = a.getAsname()
|
||||||
|
)
|
||||||
|
or
|
||||||
|
// `from m import x` — the source module `m` is also imported,
|
||||||
|
// but only if the imported member `x` is not a submodule of `m`
|
||||||
|
exists(ImportMember im | im = s.(Import).getAName().getValue() |
|
||||||
|
ImportResolution::getImmediateModuleReference(result).asExpr() = im.getModule() and
|
||||||
|
not ImportResolution::getImmediateModuleReference(_).asExpr() = im
|
||||||
|
)
|
||||||
|
or
|
||||||
|
// `from m import *`
|
||||||
|
ImportResolution::getImmediateModuleReference(result).asExpr() =
|
||||||
|
s.(ImportStar).getModule().(ImportExpr)
|
||||||
|
) and
|
||||||
|
not result.isPackage() and
|
||||||
|
not result.isPackageInit() and
|
||||||
|
Reachability::likelyReachable(s.getAnEntryNode().getBasicBlock())
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate import_time_imported_module(Module m1, Module m2, Stmt imp) {
|
||||||
|
imp.(ImportingStmt).getEnclosingModule() = m1 and
|
||||||
|
imp.getScope() instanceof ImportTimeScope and
|
||||||
|
m2 = stmt_imports(imp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Is there a cyclic import of 'm1' beginning with an import 'm2' at 'imp' where all the imports are top-level? */
|
||||||
|
predicate import_time_circular_import(Module m1, Module m2, Stmt imp) {
|
||||||
|
m1 != m2 and
|
||||||
|
import_time_imported_module(m1, m2, imp) and
|
||||||
|
import_time_transitive_import(m2, _, m1)
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate import_time_transitive_import(Module base, Stmt imp, Module last) {
|
||||||
|
last != base and
|
||||||
|
(
|
||||||
|
import_time_imported_module(base, last, imp)
|
||||||
|
or
|
||||||
|
exists(Module mid |
|
||||||
|
import_time_transitive_import(base, imp, mid) and
|
||||||
|
import_time_imported_module(mid, last, _)
|
||||||
|
)
|
||||||
|
) and
|
||||||
|
// Import must reach exit to be part of a cycle
|
||||||
|
imp.getAnEntryNode().getBasicBlock().reachesExit()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns import-time usages of module 'm' in module 'enclosing'
|
||||||
|
*/
|
||||||
|
predicate import_time_module_use(Module m, Module enclosing, Expr use, string attr) {
|
||||||
|
exists(Expr mod |
|
||||||
|
use.getEnclosingModule() = enclosing and
|
||||||
|
use.getScope() instanceof ImportTimeScope and
|
||||||
|
ImportResolution::getModuleReference(m).asExpr() = mod and
|
||||||
|
not is_annotation_with_from_future_import_annotations(use)
|
||||||
|
|
|
||||||
|
// either 'M.foo'
|
||||||
|
use.(Attribute).getObject() = mod and use.(Attribute).getName() = attr
|
||||||
|
or
|
||||||
|
// or 'from M import foo'
|
||||||
|
use.(ImportMember).getModule() = mod and use.(ImportMember).getName() = attr
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `use` appears inside an annotation.
|
||||||
|
*/
|
||||||
|
predicate is_used_in_annotation(Expr use) {
|
||||||
|
exists(FunctionExpr f |
|
||||||
|
f.getReturns().getASubExpression*() = use or
|
||||||
|
f.getArgs().getAnAnnotation().getASubExpression*() = use
|
||||||
|
)
|
||||||
|
or
|
||||||
|
exists(AnnAssign a | a.getAnnotation().getASubExpression*() = use)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `use` appears as a subexpression of an annotation, _and_ if the
|
||||||
|
* postponed evaluation of annotations presented in PEP 563 is in effect.
|
||||||
|
* See https://www.python.org/dev/peps/pep-0563/
|
||||||
|
*/
|
||||||
|
predicate is_annotation_with_from_future_import_annotations(Expr use) {
|
||||||
|
exists(ImportMember i | i.getScope() = use.getEnclosingModule() |
|
||||||
|
i.getModule().(ImportExpr).getImportedModuleName() = "__future__" and
|
||||||
|
i.getName() = "annotations"
|
||||||
|
) and
|
||||||
|
is_used_in_annotation(use)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether importing module 'first' before importing module 'other' will fail at runtime, due to an
|
||||||
|
* AttributeError at 'use' (in module 'other') caused by 'first.attr' not being defined as its definition can
|
||||||
|
* occur after the import 'other' in 'first'.
|
||||||
|
*/
|
||||||
|
predicate failing_import_due_to_cycle(
|
||||||
|
Module first, Module other, Stmt imp, ControlFlowNode defn, Expr use, string attr
|
||||||
|
) {
|
||||||
|
import_time_imported_module(other, first, _) and
|
||||||
|
import_time_transitive_import(first, imp, other) and
|
||||||
|
import_time_module_use(first, other, use, attr) and
|
||||||
|
exists(ImportTimeScope n, SsaVariable v |
|
||||||
|
defn = v.getDefinition() and
|
||||||
|
n = first and
|
||||||
|
v.getVariable().getScope() = n and
|
||||||
|
v.getId() = attr
|
||||||
|
|
|
||||||
|
not defn.strictlyDominates(imp.getAnEntryNode())
|
||||||
|
) and
|
||||||
|
not exists(If i | i.isNameEqMain() and i.contains(use))
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.ApiGraphs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if the module `name` was deprecated in Python version `major`.`minor`,
|
* Holds if the module `name` was deprecated in Python version `major`.`minor`,
|
||||||
@@ -80,7 +80,7 @@ where
|
|||||||
name = imp.getName() and
|
name = imp.getName() and
|
||||||
deprecated_module(name, instead, _, _) and
|
deprecated_module(name, instead, _, _) and
|
||||||
not exists(Try try, ExceptStmt except | except = try.getAHandler() |
|
not exists(Try try, ExceptStmt except | except = try.getAHandler() |
|
||||||
except.getType().(ExprWithPointsTo).pointsTo(ClassValue::importError()) and
|
except.getType() = API::builtin("ImportError").getAValueReachableFromSource().asExpr() and
|
||||||
except.containsInScope(imp)
|
except.containsInScope(imp)
|
||||||
)
|
)
|
||||||
select imp, deprecation_message(name) + replacement_message(name)
|
select imp, deprecation_message(name) + replacement_message(name)
|
||||||
|
|||||||
@@ -12,24 +12,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
import semmle.python.dataflow.new.DataFlow
|
||||||
|
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||||
import semmle.python.filters.Tests
|
import semmle.python.filters.Tests
|
||||||
|
|
||||||
from ImportMember im, ModuleValue m, AttrNode store_attr, string name
|
from ImportMember im, Module m, DataFlow::AttrWrite store_attr, string name
|
||||||
where
|
where
|
||||||
m.importedAs(im.getModule().(ImportExpr).getImportedModuleName()) and
|
ImportResolution::getImmediateModuleReference(m).asExpr() = im.getModule() and
|
||||||
im.getName() = name and
|
im.getName() = name and
|
||||||
/* Modification must be in a function, so it can occur during lifetime of the import value */
|
/* Modification must be in a function, so it can occur during lifetime of the import value */
|
||||||
store_attr.getScope() instanceof Function and
|
store_attr.getObject().getScope() instanceof Function and
|
||||||
/* variable resulting from import must have a long lifetime */
|
/* variable resulting from import must have a long lifetime */
|
||||||
not im.getScope() instanceof Function and
|
not im.getScope() instanceof Function and
|
||||||
store_attr.isStore() and
|
store_attr.getAttributeName() = name and
|
||||||
store_attr.getObject(name).(ControlFlowNodeWithPointsTo).pointsTo(m) and
|
ImportResolution::getModuleReference(m) = store_attr.getObject() and
|
||||||
/* Import not in same module as modification. */
|
/* Import not in same module as modification. */
|
||||||
not im.getEnclosingModule() = store_attr.getScope().getEnclosingModule() and
|
not im.getEnclosingModule() = store_attr.getObject().getScope().getEnclosingModule() and
|
||||||
/* Modification is not in a test */
|
/* Modification is not in a test */
|
||||||
not store_attr.getScope().getScope*() instanceof TestScope
|
not store_attr.getObject().getScope().getScope*() instanceof TestScope
|
||||||
select im,
|
select im,
|
||||||
"Importing the value of '" + name +
|
"Importing the value of '" + name +
|
||||||
"' from $@ means that any change made to $@ will be not be observed locally.", m,
|
"' from $@ means that any change made to $@ will be not be observed locally.", m,
|
||||||
"module " + m.getName(), store_attr, m.getName() + "." + store_attr.getName()
|
"module " + ImportResolution::moduleName(m), store_attr,
|
||||||
|
ImportResolution::moduleName(m) + "." + store_attr.getAttributeName()
|
||||||
|
|||||||
@@ -12,19 +12,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
import semmle.python.dataflow.new.DataFlow
|
||||||
|
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||||
|
|
||||||
predicate modules_imports_itself(ImportingStmt i, ModuleValue m) {
|
predicate modules_imports_itself(ImportingStmt i, Module m) {
|
||||||
i.getEnclosingModule() = m.getScope() and
|
m = i.getEnclosingModule() and
|
||||||
m =
|
ImportResolution::importedBy(i, m) and
|
||||||
max(string s, ModuleValue m_ |
|
// Exclude `from m import submodule` where the imported member is a submodule of m
|
||||||
s = i.getAnImportedModuleName() and
|
not exists(ImportMember im | im = i.(Import).getAName().getValue() |
|
||||||
m_.importedAs(s)
|
ImportResolution::getImmediateModuleReference(m).asExpr() = im.getModule() and
|
||||||
|
|
ImportResolution::importedBy(i, any(Module sub | sub != m))
|
||||||
m_ order by s.length()
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
from ImportingStmt i, ModuleValue m
|
from ImportingStmt i, Module m
|
||||||
where modules_imports_itself(i, m)
|
where modules_imports_itself(i, m)
|
||||||
select i, "The module '" + m.getName() + "' imports itself."
|
select i, "The module '" + ImportResolution::moduleName(m) + "' imports itself."
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
import Cyclic
|
import CyclicImports
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||||
|
|
||||||
// This is a potentially crashing bug if
|
// This is a potentially crashing bug if
|
||||||
// 1. the imports in the whole cycle are lexically outside a def (and so executed at import time)
|
// 1. the imports in the whole cycle are lexically outside a def (and so executed at import time)
|
||||||
@@ -22,10 +22,11 @@ private import LegacyPointsTo
|
|||||||
// 3. 'foo' is defined in M after the import in M which completes the cycle.
|
// 3. 'foo' is defined in M after the import in M which completes the cycle.
|
||||||
// then if we import the 'used' module, we will reach the cyclic import, start importing the 'using'
|
// then if we import the 'used' module, we will reach the cyclic import, start importing the 'using'
|
||||||
// module, hit the 'use', and then crash due to the imported symbol not having been defined yet
|
// module, hit the 'use', and then crash due to the imported symbol not having been defined yet
|
||||||
from ModuleValue m1, Stmt imp, ModuleValue m2, string attr, Expr use, ControlFlowNode defn
|
from Module m1, Stmt imp, Module m2, string attr, Expr use, ControlFlowNode defn
|
||||||
where failing_import_due_to_cycle(m1, m2, imp, defn, use, attr)
|
where failing_import_due_to_cycle(m1, m2, imp, defn, use, attr)
|
||||||
select use,
|
select use,
|
||||||
"'" + attr + "' may not be defined if module $@ is imported before module $@, as the $@ of " +
|
"'" + attr + "' may not be defined if module $@ is imported before module $@, as the $@ of " +
|
||||||
attr + " occurs after the cyclic $@ of " + m2.getName() + ".",
|
attr + " occurs after the cyclic $@ of " + ImportResolution::moduleName(m2) + ".",
|
||||||
// Arguments for the placeholders in the above message:
|
// Arguments for the placeholders in the above message:
|
||||||
m1, m1.getName(), m2, m2.getName(), defn, "definition", imp, "import"
|
m1, ImportResolution::moduleName(m1), m2, ImportResolution::moduleName(m2), defn, "definition",
|
||||||
|
imp, "import"
|
||||||
|
|||||||
@@ -13,23 +13,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||||
private import semmle.python.types.ImportTime
|
private import semmle.python.types.ImportTime
|
||||||
|
|
||||||
predicate import_star(ImportStar imp, ModuleValue exporter) {
|
predicate all_defined(Module exporter) {
|
||||||
exporter.importedAs(imp.getImportedModuleName())
|
exporter.(ImportTimeScope).definesName("__all__")
|
||||||
|
or
|
||||||
|
exporter.getInitModule().(ImportTimeScope).definesName("__all__")
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate all_defined(ModuleValue exporter) {
|
from ImportStar imp, Module exporter
|
||||||
exporter.isBuiltin()
|
where
|
||||||
or
|
exporter = ImportResolution::getModuleImportedByImportStar(imp) and
|
||||||
exporter.getScope().(ImportTimeScope).definesName("__all__")
|
not all_defined(exporter)
|
||||||
or
|
|
||||||
exporter.getScope().getInitModule().(ImportTimeScope).definesName("__all__")
|
|
||||||
}
|
|
||||||
|
|
||||||
from ImportStar imp, ModuleValue exporter
|
|
||||||
where import_star(imp, exporter) and not all_defined(exporter) and not exporter.isAbsent()
|
|
||||||
select imp,
|
select imp,
|
||||||
"Import pollutes the enclosing namespace, as the imported module $@ does not define '__all__'.",
|
"Import pollutes the enclosing namespace, as the imported module $@ does not define '__all__'.",
|
||||||
exporter, exporter.getName()
|
exporter, exporter.getName()
|
||||||
|
|||||||
@@ -12,9 +12,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
|
||||||
import Variables.Definition
|
import Variables.Definition
|
||||||
import semmle.python.ApiGraphs
|
import semmle.python.ApiGraphs
|
||||||
|
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||||
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
private predicate is_pytest_fixture(Import imp, Variable name) {
|
private predicate is_pytest_fixture(Import imp, Variable name) {
|
||||||
exists(Alias a, API::Node pytest_fixture, API::Node decorator |
|
exists(Alias a, API::Node pytest_fixture, API::Node decorator |
|
||||||
@@ -95,7 +96,7 @@ private string typehint_annotation_in_module(Module module_scope) {
|
|||||||
or
|
or
|
||||||
annotation = any(FunctionExpr f).getReturns().getASubExpression*()
|
annotation = any(FunctionExpr f).getReturns().getASubExpression*()
|
||||||
|
|
|
|
||||||
annotation.(ExprWithPointsTo).pointsTo(Value::forString(result)) and
|
annotation.getText() = result and
|
||||||
annotation.getEnclosingModule() = module_scope
|
annotation.getEnclosingModule() = module_scope
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -143,9 +144,8 @@ predicate unused_import(Import imp, Variable name) {
|
|||||||
not imported_module_used_in_doctest(imp) and
|
not imported_module_used_in_doctest(imp) and
|
||||||
not imported_alias_used_in_typehint(imp, name) and
|
not imported_alias_used_in_typehint(imp, name) and
|
||||||
not is_pytest_fixture(imp, name) and
|
not is_pytest_fixture(imp, name) and
|
||||||
// Only consider import statements that actually point-to something (possibly an unknown module).
|
// Only consider import statements in reachable code.
|
||||||
// If this is not the case, it's likely that the import statement never gets executed.
|
Reachability::likelyReachable(imp.getAName().getValue().getAFlowNode().getBasicBlock())
|
||||||
imp.getAName().getValue().(ExprWithPointsTo).pointsTo(_)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
from Stmt s, Variable name
|
from Stmt s, Variable name
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
import semmle.python.Metrics
|
||||||
|
|
||||||
from FunctionMetricsWithPointsTo func, int complexity
|
from FunctionMetrics func, int complexity
|
||||||
where complexity = func.getCyclomaticComplexity()
|
where complexity = func.getCyclomaticComplexity()
|
||||||
select func, complexity order by complexity desc
|
select func, complexity order by complexity desc
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||||
|
|
||||||
from ModuleValue m, int n
|
from Module m, int n
|
||||||
where n = count(ModuleValue imp | imp = m.getAnImportedModule())
|
where n = count(Module imp | ImportResolution::imports(m, imp))
|
||||||
select m.getScope(), n
|
select m, n
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||||
|
|
||||||
from ModuleValue m, int n
|
from Module m, int n
|
||||||
where n = count(ModuleValue imp | imp = m.getAnImportedModule+() and imp != m)
|
where n = count(Module imp | ImportResolution::imports+(m, imp) and imp != m)
|
||||||
select m.getScope(), n
|
select m, n
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
predicate needs_docstring(Scope s) {
|
predicate needs_docstring(Scope s) {
|
||||||
s.isPublic() and
|
s.isPublic() and
|
||||||
@@ -29,15 +29,15 @@ predicate needs_docstring(Scope s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
predicate function_needs_docstring(FunctionMetrics f) {
|
predicate function_needs_docstring(FunctionMetrics f) {
|
||||||
not exists(FunctionValue fo, FunctionValue base | fo.overrides(base) and fo.getScope() = f |
|
not exists(Function base |
|
||||||
not function_needs_docstring(base.getScope())
|
DuckTyping::overridesMethod(f) and
|
||||||
|
base.getScope() = getADirectSuperclass+(f.getScope()) and
|
||||||
|
base.getName() = f.getName() and
|
||||||
|
not function_needs_docstring(base)
|
||||||
) and
|
) and
|
||||||
f.getName() != "lambda" and
|
f.getName() != "lambda" and
|
||||||
(f.getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 and
|
(f.getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 and
|
||||||
not exists(PythonPropertyObject p |
|
not DuckTyping::isPropertyAccessor(f)
|
||||||
p.getGetter().getFunction() = f or
|
|
||||||
p.getSetter().getFunction() = f
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string scope_type(Scope s) {
|
string scope_type(Scope s) {
|
||||||
|
|||||||
@@ -12,10 +12,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.ApiGraphs
|
||||||
|
|
||||||
predicate originIsLocals(ControlFlowNodeWithPointsTo n) {
|
predicate originIsLocals(ControlFlowNode n) {
|
||||||
n.pointsTo(_, _, Value::named("locals").getACall())
|
API::builtin("locals").getReturn().getAValueReachableFromSource().asCfgNode() = n
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate modification_of_locals(ControlFlowNode f) {
|
predicate modification_of_locals(ControlFlowNode f) {
|
||||||
@@ -37,5 +37,5 @@ where
|
|||||||
// in module level scope `locals() == globals()`
|
// in module level scope `locals() == globals()`
|
||||||
// see https://docs.python.org/3/library/functions.html#locals
|
// see https://docs.python.org/3/library/functions.html#locals
|
||||||
// FP report in https://github.com/github/codeql/issues/6674
|
// FP report in https://github.com/github/codeql/issues/6674
|
||||||
not a.getScope() instanceof ModuleScope
|
not a.getScope() instanceof Module
|
||||||
select a, "Modification of the locals() dictionary will have no effect on the local variables."
|
select a, "Modification of the locals() dictionary will have no effect on the local variables."
|
||||||
|
|||||||
@@ -12,16 +12,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
from For loop, ControlFlowNodeWithPointsTo iter, Value v, ClassValue t, ControlFlowNode origin
|
from For loop, Expr iter, Class cls
|
||||||
where
|
where
|
||||||
loop.getIter().getAFlowNode() = iter and
|
iter = loop.getIter() and
|
||||||
iter.pointsTo(_, v, origin) and
|
classInstanceTracker(cls).asExpr() = iter and
|
||||||
v.getClass() = t and
|
not DuckTyping::isIterable(cls) and
|
||||||
not t.isIterable() and
|
not DuckTyping::isDescriptor(cls) and
|
||||||
not t.failedInference(_) and
|
not (loop.isAsync() and DuckTyping::hasMethod(cls, "__aiter__")) and
|
||||||
not v = Value::named("None") and
|
not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(cls))
|
||||||
not t.isDescriptorType()
|
select loop, "This for-loop may attempt to iterate over a $@ of class $@.", iter,
|
||||||
select loop, "This for-loop may attempt to iterate over a $@ of class $@.", origin,
|
"non-iterable instance", cls, cls.getName()
|
||||||
"non-iterable instance", t, t.getName()
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
|
||||||
predicate calls_close(Call c) { exists(Attribute a | c.getFunc() = a and a.getName() = "close") }
|
predicate calls_close(Call c) { exists(Attribute a | c.getFunc() = a and a.getName() = "close") }
|
||||||
|
|
||||||
@@ -23,18 +23,12 @@ predicate only_stmt_in_finally(Try t, Call c) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate points_to_context_manager(ControlFlowNodeWithPointsTo f, ClassValue cls) {
|
from Call close, Try t, Class cls
|
||||||
forex(Value v | f.pointsTo(v) | v.getClass() = cls) and
|
|
||||||
cls.isContextManager()
|
|
||||||
}
|
|
||||||
|
|
||||||
from Call close, Try t, ClassValue cls
|
|
||||||
where
|
where
|
||||||
only_stmt_in_finally(t, close) and
|
only_stmt_in_finally(t, close) and
|
||||||
calls_close(close) and
|
calls_close(close) and
|
||||||
exists(ControlFlowNode f | f = close.getFunc().getAFlowNode().(AttrNode).getObject() |
|
classInstanceTracker(cls).asExpr() = close.getFunc().(Attribute).getObject() and
|
||||||
points_to_context_manager(f, cls)
|
DuckTyping::isContextManager(cls)
|
||||||
)
|
|
||||||
select close,
|
select close,
|
||||||
"Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement.",
|
"Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement.",
|
||||||
cls, cls.getName()
|
cls, cls.getName()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.ApiGraphs
|
||||||
|
|
||||||
predicate func_with_side_effects(Expr e) {
|
predicate func_with_side_effects(Expr e) {
|
||||||
exists(string name | name = e.(Attribute).getName() or name = e.(Name).getId() |
|
exists(string name | name = e.(Attribute).getName() or name = e.(Name).getId() |
|
||||||
@@ -24,11 +24,11 @@ predicate func_with_side_effects(Expr e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
predicate call_with_side_effect(Call e) {
|
predicate call_with_side_effect(Call e) {
|
||||||
e.getAFlowNode() = Value::named("subprocess.call").getACall()
|
e.getAFlowNode() =
|
||||||
or
|
API::moduleImport("subprocess")
|
||||||
e.getAFlowNode() = Value::named("subprocess.check_call").getACall()
|
.getMember(["call", "check_call", "check_output"])
|
||||||
or
|
.getACall()
|
||||||
e.getAFlowNode() = Value::named("subprocess.check_output").getACall()
|
.asCfgNode()
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate probable_side_effect(Expr e) {
|
predicate probable_side_effect(Expr e) {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
|
||||||
|
|
||||||
predicate main_eq_name(If i) {
|
predicate main_eq_name(If i) {
|
||||||
exists(Name n, StringLiteral m, Compare c |
|
exists(Name n, StringLiteral m, Compare c |
|
||||||
@@ -32,10 +31,19 @@ predicate is_print_stmt(Stmt s) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if module `m` is likely used as a module (imported by another module),
|
||||||
|
* as opposed to being exclusively used as a script.
|
||||||
|
*/
|
||||||
|
predicate is_used_as_module(Module m) {
|
||||||
|
m.isPackageInit()
|
||||||
|
or
|
||||||
|
exists(ImportingStmt i | i.getAnImportedModuleName() = m.getName())
|
||||||
|
}
|
||||||
|
|
||||||
from Stmt p
|
from Stmt p
|
||||||
where
|
where
|
||||||
is_print_stmt(p) and
|
is_print_stmt(p) and
|
||||||
// TODO: Need to discuss how we would like to handle ModuleObject.getKind in the glorious future
|
is_used_as_module(p.getScope()) and
|
||||||
exists(ModuleValue m | m.getScope() = p.getScope() and m.isUsedAsModule()) and
|
|
||||||
not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p)
|
not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p)
|
||||||
select p, "Print statement may execute during import."
|
select p, "Print statement may execute during import."
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.ApiGraphs
|
||||||
|
|
||||||
predicate isInsideLoop(AstNode node) {
|
predicate isInsideLoop(AstNode node) {
|
||||||
node.getParentNode() instanceof While
|
node.getParentNode() instanceof While
|
||||||
@@ -33,9 +33,9 @@ where
|
|||||||
not isInsideLoop(del) and
|
not isInsideLoop(del) and
|
||||||
// False positive: calling `sys.exc_info` within a function results in a
|
// False positive: calling `sys.exc_info` within a function results in a
|
||||||
// reference cycle, and an explicit call to `del` helps break this cycle.
|
// reference cycle, and an explicit call to `del` helps break this cycle.
|
||||||
not exists(FunctionValue ex |
|
not exists(API::CallNode call |
|
||||||
ex = Value::named("sys.exc_info") and
|
call = API::moduleImport("sys").getMember("exc_info").getACall() and
|
||||||
ex.getACall().getScope() = f
|
call.getScope() = f
|
||||||
)
|
)
|
||||||
select del, "Unnecessary deletion of local variable $@ in function $@.", e, e.toString(), f,
|
select del, "Unnecessary deletion of local variable $@ in function $@.", e, e.toString(), f,
|
||||||
f.getName()
|
f.getName()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.ApiGraphs
|
||||||
|
|
||||||
predicate typing_import(ImportingStmt is) {
|
predicate typing_import(ImportingStmt is) {
|
||||||
exists(Module m |
|
exists(Module m |
|
||||||
@@ -34,11 +34,7 @@ predicate unique_yield(Stmt s) {
|
|||||||
/** Holds if `contextlib.suppress` may be used in the same scope as `s` */
|
/** Holds if `contextlib.suppress` may be used in the same scope as `s` */
|
||||||
predicate suppression_in_scope(Stmt s) {
|
predicate suppression_in_scope(Stmt s) {
|
||||||
exists(With w |
|
exists(With w |
|
||||||
w.getContextExpr()
|
w.getContextExpr() = API::moduleImport("contextlib").getMember("suppress").getACall().asExpr() and
|
||||||
.(Call)
|
|
||||||
.getFunc()
|
|
||||||
.(ExprWithPointsTo)
|
|
||||||
.pointsTo(Value::named("contextlib.suppress")) and
|
|
||||||
w.getScope() = s.getScope()
|
w.getScope() = s.getScope()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,49 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||||
|
private import semmle.python.dataflow.new.internal.Builtins
|
||||||
|
private import semmle.python.ApiGraphs
|
||||||
|
|
||||||
from Call call, ClassValue ex
|
/**
|
||||||
|
* Holds if `cls` is a user-defined exception class, i.e. it transitively
|
||||||
|
* extends one of the builtin exception base classes.
|
||||||
|
*/
|
||||||
|
predicate isUserDefinedExceptionClass(Class cls) {
|
||||||
|
cls.getABase() =
|
||||||
|
API::builtin(["BaseException", "Exception"]).getAValueReachableFromSource().asExpr()
|
||||||
|
or
|
||||||
|
isUserDefinedExceptionClass(getADirectSuperclass(cls))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the name of a builtin exception class.
|
||||||
|
*/
|
||||||
|
string getBuiltinExceptionName() {
|
||||||
|
result = Builtins::getBuiltinName() and
|
||||||
|
(
|
||||||
|
result.matches("%Error") or
|
||||||
|
result.matches("%Exception") or
|
||||||
|
result.matches("%Warning") or
|
||||||
|
result =
|
||||||
|
["GeneratorExit", "KeyboardInterrupt", "StopIteration", "StopAsyncIteration", "SystemExit"]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `call` is an instantiation of an exception class.
|
||||||
|
*/
|
||||||
|
predicate isExceptionInstantiation(Call call) {
|
||||||
|
exists(Class cls |
|
||||||
|
classTracker(cls).asExpr() = call.getFunc() and
|
||||||
|
isUserDefinedExceptionClass(cls)
|
||||||
|
)
|
||||||
|
or
|
||||||
|
call.getFunc() = API::builtin(getBuiltinExceptionName()).getAValueReachableFromSource().asExpr()
|
||||||
|
}
|
||||||
|
|
||||||
|
from Call call
|
||||||
where
|
where
|
||||||
call.getFunc().(ExprWithPointsTo).pointsTo(ex) and
|
isExceptionInstantiation(call) and
|
||||||
ex.getASuperType() = ClassValue::exception() and
|
|
||||||
exists(ExprStmt s | s.getValue() = call)
|
exists(ExprStmt s | s.getValue() = call)
|
||||||
select call, "Instantiating an exception, but not raising it, has no effect."
|
select call, "Instantiating an exception, but not raising it, has no effect."
|
||||||
|
|||||||
@@ -12,10 +12,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.ApiGraphs
|
||||||
|
|
||||||
from CallNode call, string name
|
from CallNode call, string name
|
||||||
where call.getFunction().(ControlFlowNodeWithPointsTo).pointsTo(Value::siteQuitter(name))
|
where
|
||||||
|
name = ["exit", "quit"] and
|
||||||
|
call = API::builtin(name).getACall().asCfgNode()
|
||||||
select call,
|
select call,
|
||||||
"The '" + name +
|
"The '" + name +
|
||||||
"' site.Quitter object may not exist if the 'site' module is not loaded or is modified."
|
"' site.Quitter object may not exist if the 'site' module is not loaded or is modified."
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import python
|
import python
|
||||||
private import LegacyPointsTo
|
private import semmle.python.ApiGraphs
|
||||||
import Definition
|
import Definition
|
||||||
|
|
||||||
predicate is_increment(Stmt s) {
|
predicate is_increment(Stmt s) {
|
||||||
@@ -41,23 +41,16 @@ predicate one_item_only(For f) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate points_to_call_to_range(ControlFlowNode f) {
|
/** Holds if `node` is a call to `range`, `xrange`, or `list(range(...))`. */
|
||||||
/* (x)range is a function in Py2 and a class in Py3, so we must treat it as a plain object */
|
predicate call_to_range(DataFlow::Node node) {
|
||||||
exists(Value range |
|
node = API::builtin(["range", "xrange"]).getACall()
|
||||||
range = Value::named("range") or
|
|
||||||
range = Value::named("xrange")
|
|
||||||
|
|
|
||||||
f = range.getACall()
|
|
||||||
)
|
|
||||||
or
|
or
|
||||||
/* In case points-to fails due to 'from six.moves import range' or similar. */
|
/* Handle 'from six.moves import range' or similar. */
|
||||||
exists(string range | f.getNode().(Call).getFunc().(Name).getId() = range |
|
node = API::moduleImport("six").getMember("moves").getMember(["range", "xrange"]).getACall()
|
||||||
range = "range" or range = "xrange"
|
|
||||||
)
|
|
||||||
or
|
or
|
||||||
/* Handle list(range(...)) and list(list(range(...))) */
|
/* Handle list(range(...)) and list(list(range(...))) */
|
||||||
f.(CallNode).(ControlFlowNodeWithPointsTo).pointsTo().getClass() = ClassValue::list() and
|
node = API::builtin("list").getACall() and
|
||||||
points_to_call_to_range(f.(CallNode).getArg(0))
|
call_to_range(node.(DataFlow::CallCfgNode).getArg(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether n is a use of a variable that is a not effectively a constant. */
|
/** Whether n is a use of a variable that is a not effectively a constant. */
|
||||||
@@ -102,8 +95,8 @@ from For f, Variable v, string msg
|
|||||||
where
|
where
|
||||||
f.getTarget() = v.getAnAccess() and
|
f.getTarget() = v.getAnAccess() and
|
||||||
not f.getAStmt().contains(v.getAnAccess()) and
|
not f.getAStmt().contains(v.getAnAccess()) and
|
||||||
not points_to_call_to_range(f.getIter().getAFlowNode()) and
|
not call_to_range(DataFlow::exprNode(f.getIter())) and
|
||||||
not points_to_call_to_range(get_comp_iterable(f)) and
|
not call_to_range(DataFlow::exprNode(get_comp_iterable(f).getNode())) and
|
||||||
not name_acceptable_for_unused_variable(v) and
|
not name_acceptable_for_unused_variable(v) and
|
||||||
not f.getScope().getName() = "genexpr" and
|
not f.getScope().getName() = "genexpr" and
|
||||||
not empty_loop(f) and
|
not empty_loop(f) and
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
| inconsistent_mro.py:9:1:9:14 | class Z | Construction of class Z can fail due to invalid method resolution order(MRO) for bases $@ and $@. | inconsistent_mro.py:3:1:3:16 | class X | X | inconsistent_mro.py:6:1:6:11 | class Y | Y |
|
| inconsistent_mro.py:9:1:9:14 | Class Z | Construction of class Z can fail due to invalid method resolution order(MRO) for bases $@ and $@. | inconsistent_mro.py:3:1:3:16 | Class X | X | inconsistent_mro.py:6:1:6:11 | Class Y | Y |
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
| property_old_style.py:8:6:8:13 | Property piosc | Property piosc will not work properly, as class OldStyle is an old-style class. |
|
| property_old_style.py:9:5:9:20 | Function piosc | Property piosc will not work properly, as class OldStyle is an old-style class. |
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
| newstyle_test.py:4:1:4:16 | class OldStyle1 | Using '__slots__' in an old style class just creates a class attribute called '__slots__'. |
|
| newstyle_test.py:4:1:4:16 | Class OldStyle1 | Using '__slots__' in an old style class just creates a class attribute called '__slots__'. |
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
| inconsistent_mro.py:9:1:9:14 | class Z | Construction of class Z can fail due to invalid method resolution order(MRO) for bases $@ and $@. | inconsistent_mro.py:3:1:3:16 | class X | X | inconsistent_mro.py:6:1:6:11 | class Y | Y |
|
| inconsistent_mro.py:9:1:9:14 | Class Z | Construction of class Z can fail due to invalid method resolution order(MRO) for bases $@ and $@. | inconsistent_mro.py:3:1:3:16 | Class X | X | inconsistent_mro.py:6:1:6:11 | Class Y | Y |
|
||||||
| inconsistent_mro.py:16:1:16:19 | class N | Construction of class N can fail due to invalid method resolution order(MRO) for bases $@ and $@. | file://:Compiled Code:0:0:0:0 | builtin-class object | object | inconsistent_mro.py:12:1:12:8 | class O | O |
|
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
| async_iterator.py:26:11:26:34 | For | This for-loop may attempt to iterate over a $@ of class $@. | async_iterator.py:26:20:26:33 | ControlFlowNode for MissingAiter() | non-iterable instance | async_iterator.py:13:1:13:19 | class MissingAiter | MissingAiter |
|
| async_iterator.py:26:11:26:34 | For | This for-loop may attempt to iterate over a $@ of class $@. | async_iterator.py:26:20:26:33 | MissingAiter() | non-iterable instance | async_iterator.py:13:1:13:19 | Class MissingAiter | MissingAiter |
|
||||||
| statements_test.py:34:5:34:19 | For | This for-loop may attempt to iterate over a $@ of class $@. | statements_test.py:34:18:34:18 | ControlFlowNode for IntegerLiteral | non-iterable instance | file://:0:0:0:0 | builtin-class int | int |
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
| wrong_arguments.py:65:1:65:7 | F0() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:4:5:4:26 | Function F0.__init__ | F0.__init__ |
|
| wrong_arguments.py:65:1:65:7 | F0() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ |
|
||||||
| wrong_arguments.py:66:1:66:7 | F1() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:8:5:8:36 | Function F1.__init__ | F1.__init__ |
|
| wrong_arguments.py:66:1:66:7 | F1() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ |
|
||||||
| wrong_arguments.py:67:1:67:12 | F2() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:12:5:12:30 | Function F2.__init__ | F2.__init__ |
|
| wrong_arguments.py:67:1:67:12 | F2() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:12:5:12:30 | Function __init__ | F2.__init__ |
|
||||||
| wrong_arguments.py:92:1:92:27 | F6() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
|
| wrong_arguments.py:92:1:92:27 | F6() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
| wrong_arguments.py:37:1:37:4 | F0() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:4:5:4:26 | Function F0.__init__ | F0.__init__ |
|
| wrong_arguments.py:37:1:37:4 | F0() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ |
|
||||||
| wrong_arguments.py:38:1:38:4 | F1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:8:5:8:36 | Function F1.__init__ | F1.__init__ |
|
| wrong_arguments.py:38:1:38:4 | F1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ |
|
||||||
| wrong_arguments.py:39:1:39:4 | F2() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:12:5:12:30 | Function F2.__init__ | F2.__init__ |
|
| wrong_arguments.py:39:1:39:4 | F2() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:12:5:12:30 | Function __init__ | F2.__init__ |
|
||||||
| wrong_arguments.py:40:1:40:4 | F3() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:16:5:16:40 | Function F3.__init__ | F3.__init__ |
|
| wrong_arguments.py:40:1:40:4 | F3() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:16:5:16:40 | Function __init__ | F3.__init__ |
|
||||||
| wrong_arguments.py:41:1:41:4 | F4() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:20:5:20:31 | Function F4.__init__ | F4.__init__ |
|
| wrong_arguments.py:41:1:41:4 | F4() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:20:5:20:31 | Function __init__ | F4.__init__ |
|
||||||
| wrong_arguments.py:42:1:42:4 | F5() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:24:5:24:42 | Function F5.__init__ | F5.__init__ |
|
| wrong_arguments.py:42:1:42:4 | F5() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:24:5:24:42 | Function __init__ | F5.__init__ |
|
||||||
| wrong_arguments.py:43:1:43:5 | F6() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
|
| wrong_arguments.py:43:1:43:5 | F6() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
|
||||||
| wrong_arguments.py:44:1:44:7 | F7() | Call to $@ with too few arguments; should be no fewer than 3. | wrong_arguments.py:32:5:32:33 | Function F7.__init__ | F7.__init__ |
|
| wrong_arguments.py:44:1:44:7 | F7() | Call to $@ with too few arguments; should be no fewer than 3. | wrong_arguments.py:32:5:32:33 | Function __init__ | F7.__init__ |
|
||||||
| wrong_arguments.py:48:1:48:7 | F0() | Call to $@ with too many arguments; should be no more than 1. | wrong_arguments.py:4:5:4:26 | Function F0.__init__ | F0.__init__ |
|
| wrong_arguments.py:48:1:48:7 | F0() | Call to $@ with too many arguments; should be no more than 1. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ |
|
||||||
| wrong_arguments.py:49:1:49:9 | F1() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:8:5:8:36 | Function F1.__init__ | F1.__init__ |
|
| wrong_arguments.py:49:1:49:9 | F1() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ |
|
||||||
| wrong_arguments.py:50:1:50:9 | F5() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:24:5:24:42 | Function F5.__init__ | F5.__init__ |
|
| wrong_arguments.py:50:1:50:9 | F5() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:24:5:24:42 | Function __init__ | F5.__init__ |
|
||||||
| wrong_arguments.py:51:1:51:9 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
|
| wrong_arguments.py:51:1:51:9 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
|
||||||
| wrong_arguments.py:52:1:52:11 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
|
| wrong_arguments.py:52:1:52:11 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
|
||||||
| wrong_arguments.py:85:1:85:12 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
|
|
||||||
| wrong_arguments.py:86:1:86:7 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
| should_be_context_manager.py:3:1:3:22 | class MegaDel | Class MegaDel implements __del__ (presumably to release some resource). Consider making it a context manager. |
|
| should_be_context_manager.py:3:1:3:22 | Class MegaDel | Class MegaDel implements __del__ (presumably to release some resource). Consider making it a context manager. |
|
||||||
| should_be_context_manager.py:16:1:16:22 | class MiniDel | Class MiniDel implements __del__ (presumably to release some resource). Consider making it a context manager. |
|
| should_be_context_manager.py:16:1:16:22 | Class MiniDel | Class MiniDel implements __del__ (presumably to release some resource). Consider making it a context manager. |
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
| exceptions_test.py:51:5:51:25 | ExceptStmt | Non-exception $@ in exception handler which will never match raised exception. | exceptions_test.py:33:1:33:28 | ControlFlowNode for ClassExpr | class 'NotException1' |
|
| exceptions_test.py:51:5:51:25 | ExceptStmt | Non-exception class 'NotException1' in exception handler which will never match raised exception. |
|
||||||
| exceptions_test.py:54:5:54:25 | ExceptStmt | Non-exception $@ in exception handler which will never match raised exception. | exceptions_test.py:36:1:36:28 | ControlFlowNode for ClassExpr | class 'NotException2' |
|
| exceptions_test.py:54:5:54:25 | ExceptStmt | Non-exception class 'NotException2' in exception handler which will never match raised exception. |
|
||||||
| exceptions_test.py:138:5:138:22 | ExceptStmt | Non-exception $@ in exception handler which will never match raised exception. | exceptions_test.py:133:12:133:14 | ControlFlowNode for FloatLiteral | instance of 'float' |
|
| pypy_test.py:14:5:14:14 | ExceptStmt | Non-exception class 'int' in exception handler which will never match raised exception. |
|
||||||
| pypy_test.py:14:5:14:14 | ExceptStmt | Non-exception $@ in exception handler which will never match raised exception. | pypy_test.py:14:12:14:13 | ControlFlowNode for IntegerLiteral | instance of 'int' |
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
| expressions_test.py:89:8:89:15 | Compare | This test may raise an Exception as the $@ may be of non-container class $@. | expressions_test.py:88:11:88:17 | ControlFlowNode for XIter() | target | expressions_test.py:77:1:77:20 | class XIter | XIter |
|
| expressions_test.py:89:8:89:15 | Compare | This test may raise an Exception as the $@ may be of non-container class $@. | expressions_test.py:88:11:88:17 | ControlFlowNode for XIter() | target | expressions_test.py:77:1:77:20 | Class XIter | XIter |
|
||||||
| expressions_test.py:91:8:91:19 | Compare | This test may raise an Exception as the $@ may be of non-container class $@. | expressions_test.py:88:11:88:17 | ControlFlowNode for XIter() | target | expressions_test.py:77:1:77:20 | class XIter | XIter |
|
| expressions_test.py:91:8:91:19 | Compare | This test may raise an Exception as the $@ may be of non-container class $@. | expressions_test.py:88:11:88:17 | ControlFlowNode for XIter() | target | expressions_test.py:77:1:77:20 | Class XIter | XIter |
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
| expressions_test.py:42:20:42:25 | unhash | This $@ of $@ is unhashable. | expressions_test.py:41:32:41:37 | ControlFlowNode for list() | instance | file://:0:0:0:0 | builtin-class list | list |
|
| expressions_test.py:42:20:42:25 | ControlFlowNode for unhash | This $@ of $@ is unhashable. | expressions_test.py:41:32:41:37 | ControlFlowNode for list() | instance | expressions_test.py:41:32:41:37 | ControlFlowNode for list() | list |
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
| functions_test.py:95:5:95:40 | Function DeprecatedSliceMethods.__getslice__ | __getslice__ method has been deprecated since Python 2.0. |
|
| functions_test.py:95:5:95:40 | Function __getslice__ | __getslice__ method has been deprecated since Python 2.0. |
|
||||||
| functions_test.py:98:5:98:47 | Function DeprecatedSliceMethods.__setslice__ | __setslice__ method has been deprecated since Python 2.0. |
|
| functions_test.py:98:5:98:47 | Function __setslice__ | __setslice__ method has been deprecated since Python 2.0. |
|
||||||
| functions_test.py:101:5:101:40 | Function DeprecatedSliceMethods.__delslice__ | __delslice__ method has been deprecated since Python 2.0. |
|
| functions_test.py:101:5:101:40 | Function __delslice__ | __delslice__ method has been deprecated since Python 2.0. |
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
| protocols.py:74:5:74:22 | Function MegaDel.__del__ | Overly complex '__del__' method. |
|
| protocols.py:74:5:74:22 | Function __del__ | Overly complex '__del__' method. |
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
| imports_test.py:8:1:8:19 | Import | The module 'imports_test' imports itself. |
|
| imports_test.py:8:1:8:19 | Import | The module 'imports_test' imports itself. |
|
||||||
| pkg_notok/__init__.py:4:1:4:16 | Import | The module 'pkg_notok' imports itself. |
|
| pkg_notok/__init__.py:4:1:4:16 | Import | The module 'pkg_notok' imports itself. |
|
||||||
|
| pkg_notok/__init__.py:10:1:10:20 | Import | The module 'pkg_notok' imports itself. |
|
||||||
| pkg_notok/__init__.py:12:1:12:25 | Import | The module 'pkg_notok' imports itself. |
|
| pkg_notok/__init__.py:12:1:12:25 | Import | The module 'pkg_notok' imports itself. |
|
||||||
| pkg_notok/__init__.py:13:1:13:37 | Import | The module 'pkg_notok' imports itself. |
|
| pkg_notok/__init__.py:13:1:13:37 | Import | The module 'pkg_notok' imports itself. |
|
||||||
| pkg_notok/__init__.py:14:1:14:23 | from pkg_notok import * | The module 'pkg_notok' imports itself. |
|
| pkg_notok/__init__.py:14:1:14:23 | from pkg_notok import * | The module 'pkg_notok' imports itself. |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
| code.py:35:1:35:21 | Function exceptions | 5 |
|
||||||
| code.py:23:1:23:17 | Function nested | 4 |
|
| code.py:23:1:23:17 | Function nested | 4 |
|
||||||
| code.py:12:1:12:21 | Function two_branch | 3 |
|
| code.py:12:1:12:21 | Function two_branch | 3 |
|
||||||
| code.py:6:1:6:18 | Function one_branch | 2 |
|
| code.py:6:1:6:18 | Function one_branch | 2 |
|
||||||
| code.py:35:1:35:21 | Function exceptions | 2 |
|
|
||||||
| code.py:1:1:1:16 | Function f_linear | 1 |
|
| code.py:1:1:1:16 | Function f_linear | 1 |
|
||||||
| code.py:45:1:45:39 | Function must_be_positive | 1 |
|
| code.py:45:1:45:39 | Function must_be_positive | 1 |
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
| test.py:50:1:50:23 | For | This for-loop may attempt to iterate over a $@ of class $@. | test.py:50:10:50:22 | ControlFlowNode for NonIterator() | non-iterable instance | test.py:45:1:45:26 | class NonIterator | NonIterator |
|
| test.py:50:1:50:23 | For | This for-loop may attempt to iterate over a $@ of class $@. | test.py:50:10:50:22 | NonIterator() | non-iterable instance | test.py:45:1:45:26 | Class NonIterator | NonIterator |
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
| test.py:168:9:168:17 | Attribute() | Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement. | test.py:151:1:151:17 | class CM | CM |
|
| test.py:168:9:168:17 | Attribute() | Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement. | test.py:151:1:151:17 | Class CM | CM |
|
||||||
|
|||||||
Reference in New Issue
Block a user