mirror of
https://github.com/github/codeql.git
synced 2026-05-16 04:09:27 +02:00
Compare commits
45 Commits
codeql-cli
...
tausbn/pyt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3277671428 | ||
|
|
10739ec90a | ||
|
|
27bcd490c5 | ||
|
|
39049e3fcc | ||
|
|
7a6777b558 | ||
|
|
9a8fd457f6 | ||
|
|
4c4fd32a5a | ||
|
|
5ba6211ec2 | ||
|
|
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 */
|
||||
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
|
||||
* One callable "this" depends on another callable "result"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import python
|
||||
private import semmle.python.SelfAttribute
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
|
||||
/** The metrics for a 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 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 */
|
||||
|
||||
@@ -32,7 +32,9 @@ module Builtins {
|
||||
"UnicodeDecodeError", "UnicodeEncodeError", "UnicodeError", "UnicodeTranslateError",
|
||||
"UnicodeWarning", "UserWarning", "ValueError", "Warning", "ZeroDivisionError",
|
||||
// Added for compatibility
|
||||
"exec"
|
||||
"exec",
|
||||
// Added by the `site` module (available by default unless `-S` is used)
|
||||
"copyright", "credits", "exit", "quit"
|
||||
]
|
||||
or
|
||||
// Built-in constants shared between Python 2 and 3
|
||||
@@ -51,8 +53,8 @@ module Builtins {
|
||||
or
|
||||
// Python 2 only
|
||||
result in [
|
||||
"basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload", "unichr",
|
||||
"unicode", "xrange"
|
||||
"apply", "basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload",
|
||||
"unichr", "unicode", "xrange"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1977,3 +1977,604 @@ private module OutNodes {
|
||||
* `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 `name` is a globally defined name (a builtin or VM-defined name).
|
||||
*/
|
||||
predicate globallyDefinedName(string name) {
|
||||
exists(API::builtin(name))
|
||||
or
|
||||
name = "WindowsError"
|
||||
or
|
||||
name = "_" and exists(Module m | m.getName() = "gettext")
|
||||
or
|
||||
name in ["__file__", "__builtins__", "__name__"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `name` is monkey-patched into the builtins module.
|
||||
*/
|
||||
predicate monkeyPatchedBuiltin(string name) {
|
||||
any(DataFlow::AttrWrite aw)
|
||||
.writes(API::moduleImport("builtins").getAValueReachableFromSource(), name, _)
|
||||
or
|
||||
// B.__dict__["name"] = value
|
||||
exists(SubscriptNode subscr |
|
||||
subscr.isStore() and
|
||||
subscr.getObject() =
|
||||
API::moduleImport("builtins")
|
||||
.getMember("__dict__")
|
||||
.getAValueReachableFromSource()
|
||||
.asCfgNode() and
|
||||
subscr.getIndex().getNode().(StringLiteral).getText() = name
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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" }
|
||||
}
|
||||
|
||||
/** The builtin `NameError` exception type. */
|
||||
class NameError extends BuiltinExceptType {
|
||||
NameError() { name = "NameError" }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `var` is an SSA variable that is implicitly defined (a builtin,
|
||||
* VM-defined name, or `__path__` in a package init).
|
||||
*/
|
||||
private predicate implicitlyDefined(SsaVariable var) {
|
||||
not exists(var.getDefinition()) and
|
||||
not py_ssa_phi(var, _) and
|
||||
exists(GlobalVariable gv | var.getVariable() = gv |
|
||||
DuckTyping::globallyDefinedName(gv.getId())
|
||||
or
|
||||
gv.getId() = "__path__" and gv.getScope().(Module).isPackageInit()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a phi input of `var`, pruned of unlikely edges.
|
||||
*/
|
||||
private SsaVariable getAPrunedPhiInput(SsaVariable var) {
|
||||
result = var.getAPhiInput() and
|
||||
exists(BasicBlock incoming | incoming = var.getPredecessorBlockForPhiArgument(result) |
|
||||
not unlikelySuccessor(incoming.getLastNode(), var.getDefinition().getBasicBlock().firstNode())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a predecessor block for a phi node, pruned of unlikely edges.
|
||||
*/
|
||||
private BasicBlock getAPrunedPredecessorBlockForPhi(SsaVariable var) {
|
||||
result = var.getAPredecessorBlockForPhi() and
|
||||
not unlikelySuccessor(result.getLastNode(), var.getDefinition().getBasicBlock().firstNode())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the SSA variable `var` may be undefined at some use.
|
||||
*/
|
||||
private predicate ssaMaybeUndefined(SsaVariable var) {
|
||||
// No definition, not a phi, not implicitly defined
|
||||
not exists(var.getDefinition()) and not py_ssa_phi(var, _) and not implicitlyDefined(var)
|
||||
or
|
||||
// Defined by a deletion
|
||||
var.getDefinition().isDelete()
|
||||
or
|
||||
// A phi input may be undefined
|
||||
exists(SsaVariable input | input = getAPrunedPhiInput(var) | ssaMaybeUndefined(input))
|
||||
or
|
||||
// A phi predecessor has no dominating definition
|
||||
exists(BasicBlock incoming |
|
||||
likelyReachable(incoming) and
|
||||
incoming = getAPrunedPredecessorBlockForPhi(var) and
|
||||
not var.getAPhiInput().getDefinition().getBasicBlock().dominates(incoming)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the name `u` may be undefined at its use.
|
||||
*/
|
||||
predicate maybeUndefined(Name u) {
|
||||
exists(SsaVariable var | var.getAUse().getNode() = u | ssaMaybeUndefined(var))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,4 +377,30 @@ module ImportResolution {
|
||||
}
|
||||
|
||||
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
|
||||
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) {
|
||||
t.isNewStyle() and
|
||||
predicate invalid_mro(Class t, Class left, Class right) {
|
||||
DuckTyping::isNewStyle(t) 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)
|
||||
select t,
|
||||
"Construction of class " + t.getName() +
|
||||
|
||||
@@ -11,10 +11,13 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
|
||||
from PropertyObject prop, ClassObject cls
|
||||
where cls.declaredAttribute(_) = prop and not cls.failedInference() and not cls.isNewStyle()
|
||||
from Function prop, Class cls
|
||||
where
|
||||
prop.getScope() = cls and
|
||||
prop.getADecorator().(Name).getId() = "property" and
|
||||
not DuckTyping::isNewStyle(cls)
|
||||
select prop,
|
||||
"Property " + prop.getName() + " will not work properly, as class " + cls.getName() +
|
||||
" is an old-style class."
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
|
||||
from ClassValue c
|
||||
where not c.isBuiltin() and not c.isContextManager() and exists(c.declaredAttribute("__del__"))
|
||||
from Class c
|
||||
where
|
||||
not DuckTyping::isContextManager(c) and
|
||||
DuckTyping::hasMethod(c, "__del__")
|
||||
select c,
|
||||
"Class " + c.getName() +
|
||||
" implements __del__ (presumably to release some resource). Consider making it a context manager."
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
|
||||
from ClassObject c
|
||||
where not c.isNewStyle() and c.declaresAttribute("__slots__") and not c.failedInference()
|
||||
from Class c
|
||||
where
|
||||
not DuckTyping::isNewStyle(c) and
|
||||
DuckTyping::declaresAttribute(c, "__slots__")
|
||||
select c,
|
||||
"Using '__slots__' in an old style class just creates a class attribute called '__slots__'."
|
||||
|
||||
@@ -11,14 +11,13 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
|
||||
predicate uses_of_super_in_old_style_class(Call s) {
|
||||
exists(Function f, ClassObject c |
|
||||
exists(Function f, Class c |
|
||||
s.getScope() = f and
|
||||
f.getScope() = c.getPyClass() and
|
||||
not c.failedInference() and
|
||||
not c.isNewStyle() and
|
||||
f.getScope() = c and
|
||||
not DuckTyping::isNewStyle(c) and
|
||||
s.getFunc().(Name).getId() = "super"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
|
||||
predicate fewer_than_two_public_methods(Class cls, int methods) {
|
||||
(methods = 0 or methods = 1) and
|
||||
@@ -25,13 +25,8 @@ predicate does_not_define_special_method(Class cls) {
|
||||
}
|
||||
|
||||
predicate no_inheritance(Class c) {
|
||||
not exists(ClassValue cls, ClassValue other |
|
||||
cls.getScope() = c and
|
||||
other != ClassValue::object()
|
||||
|
|
||||
other.getABaseType() = cls or
|
||||
cls.getABaseType() = other
|
||||
) and
|
||||
not exists(getADirectSubclass(c)) and
|
||||
not exists(getADirectSuperclass(c)) and
|
||||
not exists(Expr base | base = c.getABase() |
|
||||
not base instanceof Name or base.(Name).getId() != "object"
|
||||
)
|
||||
|
||||
@@ -15,12 +15,34 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import Expressions.CallArgs
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
|
||||
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
|
||||
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,
|
||||
init.getQualifiedName()
|
||||
|
||||
@@ -14,10 +14,60 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import Expressions.CallArgs
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
|
||||
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
|
||||
(
|
||||
too_many_args(call, cls, limit) and
|
||||
@@ -28,6 +78,6 @@ where
|
||||
too = "too few arguments" and
|
||||
should = "no fewer than "
|
||||
) and
|
||||
init = get_function_or_initializer(cls)
|
||||
init = DuckTyping::getInit(cls)
|
||||
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init,
|
||||
init.getQualifiedName()
|
||||
|
||||
@@ -12,20 +12,38 @@
|
||||
*/
|
||||
|
||||
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
|
||||
ex.handledException(t, c, origin) and
|
||||
(
|
||||
exists(ClassValue x | x = t |
|
||||
not x.isLegalExceptionType() and
|
||||
not x.failedInference(_) and
|
||||
what = "class '" + x.getName() + "'"
|
||||
)
|
||||
or
|
||||
not t instanceof ClassValue and
|
||||
what = "instance of '" + c.getName() + "'"
|
||||
exists(ExceptType t | t = illegalHandlerType(ex) |
|
||||
msg =
|
||||
"Non-exception class '" + t.getName() +
|
||||
"' in exception handler which will never match raised exception."
|
||||
)
|
||||
select ex.getNode(),
|
||||
"Non-exception $@ in exception handler which will never match raised exception.", origin, what
|
||||
or
|
||||
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 Raising
|
||||
import Exceptions.NotImplemented
|
||||
private import LegacyPointsTo
|
||||
import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
import semmle.python.ApiGraphs
|
||||
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
|
||||
type_or_typeof(r, t, _) and
|
||||
not t.isLegalExceptionType() and
|
||||
not t.failedInference(_) and
|
||||
not use_of_not_implemented_in_raise(r, _)
|
||||
select r,
|
||||
"Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead."
|
||||
not raisesNonExceptionBuiltin(r, "NotImplemented") and
|
||||
(
|
||||
exists(ExceptType t |
|
||||
t.isRaisedBy(r) and
|
||||
not t.isLegalExceptionType() and
|
||||
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 semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
import semmle.python.ApiGraphs
|
||||
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
|
||||
}
|
||||
}
|
||||
private import ExceptionTypes
|
||||
|
||||
predicate incorrectExceptOrder(ExceptStmt ex1, ExceptType cls1, ExceptStmt ex2, ExceptType cls2) {
|
||||
exists(int i, int j, Try t |
|
||||
|
||||
@@ -12,25 +12,21 @@
|
||||
*/
|
||||
|
||||
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) {
|
||||
exists(Cmpop op, int i | cmp.getOp(i) = op and cmp.getComparator(i) = rhs.getNode() |
|
||||
predicate rhs_in_expr(Expr rhs, Compare cmp) {
|
||||
exists(Cmpop op, int i | cmp.getOp(i) = op and cmp.getComparator(i) = rhs |
|
||||
op instanceof In or op instanceof NotIn
|
||||
)
|
||||
}
|
||||
|
||||
from
|
||||
ControlFlowNodeWithPointsTo non_seq, Compare cmp, Value v, ClassValue cls, ControlFlowNode origin
|
||||
from Compare cmp, DataFlow::LocalSourceNode origin, DataFlow::Node rhs, Class cls
|
||||
where
|
||||
rhs_in_expr(non_seq, cmp) and
|
||||
non_seq.pointsTo(_, v, origin) and
|
||||
v.getClass() = cls and
|
||||
not Types::failedInference(cls, _) and
|
||||
not cls.hasAttribute("__contains__") and
|
||||
not cls.hasAttribute("__iter__") and
|
||||
not cls.hasAttribute("__getitem__") and
|
||||
not cls = ClassValue::nonetype() and
|
||||
not cls = Value::named("types.MappingProxyType")
|
||||
origin = classInstanceTracker(cls) and
|
||||
origin.flowsTo(rhs) and
|
||||
not DuckTyping::isContainer(cls) and
|
||||
not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(cls)) and
|
||||
rhs_in_expr(rhs.asExpr(), cmp)
|
||||
select cmp, "This test may raise an Exception as the $@ may be of non-container class $@.", origin,
|
||||
"target", cls, cls.getName()
|
||||
|
||||
@@ -12,76 +12,97 @@
|
||||
*/
|
||||
|
||||
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.
|
||||
* 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.
|
||||
/**
|
||||
* Holds if `cls` explicitly sets `__hash__` to `None`, making instances unhashable.
|
||||
*/
|
||||
|
||||
predicate numpy_array_type(ClassValue na) {
|
||||
exists(ModuleValue np | np.getName() = "numpy" or np.getName() = "numpy.core" |
|
||||
na.getASuperType() = np.attr("ndarray")
|
||||
)
|
||||
predicate setsHashToNone(Class cls) {
|
||||
DuckTyping::getAnAttributeValue(cls, "__hash__") instanceof None
|
||||
}
|
||||
|
||||
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
|
||||
numpy_array_type(v.getClass())
|
||||
setsHashToNone(cls)
|
||||
}
|
||||
|
||||
predicate explicitly_hashed(ControlFlowNode f) {
|
||||
exists(CallNode c, GlobalVariable hash |
|
||||
c.getArg(0) = f and c.getFunction().(NameNode).uses(hash) and hash.getId() = "hash"
|
||||
/**
|
||||
* Gets the name of a builtin type whose instances are unhashable.
|
||||
*/
|
||||
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) {
|
||||
is_unhashable(f, c, origin) and
|
||||
exists(SubscriptNode sub | sub.getIndex() = f |
|
||||
exists(Value custom_getitem |
|
||||
sub.getObject().(ControlFlowNodeWithPointsTo).pointsTo(custom_getitem) and
|
||||
not has_custom_getitem(custom_getitem)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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")
|
||||
predicate unhashable_subscript(DataFlow::LocalSourceNode origin, DataFlow::Node node, string clsName) {
|
||||
exists(Subscript sub |
|
||||
node = DataFlow::exprNode(sub.getIndex()) and
|
||||
subscriptUsesHashing(sub)
|
||||
|
|
||||
isUnhashable(origin, node, clsName)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `f` is inside a `try` that catches `TypeError`. For example:
|
||||
*
|
||||
* 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.
|
||||
* Holds if `e` is inside a `try` that catches `TypeError`.
|
||||
*/
|
||||
predicate typeerror_is_caught(ControlFlowNode f) {
|
||||
predicate typeerror_is_caught(Expr e) {
|
||||
exists(Try try |
|
||||
try.getBody().contains(f.getNode()) and
|
||||
try.getAHandler().getType().(ExprWithPointsTo).pointsTo(ClassValue::typeError())
|
||||
try.getBody().contains(e) and
|
||||
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
|
||||
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
|
||||
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
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.types.Builtins
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
from CallNode call, ControlFlowNodeWithPointsTo func
|
||||
where major_version() = 2 and call.getFunction() = func and func.pointsTo(Value::named("apply"))
|
||||
from CallNode call
|
||||
where
|
||||
major_version() = 2 and
|
||||
call = API::builtin("apply").getACall().asCfgNode()
|
||||
select call, "Call to the obsolete builtin function 'apply'."
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
|
||||
predicate explicitly_returns_non_none(Function func) {
|
||||
exists(Return return |
|
||||
@@ -22,8 +22,8 @@ predicate explicitly_returns_non_none(Function func) {
|
||||
}
|
||||
|
||||
predicate has_implicit_return(Function func) {
|
||||
exists(ControlFlowNodeWithPointsTo fallthru |
|
||||
fallthru = func.getFallthroughNode() and not fallthru.unlikelyReachable()
|
||||
exists(ControlFlowNode fallthru |
|
||||
fallthru = func.getFallthroughNode() and not Reachability::unlikelyReachable(fallthru)
|
||||
)
|
||||
or
|
||||
exists(Return return | return.getScope() = func and not exists(return.getValue()))
|
||||
|
||||
@@ -10,16 +10,17 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
|
||||
predicate slice_method_name(string name) {
|
||||
name = "__getslice__" or name = "__setslice__" or name = "__delslice__"
|
||||
}
|
||||
|
||||
from PythonFunctionValue f, string meth
|
||||
from Function f, string meth
|
||||
where
|
||||
f.getScope().isMethod() and
|
||||
not f.isOverridingMethod() and
|
||||
f.isMethod() 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."
|
||||
|
||||
@@ -12,12 +12,11 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
import semmle.python.Metrics
|
||||
|
||||
from FunctionValue method
|
||||
from FunctionMetrics method
|
||||
where
|
||||
exists(ClassValue c |
|
||||
c.declaredAttribute("__del__") = method and
|
||||
method.getScope().(FunctionMetricsWithPointsTo).getCyclomaticComplexity() > 3
|
||||
)
|
||||
method.getName() = "__del__" and
|
||||
method.isMethod() and
|
||||
method.getCyclomaticComplexity() > 3
|
||||
select method, "Overly complex '__del__' method."
|
||||
|
||||
@@ -12,16 +12,16 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import Cyclic
|
||||
private import LegacyPointsTo
|
||||
import CyclicImports
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
|
||||
from ModuleValue m1, ModuleValue m2, Stmt imp
|
||||
from Module m1, Module m2, Stmt imp
|
||||
where
|
||||
imp.getEnclosingModule() = m1.getScope() and
|
||||
imp.getEnclosingModule() = m1 and
|
||||
stmt_imports(imp) = m2 and
|
||||
circular_import(m1, m2) and
|
||||
m1 != m2 and
|
||||
// this query finds all cyclic imports that are *not* flagged by ModuleLevelCyclicImport
|
||||
not failing_import_due_to_cycle(m2, m1, _, _, _, _) and
|
||||
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
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Holds if the module `name` was deprecated in Python version `major`.`minor`,
|
||||
@@ -80,7 +80,7 @@ where
|
||||
name = imp.getName() and
|
||||
deprecated_module(name, instead, _, _) and
|
||||
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)
|
||||
)
|
||||
select imp, deprecation_message(name) + replacement_message(name)
|
||||
|
||||
@@ -12,24 +12,26 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
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
|
||||
m.importedAs(im.getModule().(ImportExpr).getImportedModuleName()) and
|
||||
ImportResolution::getImmediateModuleReference(m).asExpr() = im.getModule() and
|
||||
im.getName() = name and
|
||||
/* 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 */
|
||||
not im.getScope() instanceof Function and
|
||||
store_attr.isStore() and
|
||||
store_attr.getObject(name).(ControlFlowNodeWithPointsTo).pointsTo(m) and
|
||||
store_attr.getAttributeName() = name and
|
||||
ImportResolution::getModuleReference(m) = store_attr.getObject() and
|
||||
/* 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 */
|
||||
not store_attr.getScope().getScope*() instanceof TestScope
|
||||
not store_attr.getObject().getScope().getScope*() instanceof TestScope
|
||||
select im,
|
||||
"Importing the value of '" + name +
|
||||
"' 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
|
||||
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) {
|
||||
i.getEnclosingModule() = m.getScope() and
|
||||
m =
|
||||
max(string s, ModuleValue m_ |
|
||||
s = i.getAnImportedModuleName() and
|
||||
m_.importedAs(s)
|
||||
|
|
||||
m_ order by s.length()
|
||||
)
|
||||
predicate modules_imports_itself(ImportingStmt i, Module m) {
|
||||
m = i.getEnclosingModule() and
|
||||
ImportResolution::importedBy(i, m) and
|
||||
// Exclude `from m import submodule` where the imported member is a submodule of m
|
||||
not exists(ImportMember im | im = i.(Import).getAName().getValue() |
|
||||
ImportResolution::getImmediateModuleReference(m).asExpr() = im.getModule() and
|
||||
ImportResolution::importedBy(i, any(Module sub | sub != m))
|
||||
)
|
||||
}
|
||||
|
||||
from ImportingStmt i, ModuleValue m
|
||||
from ImportingStmt i, Module 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 Cyclic
|
||||
private import LegacyPointsTo
|
||||
import CyclicImports
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
|
||||
// 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)
|
||||
@@ -22,10 +22,11 @@ private import LegacyPointsTo
|
||||
// 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'
|
||||
// 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)
|
||||
select use,
|
||||
"'" + 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:
|
||||
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
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
private import semmle.python.types.ImportTime
|
||||
|
||||
predicate import_star(ImportStar imp, ModuleValue exporter) {
|
||||
exporter.importedAs(imp.getImportedModuleName())
|
||||
predicate all_defined(Module exporter) {
|
||||
exporter.(ImportTimeScope).definesName("__all__")
|
||||
or
|
||||
exporter.getInitModule().(ImportTimeScope).definesName("__all__")
|
||||
}
|
||||
|
||||
predicate all_defined(ModuleValue exporter) {
|
||||
exporter.isBuiltin()
|
||||
or
|
||||
exporter.getScope().(ImportTimeScope).definesName("__all__")
|
||||
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()
|
||||
from ImportStar imp, Module exporter
|
||||
where
|
||||
exporter = ImportResolution::getModuleImportedByImportStar(imp) and
|
||||
not all_defined(exporter)
|
||||
select imp,
|
||||
"Import pollutes the enclosing namespace, as the imported module $@ does not define '__all__'.",
|
||||
exporter, exporter.getName()
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
import Variables.Definition
|
||||
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) {
|
||||
exists(Alias a, API::Node pytest_fixture, API::Node decorator |
|
||||
@@ -95,7 +96,7 @@ private string typehint_annotation_in_module(Module module_scope) {
|
||||
or
|
||||
annotation = any(FunctionExpr f).getReturns().getASubExpression*()
|
||||
|
|
||||
annotation.(ExprWithPointsTo).pointsTo(Value::forString(result)) and
|
||||
annotation.getText() = result and
|
||||
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_alias_used_in_typehint(imp, name) and
|
||||
not is_pytest_fixture(imp, name) and
|
||||
// Only consider import statements that actually point-to something (possibly an unknown module).
|
||||
// If this is not the case, it's likely that the import statement never gets executed.
|
||||
imp.getAName().getValue().(ExprWithPointsTo).pointsTo(_)
|
||||
// Only consider import statements in reachable code.
|
||||
Reachability::likelyReachable(imp.getAName().getValue().getAFlowNode().getBasicBlock())
|
||||
}
|
||||
|
||||
from Stmt s, Variable name
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
import semmle.python.Metrics
|
||||
|
||||
from FunctionMetricsWithPointsTo func, int complexity
|
||||
from FunctionMetrics func, int complexity
|
||||
where complexity = func.getCyclomaticComplexity()
|
||||
select func, complexity order by complexity desc
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
|
||||
from ModuleValue m, int n
|
||||
where n = count(ModuleValue imp | imp = m.getAnImportedModule())
|
||||
select m.getScope(), n
|
||||
from Module m, int n
|
||||
where n = count(Module imp | ImportResolution::imports(m, imp))
|
||||
select m, n
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
|
||||
from ModuleValue m, int n
|
||||
where n = count(ModuleValue imp | imp = m.getAnImportedModule+() and imp != m)
|
||||
select m.getScope(), n
|
||||
from Module m, int n
|
||||
where n = count(Module imp | ImportResolution::imports+(m, imp) and imp != m)
|
||||
select m, n
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
|
||||
predicate needs_docstring(Scope s) {
|
||||
s.isPublic() and
|
||||
@@ -29,15 +29,15 @@ predicate needs_docstring(Scope s) {
|
||||
}
|
||||
|
||||
predicate function_needs_docstring(FunctionMetrics f) {
|
||||
not exists(FunctionValue fo, FunctionValue base | fo.overrides(base) and fo.getScope() = f |
|
||||
not function_needs_docstring(base.getScope())
|
||||
not exists(Function base |
|
||||
DuckTyping::overridesMethod(f) and
|
||||
base.getScope() = getADirectSuperclass+(f.getScope()) and
|
||||
base.getName() = f.getName() and
|
||||
not function_needs_docstring(base)
|
||||
) and
|
||||
f.getName() != "lambda" and
|
||||
(f.getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 and
|
||||
not exists(PythonPropertyObject p |
|
||||
p.getGetter().getFunction() = f or
|
||||
p.getSetter().getFunction() = f
|
||||
)
|
||||
not DuckTyping::isPropertyAccessor(f)
|
||||
}
|
||||
|
||||
string scope_type(Scope s) {
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
predicate originIsLocals(ControlFlowNodeWithPointsTo n) {
|
||||
n.pointsTo(_, _, Value::named("locals").getACall())
|
||||
predicate originIsLocals(ControlFlowNode n) {
|
||||
API::builtin("locals").getReturn().getAValueReachableFromSource().asCfgNode() = n
|
||||
}
|
||||
|
||||
predicate modification_of_locals(ControlFlowNode f) {
|
||||
@@ -37,5 +37,5 @@ where
|
||||
// in module level scope `locals() == globals()`
|
||||
// see https://docs.python.org/3/library/functions.html#locals
|
||||
// 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."
|
||||
|
||||
@@ -12,16 +12,15 @@
|
||||
*/
|
||||
|
||||
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
|
||||
loop.getIter().getAFlowNode() = iter and
|
||||
iter.pointsTo(_, v, origin) and
|
||||
v.getClass() = t and
|
||||
not t.isIterable() and
|
||||
not t.failedInference(_) and
|
||||
not v = Value::named("None") and
|
||||
not t.isDescriptorType()
|
||||
select loop, "This for-loop may attempt to iterate over a $@ of class $@.", origin,
|
||||
"non-iterable instance", t, t.getName()
|
||||
iter = loop.getIter() and
|
||||
classInstanceTracker(cls).asExpr() = iter and
|
||||
not DuckTyping::isIterable(cls) and
|
||||
not DuckTyping::isDescriptor(cls) and
|
||||
not (loop.isAsync() and DuckTyping::hasMethod(cls, "__aiter__")) and
|
||||
not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(cls))
|
||||
select loop, "This for-loop may attempt to iterate over a $@ of class $@.", iter,
|
||||
"non-iterable instance", cls, cls.getName()
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
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") }
|
||||
|
||||
@@ -23,18 +23,12 @@ predicate only_stmt_in_finally(Try t, Call c) {
|
||||
)
|
||||
}
|
||||
|
||||
predicate points_to_context_manager(ControlFlowNodeWithPointsTo f, ClassValue cls) {
|
||||
forex(Value v | f.pointsTo(v) | v.getClass() = cls) and
|
||||
cls.isContextManager()
|
||||
}
|
||||
|
||||
from Call close, Try t, ClassValue cls
|
||||
from Call close, Try t, Class cls
|
||||
where
|
||||
only_stmt_in_finally(t, close) and
|
||||
calls_close(close) and
|
||||
exists(ControlFlowNode f | f = close.getFunc().getAFlowNode().(AttrNode).getObject() |
|
||||
points_to_context_manager(f, cls)
|
||||
)
|
||||
classInstanceTracker(cls).asExpr() = close.getFunc().(Attribute).getObject() and
|
||||
DuckTyping::isContextManager(cls)
|
||||
select close,
|
||||
"Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement.",
|
||||
cls, cls.getName()
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
predicate func_with_side_effects(Expr e) {
|
||||
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) {
|
||||
e.getAFlowNode() = Value::named("subprocess.call").getACall()
|
||||
or
|
||||
e.getAFlowNode() = Value::named("subprocess.check_call").getACall()
|
||||
or
|
||||
e.getAFlowNode() = Value::named("subprocess.check_output").getACall()
|
||||
e.getAFlowNode() =
|
||||
API::moduleImport("subprocess")
|
||||
.getMember(["call", "check_call", "check_output"])
|
||||
.getACall()
|
||||
.asCfgNode()
|
||||
}
|
||||
|
||||
predicate probable_side_effect(Expr e) {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
predicate main_eq_name(If i) {
|
||||
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
|
||||
where
|
||||
is_print_stmt(p) and
|
||||
// TODO: Need to discuss how we would like to handle ModuleObject.getKind in the glorious future
|
||||
exists(ModuleValue m | m.getScope() = p.getScope() and m.isUsedAsModule()) and
|
||||
is_used_as_module(p.getScope()) and
|
||||
not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p)
|
||||
select p, "Print statement may execute during import."
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
predicate isInsideLoop(AstNode node) {
|
||||
node.getParentNode() instanceof While
|
||||
@@ -33,9 +33,9 @@ where
|
||||
not isInsideLoop(del) and
|
||||
// False positive: calling `sys.exc_info` within a function results in a
|
||||
// reference cycle, and an explicit call to `del` helps break this cycle.
|
||||
not exists(FunctionValue ex |
|
||||
ex = Value::named("sys.exc_info") and
|
||||
ex.getACall().getScope() = f
|
||||
not exists(API::CallNode call |
|
||||
call = API::moduleImport("sys").getMember("exc_info").getACall() and
|
||||
call.getScope() = f
|
||||
)
|
||||
select del, "Unnecessary deletion of local variable $@ in function $@.", e, e.toString(), f,
|
||||
f.getName()
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
predicate typing_import(ImportingStmt is) {
|
||||
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` */
|
||||
predicate suppression_in_scope(Stmt s) {
|
||||
exists(With w |
|
||||
w.getContextExpr()
|
||||
.(Call)
|
||||
.getFunc()
|
||||
.(ExprWithPointsTo)
|
||||
.pointsTo(Value::named("contextlib.suppress")) and
|
||||
w.getContextExpr() = API::moduleImport("contextlib").getMember("suppress").getACall().asExpr() and
|
||||
w.getScope() = s.getScope()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,11 +12,49 @@
|
||||
*/
|
||||
|
||||
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
|
||||
call.getFunc().(ExprWithPointsTo).pointsTo(ex) and
|
||||
ex.getASuperType() = ClassValue::exception() and
|
||||
isExceptionInstantiation(call) and
|
||||
exists(ExprStmt s | s.getValue() = call)
|
||||
select call, "Instantiating an exception, but not raising it, has no effect."
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
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,
|
||||
"The '" + name +
|
||||
"' site.Quitter object may not exist if the 'site' module is not loaded or is modified."
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
private import semmle.python.ApiGraphs
|
||||
import Shadowing
|
||||
import semmle.python.types.Builtins
|
||||
|
||||
predicate shadows(Name d, GlobalVariable g, Function scope, int line) {
|
||||
g.getScope() = scope.getScope() and
|
||||
@@ -28,34 +28,19 @@ predicate shadows(Name d, GlobalVariable g, Function scope, int line) {
|
||||
) and
|
||||
not exists(Import il, Import ig, Name gd | il.contains(d) and gd.defines(g) and ig.contains(gd)) and
|
||||
not exists(Assign a | a.getATarget() = d and a.getValue() = g.getAnAccess()) and
|
||||
not exists(Builtin::builtin(g.getId())) and
|
||||
not DuckTyping::globallyDefinedName(g.getId()) and
|
||||
d.getLocation().getStartLine() = line and
|
||||
exists(Name defn | defn.defines(g) | not exists(If i | i.isNameEqMain() | i.contains(defn))) and
|
||||
not optimizing_parameter(d)
|
||||
}
|
||||
|
||||
/* pytest dynamically populates its namespace so, we cannot look directly for the pytest.fixture function */
|
||||
AttrNode pytest_fixture_attr() {
|
||||
exists(ModuleValue pytest |
|
||||
result.getObject("fixture").(ControlFlowNodeWithPointsTo).pointsTo(pytest)
|
||||
)
|
||||
}
|
||||
|
||||
Value pytest_fixture() {
|
||||
exists(CallNode call |
|
||||
call.getFunction() = pytest_fixture_attr()
|
||||
or
|
||||
call.getFunction().(CallNode).getFunction() = pytest_fixture_attr()
|
||||
|
|
||||
call.(ControlFlowNodeWithPointsTo).pointsTo(result)
|
||||
)
|
||||
}
|
||||
|
||||
/* pytest fixtures require that the parameter name is also a global */
|
||||
predicate assigned_pytest_fixture(GlobalVariable v) {
|
||||
exists(NameNode def |
|
||||
exists(NameNode def, API::Node fixture |
|
||||
fixture = API::moduleImport("pytest").getMember("fixture") and
|
||||
def.defines(v) and
|
||||
def.(DefinitionNode).getValue().(ControlFlowNodeWithPointsTo).pointsTo(pytest_fixture())
|
||||
def.(DefinitionNode).getValue() =
|
||||
[fixture.getACall(), fixture.getReturn().getACall()].asCfgNode()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.ApiGraphs
|
||||
import Definition
|
||||
|
||||
predicate is_increment(Stmt s) {
|
||||
@@ -41,23 +41,16 @@ predicate one_item_only(For f) {
|
||||
)
|
||||
}
|
||||
|
||||
predicate points_to_call_to_range(ControlFlowNode f) {
|
||||
/* (x)range is a function in Py2 and a class in Py3, so we must treat it as a plain object */
|
||||
exists(Value range |
|
||||
range = Value::named("range") or
|
||||
range = Value::named("xrange")
|
||||
|
|
||||
f = range.getACall()
|
||||
)
|
||||
/** Holds if `node` is a call to `range`, `xrange`, or `list(range(...))`. */
|
||||
predicate call_to_range(DataFlow::Node node) {
|
||||
node = API::builtin(["range", "xrange"]).getACall()
|
||||
or
|
||||
/* In case points-to fails due to 'from six.moves import range' or similar. */
|
||||
exists(string range | f.getNode().(Call).getFunc().(Name).getId() = range |
|
||||
range = "range" or range = "xrange"
|
||||
)
|
||||
/* Handle 'from six.moves import range' or similar. */
|
||||
node = API::moduleImport("six").getMember("moves").getMember(["range", "xrange"]).getACall()
|
||||
or
|
||||
/* Handle list(range(...)) and list(list(range(...))) */
|
||||
f.(CallNode).(ControlFlowNodeWithPointsTo).pointsTo().getClass() = ClassValue::list() and
|
||||
points_to_call_to_range(f.(CallNode).getArg(0))
|
||||
node = API::builtin("list").getACall() and
|
||||
call_to_range(node.(DataFlow::CallCfgNode).getArg(0))
|
||||
}
|
||||
|
||||
/** 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
|
||||
f.getTarget() = v.getAnAccess() and
|
||||
not f.getAStmt().contains(v.getAnAccess()) and
|
||||
not points_to_call_to_range(f.getIter().getAFlowNode()) and
|
||||
not points_to_call_to_range(get_comp_iterable(f)) and
|
||||
not call_to_range(DataFlow::exprNode(f.getIter())) and
|
||||
not call_to_range(DataFlow::exprNode(get_comp_iterable(f).getNode())) and
|
||||
not name_acceptable_for_unused_variable(v) and
|
||||
not f.getScope().getName() = "genexpr" and
|
||||
not empty_loop(f) and
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/** Whether name is declared in the __all__ list of this module */
|
||||
predicate declaredInAll(Module m, StringLiteral name) {
|
||||
@@ -25,62 +27,42 @@ predicate declaredInAll(Module m, StringLiteral name) {
|
||||
)
|
||||
}
|
||||
|
||||
predicate mutates_globals(ModuleValue m) {
|
||||
predicate mutates_globals(Module m) {
|
||||
exists(CallNode globals |
|
||||
globals = Value::named("globals").(FunctionValue).getACall() and
|
||||
globals.getScope() = m.getScope()
|
||||
globals = API::builtin("globals").getACall().asCfgNode() and
|
||||
globals.getScope() = m
|
||||
|
|
||||
exists(AttrNode attr | attr.getObject() = globals)
|
||||
or
|
||||
exists(SubscriptNode sub | sub.getObject() = globals and sub.isStore())
|
||||
)
|
||||
or
|
||||
// Enum (added in 3.4) has method `_convert_` that alters globals
|
||||
// This was called `_convert` until 3.8, but that name will be removed in 3.9
|
||||
exists(ClassValue enum_class |
|
||||
enum_class.getASuperType() = Value::named("enum.Enum") and
|
||||
(
|
||||
// In Python < 3.8, Enum._convert can be found with points-to
|
||||
exists(Value enum_convert |
|
||||
enum_convert = enum_class.attr("_convert") and
|
||||
exists(CallNode call | call.getScope() = m.getScope() |
|
||||
enum_convert.getACall() = call or
|
||||
call.getFunction().(ControlFlowNodeWithPointsTo).pointsTo(enum_convert)
|
||||
)
|
||||
)
|
||||
or
|
||||
// In Python 3.8, Enum._convert_ is implemented using a metaclass, and our points-to
|
||||
// analysis doesn't handle that well enough. So we need a special case for this
|
||||
not exists(enum_class.attr("_convert")) and
|
||||
exists(CallNode call | call.getScope() = m.getScope() |
|
||||
call.getFunction()
|
||||
.(AttrNode)
|
||||
.getObject(["_convert", "_convert_"])
|
||||
.(ControlFlowNodeWithPointsTo)
|
||||
.pointsTo() = enum_class
|
||||
)
|
||||
)
|
||||
// Enum._convert_ is a metaclass method that alters the module's globals.
|
||||
// It was called `_convert` until Python 3.8, when it was renamed to `_convert_`.
|
||||
API::moduleImport("enum")
|
||||
.getMember("Enum")
|
||||
.getASubclass*()
|
||||
.getMember(["_convert", "_convert_"])
|
||||
.getACall()
|
||||
.getScope() = m
|
||||
}
|
||||
|
||||
predicate is_exported_submodule_name(Module m, string exported_name) {
|
||||
m.getShortName() = "__init__" and
|
||||
exists(m.getPackage().getSubModule(exported_name))
|
||||
}
|
||||
|
||||
predicate contains_unknown_import_star(Module m) {
|
||||
exists(ImportStar imp | imp.getScope() = m |
|
||||
not exists(ImportResolution::getModuleImportedByImportStar(imp))
|
||||
)
|
||||
}
|
||||
|
||||
predicate is_exported_submodule_name(ModuleValue m, string exported_name) {
|
||||
m.getScope().getShortName() = "__init__" and
|
||||
exists(m.getScope().getPackage().getSubModule(exported_name))
|
||||
}
|
||||
|
||||
predicate contains_unknown_import_star(ModuleValue m) {
|
||||
exists(ImportStarNode imp | imp.getEnclosingModule() = m.getScope() |
|
||||
imp.getModule().(ControlFlowNodeWithPointsTo).pointsTo().isAbsent()
|
||||
or
|
||||
not exists(imp.getModule().(ControlFlowNodeWithPointsTo).pointsTo())
|
||||
)
|
||||
}
|
||||
|
||||
from ModuleValue m, StringLiteral name, string exported_name
|
||||
from Module m, StringLiteral name, string exported_name
|
||||
where
|
||||
declaredInAll(m.getScope(), name) and
|
||||
declaredInAll(m, name) and
|
||||
exported_name = name.getText() and
|
||||
not m.hasAttribute(exported_name) and
|
||||
not ImportResolution::module_export(m, exported_name, _) and
|
||||
not is_exported_submodule_name(m, exported_name) and
|
||||
not contains_unknown_import_star(m) and
|
||||
not mutates_globals(m)
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.types.ImportTime
|
||||
import Variables.MonkeyPatched
|
||||
import Loop
|
||||
|
||||
predicate guarded_against_name_error(Name u) {
|
||||
@@ -32,10 +33,13 @@ predicate guarded_against_name_error(Name u) {
|
||||
}
|
||||
|
||||
predicate contains_unknown_import_star(Module m) {
|
||||
exists(ImportStar imp | imp.getScope() = m |
|
||||
exists(ModuleValue imported | imported.importedAs(imp.getImportedModuleName()) |
|
||||
not imported.hasCompleteExportInfo()
|
||||
)
|
||||
exists(ImportStar imp, Module imported |
|
||||
imp.getScope() = m and
|
||||
ImportResolution::getModuleImportedByImportStar(imp) = imported
|
||||
|
|
||||
// The imported module dynamically creates attributes, so we can't
|
||||
// enumerate its exports.
|
||||
exists(Function f | f.getName() = "__getattr__" and f.getScope() = imported)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -60,9 +64,9 @@ predicate undefined_use_in_function(Name u) {
|
||||
)
|
||||
) and
|
||||
not u.getEnclosingModule().(ImportTimeScope).definesName(u.getId()) and
|
||||
not exists(ModuleValue m | m.getScope() = u.getEnclosingModule() | m.hasAttribute(u.getId())) and
|
||||
not globallyDefinedName(u.getId()) and
|
||||
not exists(SsaVariableWithPointsTo var | var.getAUse().getNode() = u and not var.maybeUndefined()) and
|
||||
not ImportResolution::module_export(u.getEnclosingModule(), u.getId(), _) and
|
||||
not DuckTyping::globallyDefinedName(u.getId()) and
|
||||
not Reachability::maybeUndefined(u) and
|
||||
not guarded_against_name_error(u) and
|
||||
not (u.getEnclosingModule().isPackageInit() and u.getId() = "__path__")
|
||||
}
|
||||
@@ -70,20 +74,18 @@ predicate undefined_use_in_function(Name u) {
|
||||
predicate undefined_use_in_class_or_module(Name u) {
|
||||
exists(GlobalVariable v | u.uses(v)) and
|
||||
not u.getScope().getScope*() instanceof Function and
|
||||
exists(SsaVariableWithPointsTo var | var.getAUse().getNode() = u | var.maybeUndefined()) and
|
||||
Reachability::maybeUndefined(u) and
|
||||
not guarded_against_name_error(u) and
|
||||
not exists(ModuleValue m | m.getScope() = u.getEnclosingModule() | m.hasAttribute(u.getId())) and
|
||||
not u.getEnclosingModule().(ImportTimeScope).definesName(u.getId()) and
|
||||
not ImportResolution::module_export(u.getEnclosingModule(), u.getId(), _) and
|
||||
not (u.getEnclosingModule().isPackageInit() and u.getId() = "__path__") and
|
||||
not globallyDefinedName(u.getId())
|
||||
not DuckTyping::globallyDefinedName(u.getId())
|
||||
}
|
||||
|
||||
predicate use_of_exec(Module m) {
|
||||
exists(Exec exec | exec.getScope() = m)
|
||||
or
|
||||
exists(CallNode call, FunctionValue exec | exec.getACall() = call and call.getScope() = m |
|
||||
exec = Value::named("exec") or
|
||||
exec = Value::named("execfile")
|
||||
)
|
||||
API::builtin(["exec", "execfile"]).getACall().getScope() = m
|
||||
}
|
||||
|
||||
predicate undefined_use(Name u) {
|
||||
@@ -92,11 +94,10 @@ predicate undefined_use(Name u) {
|
||||
or
|
||||
undefined_use_in_function(u)
|
||||
) and
|
||||
not monkey_patched_builtin(u.getId()) and
|
||||
not DuckTyping::monkeyPatchedBuiltin(u.getId()) and
|
||||
not contains_unknown_import_star(u.getEnclosingModule()) and
|
||||
not use_of_exec(u.getEnclosingModule()) and
|
||||
not exists(u.getVariable().getAStore()) and
|
||||
not u.(ExprWithPointsTo).pointsTo(_) and
|
||||
not probably_defined_in_loop(u)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,17 +12,23 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import Variables.MonkeyPatched
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
private import semmle.python.types.ImportTime
|
||||
|
||||
/* Global Stuff */
|
||||
predicate not_a_global(PlaceHolder use) {
|
||||
not ImportResolution::module_export(use.getEnclosingModule(), use.getId(), _) and
|
||||
not DuckTyping::globallyDefinedName(use.getId()) and
|
||||
not DuckTyping::monkeyPatchedBuiltin(use.getId())
|
||||
}
|
||||
|
||||
/* Local variable part */
|
||||
predicate initialized_as_local(PlaceHolder use) {
|
||||
exists(SsaVariableWithPointsTo l, Function f |
|
||||
f = use.getScope() and l.getAUse() = use.getAFlowNode()
|
||||
|
|
||||
exists(SsaVariable l, Function f | f = use.getScope() and l.getAUse() = use.getAFlowNode() |
|
||||
l.getVariable() instanceof LocalVariable and
|
||||
not l.maybeUndefined()
|
||||
exists(l.getDefinition()) and
|
||||
not l.getDefinition().isDelete()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,16 +39,6 @@ predicate template_attribute(PlaceHolder use) {
|
||||
exists(ImportTimeScope cls | cls = enclosing_class(use) | cls.definesName(use.getId()))
|
||||
}
|
||||
|
||||
/* Global Stuff */
|
||||
predicate not_a_global(PlaceHolder use) {
|
||||
not exists(PythonModuleObject mo |
|
||||
mo.hasAttribute(use.getId()) and mo.getModule() = use.getEnclosingModule()
|
||||
) and
|
||||
not globallyDefinedName(use.getId()) and
|
||||
not monkey_patched_builtin(use.getId()) and
|
||||
not globallyDefinedName(use.getId())
|
||||
}
|
||||
|
||||
from PlaceHolder p
|
||||
where
|
||||
not initialized_as_local(p) and
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
import Undefined
|
||||
|
||||
predicate uninitialized_local(NameNode use) {
|
||||
@@ -21,16 +21,16 @@ predicate uninitialized_local(NameNode use) {
|
||||
) and
|
||||
(
|
||||
any(Uninitialized uninit).taints(use) and
|
||||
PointsToInternal::reachableBlock(use.getBasicBlock(), _)
|
||||
Reachability::likelyReachable(use.getBasicBlock())
|
||||
or
|
||||
not exists(EssaVariable var | var.getASourceUse() = use)
|
||||
)
|
||||
}
|
||||
|
||||
predicate explicitly_guarded(NameNode u) {
|
||||
exists(Try t |
|
||||
exists(Try t, ExceptionTypes::NameError nameError |
|
||||
t.getBody().contains(u.getNode()) and
|
||||
t.getAHandler().getType().(ExprWithPointsTo).pointsTo(ClassValue::nameError())
|
||||
nameError.getAUse().asExpr() = t.getAHandler().getType()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,21 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
import Definition
|
||||
|
||||
/**
|
||||
* Whether `name` is exported from module `m`, following Python's convention:
|
||||
* if `__all__` is defined, use its entries; otherwise, export public names (not starting with `_`).
|
||||
*/
|
||||
private predicate isExported(Module m, string name) {
|
||||
py_exports(m, name)
|
||||
or
|
||||
not py_exports(m, _) and
|
||||
ImportResolution::module_export(m, name, _) and
|
||||
not name.charAt(0) = "_"
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the module contains an __all__ definition,
|
||||
* but it is more complex than a simple list of strings
|
||||
@@ -59,7 +71,7 @@ predicate unused_global(Name unused, GlobalVariable v) {
|
||||
// indirectly
|
||||
defn.getBasicBlock().reachesExit() and u.getScope() != unused.getScope()
|
||||
) and
|
||||
not unused.getEnclosingModule().(ModuleWithPointsTo).getAnExport() = v.getId() and
|
||||
not isExported(unused.getEnclosingModule(), v.getId()) and
|
||||
not exists(unused.getParentNode().(ClassDef).getDefinedClass().getADecorator()) and
|
||||
not exists(unused.getParentNode().(FunctionDef).getDefinedFunction().getADecorator()) and
|
||||
unused.defines(v) and
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
|
||||
import python
|
||||
import Definition
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
|
||||
predicate unused_parameter(FunctionValue f, LocalVariable v) {
|
||||
predicate unused_parameter(Function f, LocalVariable v) {
|
||||
v.isParameter() and
|
||||
v.getScope() = f.getScope() and
|
||||
v.getScope() = f and
|
||||
not name_acceptable_for_unused_variable(v) and
|
||||
not exists(NameNode u | u.uses(v)) and
|
||||
not exists(Name inner, LocalVariable iv |
|
||||
@@ -26,15 +26,19 @@ predicate unused_parameter(FunctionValue f, LocalVariable v) {
|
||||
)
|
||||
}
|
||||
|
||||
predicate is_abstract(FunctionValue func) {
|
||||
func.getScope().getADecorator().(Name).getId().matches("%abstract%")
|
||||
predicate is_abstract(Function func) { func.getADecorator().(Name).getId().matches("%abstract%") }
|
||||
|
||||
predicate is_overridden(Function f) {
|
||||
exists(Class cls | f.getScope() = cls |
|
||||
DuckTyping::hasMethod(getADirectSubclass(cls), f.getName())
|
||||
)
|
||||
}
|
||||
|
||||
from PythonFunctionValue f, LocalVariable v
|
||||
from Function f, LocalVariable v
|
||||
where
|
||||
v.getId() != "self" and
|
||||
unused_parameter(f, v) and
|
||||
not f.isOverridingMethod() and
|
||||
not f.isOverriddenMethod() and
|
||||
not DuckTyping::overridesMethod(f) and
|
||||
not is_overridden(f) and
|
||||
not is_abstract(f)
|
||||
select f, "The parameter '" + v.getId() + "' is never used."
|
||||
|
||||
@@ -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: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 |
|
||||
| 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,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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
@@ -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: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: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: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: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 __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 __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 __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: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: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: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: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: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: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: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: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: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: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: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: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: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__ |
|
||||
| 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 __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 __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 __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 __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 __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 __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 __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 __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 __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 __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 __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__ |
|
||||
|
||||
@@ -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: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: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. |
|
||||
|
||||
@@ -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: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: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 $@ in exception handler which will never match raised exception. | pypy_test.py:14:12:14:13 | ControlFlowNode for IntegerLiteral | instance of 'int' |
|
||||
| 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 class 'NotException2' in exception handler which will never match raised exception. |
|
||||
| pypy_test.py:14:5:14:14 | ExceptStmt | Non-exception class 'int' in exception handler which will never match raised exception. |
|
||||
|
||||
@@ -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: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: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 |
|
||||
|
||||
@@ -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:98:5:98:47 | Function DeprecatedSliceMethods.__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:95:5:95:40 | Function __getslice__ | __getslice__ 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 __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. |
|
||||
| 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: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. |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
| code.py:35:1:35:21 | Function exceptions | 5 |
|
||||
| code.py:23:1:23:17 | Function nested | 4 |
|
||||
| code.py:12:1:12:21 | Function two_branch | 3 |
|
||||
| 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: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