Compare commits

...

37 Commits

Author SHA1 Message Date
Taus
28eec77cd8 Python: Port UnusedImport.ql
Changes the "has points-to value" check into a "is reachable" check
instead. No test changes.
2026-03-09 17:22:07 +00:00
Taus
09fc9f0bf2 Python: Port UnintentionalImport.ql
No test changes.
2026-03-09 17:22:07 +00:00
Taus
330dba6ed7 Python: Port FromImportOfMutableAttribute.ql
A fairly straightforward port. No test changes.
2026-03-09 17:22:07 +00:00
Taus
6b64443c49 Python: Port cyclic import queries
The new CyclicImports.qll is a fairly straight port of Cyclic.qll, with
the main changes being:

- We now use Module instead of ModuleValue everywhere
- We use getModuleReference instead of pointsTo
- is_import_time was replaced with a use of `ImportTimeScope`

The predicate that changed the most is `stmt_imports`, which in the
original just did `s.getASubExpression().pointsTo(result)`. The new
version has three branches, one for each kind of import, and with
special handling of imports from within a submodule (which is not
something that should be flagged).

No test changes.
2026-03-09 17:22:07 +00:00
Taus
970349bc1f Python: Extend reachability analysis with common guards
Adds `if False: ...` and `if typing.TYPE_CHECKING: ...` to the set of
nodes that are unlikely to be reachable.
2026-03-09 17:22:07 +00:00
Taus
47421a63a4 Python: Port import metrics queries 2026-03-09 17:22:06 +00:00
Taus
603d37cd60 Python: Port ModuleImportsItself.ql
Uses the existing machinery in ImportResolution.qll, after adding a few
convenience predicates.

The new modelling actually manages to find a result that the old
points-to analysis did not. Apart from that there are no test changes.
2026-03-09 17:22:06 +00:00
Taus
e2eb69ce8d Python: Port IllegalExceptionHandlerType.ql
A few relevant changes compared to the points-to version:
- we've lost `origin`, so we can no longer point to where the illegal
type lives. I opted to keep the output message the same, mirroring what
we were already doing in IllegalRaise.ql.
- We no longer track literal values flowing in from elsewhere, so we
lost a single test result where the handled "type" is the result of
calling a float-returning function.

Apart from that, the only test changes are cosmetic.
2026-03-09 17:22:01 +00:00
Taus
c4ec331e96 Python: Port IllegalRaise.ql
Adds a convenient way to get the class name for an immutable literal (to
maintain the same output format as was provided by the points-to
version). I don't know if people are in the habit of writing `raise 5`,
but I guess `raise "NotImplemented"` (wrong on so many levels) is not
entirely impossible.

No test changes.
2026-03-09 17:22:01 +00:00
Taus
4606d904ce Python: Extend ExceptionTypes API
Adds support for finding instances, and adds a `BaseException`
convenience class.
2026-03-09 17:22:01 +00:00
Taus
54af9dd10b Python: Port ConsistentReturns.ql
No test changes.
2026-03-09 17:22:01 +00:00
Taus
853df14468 Python: Port OverlyComplexDelMethod.ql
Only trivial test changes.
2026-03-09 17:22:01 +00:00
Taus
156d2c09a0 Python: Port getCyclomaticComplexity function
Note that this does not give the exact same results as the old function,
however it's not clear to me that the old results were actually correct
(it _looks_ like `read()` might be doing an IO operation, but in fact
`read` is not defined, so at best this will raise a NameError, not an
IOError).
2026-03-09 17:22:01 +00:00
Taus
7d8b4aca8b Python: Add Reachability module
The implementation is essentially the same as the one from
`BasicBlockWithPointsTo`, with the main difference being that this one
uses the exception machinery we just added (and some extensions added in
this commit).
2026-03-09 17:22:01 +00:00
Taus
f5361f43dc Python: Move exception modelling to DataFlowDispatch.qll
This analysis will is needed for the reachability modelling (which
tracks things like which exceptions are caught by which handles), so it
makes more sense for it to move to `DataFlowDispatch` for now.
2026-03-09 17:22:00 +00:00
Taus
f6cd63f508 Python: Port DocStrings.ql 2026-03-09 17:13:04 +00:00
Taus
5954287f89 Python: Port DeprecatedSliceMethod.ql
Only trivial test changes.
2026-03-09 17:13:04 +00:00
Taus
c837e1491a Python: Extend DuckTyping module
Adds `overridesMethod` and `isPropertyAccessor`.
2026-03-09 17:13:04 +00:00
Taus
d9693e70de Python: Remove missing results
These results are missing because we no longer (unlike the points-to
analysis) track how many elements a given tuple has. This is something
we might want to implement in the future (most likely through an
`int`-indexed type tracker).
2026-03-09 17:13:04 +00:00
Taus
0509ec6f0b Python: Port WrongNumberArgumentsInClassInstantiation.ql
Included test changes are trivial `toString` changes.
2026-03-09 17:13:03 +00:00
Taus
0094271966 Python: Port WrongNameForArgumentInClassInstantiation.ql 2026-03-09 17:13:03 +00:00
Taus
6c56882a75 Python: Port ShouldBeContextManager.ql
Only trivial test changes.
2026-03-09 17:13:03 +00:00
Taus
7ceb6a5748 Python: Port UselessClass.ql
No test changes.
2026-03-09 17:13:03 +00:00
Taus
c4a4e20be0 Python: Port HashedButNoHash.ql
This one is a bit more involved. Of note is the fact that it at present
only uses local flow when determining the origin of some value (whereas
the points-to version used global flow). It may be desirable to rewrite
this query to use global data-flow, but this should be done with some
care (as using "all unhashable objects" as the set of sources is
somewhat iffy with respect to performance). For that reason, I'm
sticking to mostly local flow (except for well behaved things like types
and built-ins).
2026-03-09 17:13:03 +00:00
Taus
e2dcfae3ee Python: Port InconsistentMRO.ql
For this one we actually lose a test result. However, this is kind of to
be expected since we no longer have the "precise" MRO that the points-to
analysis computes.

Honestly, I'm on the fence about even keeping this query at all. It
seems like it might be superfluous in a world with good Python type
checking.
2026-03-09 17:13:03 +00:00
Taus
8fe680c716 Python: Port PropertyInOldStyleClass.ql
Only trivial test changes.
2026-03-09 17:13:03 +00:00
Taus
8f154f6374 Python: Port SuperInOldStyleClass.ql 2026-03-09 17:13:03 +00:00
Taus
f4f217c993 Python: Port SlotsInOldStyleClass.ql
Only trivial test changes.
2026-03-09 17:13:03 +00:00
Taus
c1beca80e6 Python: Add declares/getAttribute API
These could arguably be moved to `Class` itself, but for now I'm
choosing to limit the changes to the `DuckTyping` module (until we
decide on a proper API).
2026-03-09 17:13:03 +00:00
Taus
793ecb6416 Python: Add DuckTyping::isNewStyle
Approximates the behaviour of `Types::isNewStyle` but without depending
on points-to
2026-03-09 17:13:03 +00:00
Taus
8ffcdfeb05 Python: Port UnusedExceptionObject.ql
Depending on whether other queries depend on this, we may end up moving
the exception utility functions to a more central location.
2026-03-09 17:13:02 +00:00
Taus
918e5e25ec Python: Port ShouldUseWithStatement.ql
Only trivial test changes.
2026-03-09 17:13:02 +00:00
Taus
485e949467 Python: Port NonIteratorInForLoop.ql
Same comment as for the preceding commit. We lose one test result due to
the fact that we don't know what to do about `for ... in 1` (because `1`
is an instance of a built-in). I'm going to defer addressing this until
we get some modelling of built-in types.
2026-03-09 17:13:02 +00:00
Taus
1159d20375 Python: Port ContainsNonContainer.ql
Uses the new `DuckTyping` module to handle recognising whether a class
is a container or not. Only trivial test changes (one version uses
"class", the other "Class").

Note that the ported query has no understanding of built-in classes. At
some point we'll likely want to replace `hasUnresolvedBase` (which will
hold for any class that extends a built-in) with something that's aware
of the built-in classes.
2026-03-09 17:13:02 +00:00
Taus
62d5cac6e0 Python: Introduce DuckTyping module
This module (which for convenience currently resides inside
`DataFlowDispatch`, but this may change later) contains convenience
predicates for bridging the gap between the data-flow layer and the old
points-to analysis.
2026-03-09 17:13:02 +00:00
Taus
452e189bbc Python: Port py/print-during-import
Uses a (perhaps) slightly coarser approximation of what modules are
imported, but it's probably fine.
2026-03-09 16:38:52 +00:00
Taus
5d5060b02b Python: Use API graphs instead of points-to for simple built-ins
Removes the use of points-to for accessing various built-ins from three
of the queries. In order for this to work I had to extend the lists of
known built-ins slightly.
2026-03-09 16:38:52 +00:00
61 changed files with 1190 additions and 423 deletions

View File

@@ -433,38 +433,6 @@ private predicate exits_early(BasicBlock b) {
/** The metrics for a function that require points-to analysis */ /** The metrics for a function that require points-to analysis */
class FunctionMetricsWithPointsTo extends FunctionMetrics { class FunctionMetricsWithPointsTo extends FunctionMetrics {
/**
* Gets the cyclomatic complexity of the function:
* The number of linearly independent paths through the source code.
* Computed as E - N + 2P,
* where
* E = the number of edges of the graph.
* N = the number of nodes of the graph.
* P = the number of connected components, which for a single function is 1.
*/
int getCyclomaticComplexity() {
exists(int e, int n |
n = count(BasicBlockWithPointsTo b | b = this.getABasicBlock() and b.likelyReachable()) and
e =
count(BasicBlockWithPointsTo b1, BasicBlockWithPointsTo b2 |
b1 = this.getABasicBlock() and
b1.likelyReachable() and
b2 = this.getABasicBlock() and
b2.likelyReachable() and
b2 = b1.getASuccessor() and
not b1.unlikelySuccessor(b2)
)
|
result = e - n + 2
)
}
private BasicBlock getABasicBlock() {
result = this.getEntryNode().getBasicBlock()
or
exists(BasicBlock mid | mid = this.getABasicBlock() and result = mid.getASuccessor())
}
/** /**
* Dependency of Callables * Dependency of Callables
* One callable "this" depends on another callable "result" * One callable "this" depends on another callable "result"

View File

@@ -1,5 +1,6 @@
import python import python
private import semmle.python.SelfAttribute private import semmle.python.SelfAttribute
private import semmle.python.dataflow.new.internal.DataFlowDispatch
/** The metrics for a function */ /** The metrics for a function */
class FunctionMetrics extends Function { class FunctionMetrics extends Function {
@@ -27,6 +28,32 @@ class FunctionMetrics extends Function {
int getStatementNestingDepth() { result = max(Stmt s | s.getScope() = this | getNestingDepth(s)) } int getStatementNestingDepth() { result = max(Stmt s | s.getScope() = this | getNestingDepth(s)) }
int getNumberOfCalls() { result = count(Call c | c.getScope() = this) } int getNumberOfCalls() { result = count(Call c | c.getScope() = this) }
/**
* Gets the cyclomatic complexity of the function:
* The number of linearly independent paths through the source code.
* Computed as E - N + 2P,
* where
* E = the number of edges of the graph.
* N = the number of nodes of the graph.
* P = the number of connected components, which for a single function is 1.
*/
int getCyclomaticComplexity() {
exists(int n, int e |
n = count(BasicBlock b | b.getScope() = this and Reachability::likelyReachable(b)) and
e =
count(BasicBlock b1, BasicBlock b2 |
b1.getScope() = this and
Reachability::likelyReachable(b1) and
b2.getScope() = this and
Reachability::likelyReachable(b2) and
b2 = b1.getASuccessor() and
not Reachability::unlikelySuccessor(b1.getLastNode(), b2.firstNode())
)
|
result = e - n + 2
)
}
} }
/** The metrics for a class */ /** The metrics for a class */

View File

@@ -32,7 +32,9 @@ module Builtins {
"UnicodeDecodeError", "UnicodeEncodeError", "UnicodeError", "UnicodeTranslateError", "UnicodeDecodeError", "UnicodeEncodeError", "UnicodeError", "UnicodeTranslateError",
"UnicodeWarning", "UserWarning", "ValueError", "Warning", "ZeroDivisionError", "UnicodeWarning", "UserWarning", "ValueError", "Warning", "ZeroDivisionError",
// Added for compatibility // Added for compatibility
"exec" "exec",
// Added by the `site` module (available by default unless `-S` is used)
"copyright", "credits", "exit", "quit"
] ]
or or
// Built-in constants shared between Python 2 and 3 // Built-in constants shared between Python 2 and 3
@@ -51,8 +53,8 @@ module Builtins {
or or
// Python 2 only // Python 2 only
result in [ result in [
"basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload", "unichr", "apply", "basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload",
"unicode", "xrange" "unichr", "unicode", "xrange"
] ]
} }

View File

@@ -1977,3 +1977,507 @@ private module OutNodes {
* `kind`. * `kind`.
*/ */
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) } OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
/**
* Provides predicates for approximating type properties of user-defined classes
* based on their structure (method declarations, base classes).
*
* This module should _not_ be used in the call graph computation itself, as parts of it may depend
* on layers that themselves build upon the call graph (e.g. API graphs).
*/
module DuckTyping {
private import semmle.python.ApiGraphs
/**
* Holds if `cls` or any of its resolved superclasses declares a method with the given `name`.
*/
predicate hasMethod(Class cls, string name) {
cls.getAMethod().getName() = name
or
hasMethod(getADirectSuperclass(cls), name)
}
/**
* Holds if `cls` has a base class that cannot be resolved to a user-defined class
* and is not just `object`, meaning it may inherit methods from an unknown class.
*/
predicate hasUnresolvedBase(Class cls) {
exists(Expr base | base = cls.getABase() |
not base = classTracker(_).asExpr() and
not base = API::builtin("object").getAValueReachableFromSource().asExpr()
)
}
/**
* Holds if `cls` supports the container protocol, i.e. it declares
* `__contains__`, `__iter__`, or `__getitem__`.
*/
predicate isContainer(Class cls) {
hasMethod(cls, "__contains__") or
hasMethod(cls, "__iter__") or
hasMethod(cls, "__getitem__")
}
/**
* Holds if `cls` supports the iterable protocol, i.e. it declares
* `__iter__` or `__getitem__`.
*/
predicate isIterable(Class cls) {
hasMethod(cls, "__iter__") or
hasMethod(cls, "__getitem__")
}
/**
* Holds if `cls` supports the iterator protocol, i.e. it declares
* both `__iter__` and `__next__`.
*/
predicate isIterator(Class cls) {
hasMethod(cls, "__iter__") and
hasMethod(cls, "__next__")
}
/**
* Holds if `cls` supports the context manager protocol, i.e. it declares
* both `__enter__` and `__exit__`.
*/
predicate isContextManager(Class cls) {
hasMethod(cls, "__enter__") and
hasMethod(cls, "__exit__")
}
/**
* Holds if `cls` supports the descriptor protocol, i.e. it declares
* `__get__`, `__set__`, or `__delete__`.
*/
predicate isDescriptor(Class cls) {
hasMethod(cls, "__get__") or
hasMethod(cls, "__set__") or
hasMethod(cls, "__delete__")
}
/**
* Holds if `cls` directly assigns to an attribute named `name` in its class body.
* This covers attribute assignments like `x = value`, but not method definitions.
*/
predicate declaresAttribute(Class cls, string name) { exists(getAnAttributeValue(cls, name)) }
/**
* Gets the value expression assigned to attribute `name` directly in the class body of `cls`.
*/
Expr getAnAttributeValue(Class cls, string name) {
exists(Assign a |
a.getScope() = cls and
a.getATarget().(Name).getId() = name and
result = a.getValue()
)
}
/**
* Holds if `cls` is callable, i.e. it declares `__call__`.
*/
predicate isCallable(Class cls) { hasMethod(cls, "__call__") }
/**
* Holds if `cls` supports the mapping protocol, i.e. it declares
* `__getitem__` and `keys`, or `__getitem__` and `__iter__`.
*/
predicate isMapping(Class cls) {
hasMethod(cls, "__getitem__") and
(hasMethod(cls, "keys") or hasMethod(cls, "__iter__"))
}
/**
* Holds if `cls` is a new-style class. In Python 3, all classes are new-style.
* In Python 2, a class is new-style if it (transitively) inherits from `object`,
* or has a declared `__metaclass__`, or has an unresolved base class.
*/
predicate isNewStyle(Class cls) {
major_version() = 3
or
major_version() = 2 and
(
cls.getABase() = API::builtin("object").getAValueReachableFromSource().asExpr()
or
isNewStyle(getADirectSuperclass(cls))
or
hasUnresolvedBase(cls)
or
exists(cls.getMetaClass())
or
// Module-level __metaclass__ = type makes all classes in the module new-style
exists(Assign a |
a.getScope() = cls.getEnclosingModule() and
a.getATarget().(Name).getId() = "__metaclass__"
)
)
}
/**
* Gets the `__init__` function that will be invoked when `cls` is constructed,
* resolved according to the MRO.
*/
Function getInit(Class cls) { result = invokedFunctionFromClassConstruction(cls, "__init__") }
/**
* Holds if `f` overrides a method in a superclass with the same name.
*/
predicate overridesMethod(Function f) {
exists(Class cls | f.getScope() = cls | hasMethod(getADirectSuperclass(cls), f.getName()))
}
/**
* Holds if `f` is a property accessor (decorated with `@property`, `@name.setter`,
* or `@name.deleter`).
*/
predicate isPropertyAccessor(Function f) {
exists(Attribute a | a = f.getADecorator() | a.getName() = "setter" or a.getName() = "deleter")
or
f.getADecorator().(Name).getId() = "property"
}
/** Gets the name of the builtin class of the immutable literal `lit`. */
string getClassName(ImmutableLiteral lit) {
lit instanceof IntegerLiteral and result = "int"
or
lit instanceof FloatLiteral and result = "float"
or
lit instanceof ImaginaryLiteral and result = "complex"
or
lit instanceof NegativeIntegerLiteral and result = "int"
or
lit instanceof StringLiteral and result = "str"
or
lit instanceof BooleanLiteral and result = "bool"
or
lit instanceof None and result = "NoneType"
}
}
/**
* Provides a class hierarchy for exception types, covering both builtin
* exceptions (from typeshed models) and user-defined exception classes.
*/
module ExceptionTypes {
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.data.internal.ApiGraphModels
/** Holds if `name` is a builtin exception class name. */
predicate builtinException(string name) {
typeModel("builtins.BaseException~Subclass", "builtins." + name, "")
}
/** Holds if builtin exception `sub` is a direct subclass of builtin exception `base`. */
private predicate builtinExceptionSubclass(string base, string sub) {
typeModel("builtins." + base + "~Subclass", "builtins." + sub, "")
}
/** An exception type, either a builtin exception or a user-defined exception class. */
newtype TExceptType =
/** A user-defined exception class. */
TUserExceptType(Class c) or
/** A builtin exception class, identified by name. */
TBuiltinExceptType(string name) { builtinException(name) }
/** An exception type, either a builtin exception or a user-defined exception class. */
class ExceptType extends TExceptType {
/** Gets the name of this exception type. */
string getName() { none() }
/** Gets a data-flow node that refers to this exception type. */
DataFlow::Node getAUse() { none() }
/** Gets a direct superclass of this exception type. */
ExceptType getADirectSuperclass() { none() }
/** Gets a string representation of this exception type. */
string toString() { result = this.getName() }
/** Gets a data-flow node that refers to an instance of this exception type. */
DataFlow::Node getAnInstance() { none() }
/** Holds if this is a legal exception type (a subclass of `BaseException`). */
predicate isLegalExceptionType() { this.getADirectSuperclass*() instanceof BaseException }
/**
* Holds if this exception type is raised by `r`, either as a class reference
* (e.g. `raise ValueError`) or as an instantiation (e.g. `raise ValueError("msg")`).
*/
predicate isRaisedBy(Raise r) {
exists(Expr raised | raised = r.getRaised() |
this.getAUse().asExpr() in [raised, raised.(Call).getFunc()]
or
this.getAnInstance().asExpr() = raised
)
}
/** Holds if this exception type may be raised at control flow node `r`. */
predicate isRaisedAt(ControlFlowNode r) {
this.isRaisedBy(r.getNode())
or
exists(Function callee |
resolveCall(r, callee, _) and
this.isRaisedIn(callee)
)
}
/**
* Holds if this exception type may be raised in function `f`, either
* directly via `raise` statements or transitively through calls to other functions.
*/
predicate isRaisedIn(Function f) { this.isRaisedAt(any(ControlFlowNode r | r.getScope() = f)) }
/** Holds if this exception type is handled by the `except` clause at `handler`. */
predicate isHandledAt(ExceptFlowNode handler) {
exists(ExceptStmt ex, Expr typeExpr | ex = handler.getNode() |
(
typeExpr = ex.getType()
or
typeExpr = ex.getType().(Tuple).getAnElt()
) and
this.getAUse().asExpr() = typeExpr
)
or
// A bare `except:` handles everything
not exists(handler.getNode().(ExceptStmt).getType()) and
this instanceof BaseException
}
/**
* Holds if this element is at the specified location.
* The location spans column `startColumn` of line `startLine` to
* column `endColumn` of line `endLine` in file `filepath`.
* For more information, see
* [Providing locations in CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filePath, int startLine, int startColumn, int endLine, int endColumn
) {
none()
}
}
/** A user-defined exception class. */
class UserExceptType extends ExceptType, TUserExceptType {
Class cls;
UserExceptType() { this = TUserExceptType(cls) }
/** Gets the underlying class. */
Class asClass() { result = cls }
override string getName() { result = cls.getName() }
override DataFlow::Node getAUse() { result = classTracker(cls) }
override DataFlow::Node getAnInstance() { result = classInstanceTracker(cls) }
override ExceptType getADirectSuperclass() {
result.(UserExceptType).asClass() = getADirectSuperclass(cls)
or
result.(BuiltinExceptType).getAUse().asExpr() = cls.getABase()
}
override predicate hasLocationInfo(
string filePath, int startLine, int startColumn, int endLine, int endColumn
) {
cls.getLocation().hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn)
}
}
/** A builtin exception class, identified by name. */
class BuiltinExceptType extends ExceptType, TBuiltinExceptType {
string name;
BuiltinExceptType() { this = TBuiltinExceptType(name) }
/** Gets the builtin name. */
string asBuiltinName() { result = name }
override string getName() { result = name }
override DataFlow::Node getAUse() { result = API::builtin(name).getAValueReachableFromSource() }
override DataFlow::Node getAnInstance() {
result = API::builtin(name).getAnInstance().getAValueReachableFromSource()
}
override ExceptType getADirectSuperclass() {
builtinExceptionSubclass(result.(BuiltinExceptType).asBuiltinName(), name) and
result != this
}
override predicate hasLocationInfo(
string filePath, int startLine, int startColumn, int endLine, int endColumn
) {
filePath = "" and
startLine = 0 and
startColumn = 0 and
endLine = 0 and
endColumn = 0
}
}
/** The builtin `BaseException` type. */
class BaseException extends BuiltinExceptType {
BaseException() { name = "BaseException" }
}
/**
* Holds if the exception edge from `r` to `handler` is unlikely because
* none of the exception types that `r` may raise are handled by `handler`.
*/
predicate unlikelyExceptionEdge(ControlFlowNode r, ExceptFlowNode handler) {
handler = r.getAnExceptionalSuccessor() and
// We can determine at least one raised type
exists(ExceptType t | t.isRaisedAt(r)) and
// But none of them are handled by this handler
not exists(ExceptType raised, ExceptType handled |
raised.isRaisedAt(r) and
handled.isHandledAt(handler) and
raised.getADirectSuperclass*() = handled
)
}
}
/**
* Provides predicates for reasoning about the reachability of control flow nodes
* and basic blocks.
*/
module Reachability {
private import semmle.python.ApiGraphs
import ExceptionTypes
/**
* Holds if `call` is a call to a function that is known to never return normally
* (e.g. `sys.exit()`, `os._exit()`, `os.abort()`).
*/
predicate isCallToNeverReturningFunction(CallNode call) {
// Known never-returning builtins/stdlib functions via API graphs
call = API::builtin("exit").getACall().asCfgNode()
or
call = API::builtin("quit").getACall().asCfgNode()
or
call = API::moduleImport("sys").getMember("exit").getACall().asCfgNode()
or
call = API::moduleImport("os").getMember("_exit").getACall().asCfgNode()
or
call = API::moduleImport("os").getMember("abort").getACall().asCfgNode()
or
// User-defined functions that only contain raise statements (no normal returns)
exists(Function target |
resolveCall(call, target, _) and
neverReturns(target)
)
}
/**
* Holds if function `f` never returns normally, because every normal exit
* is dominated by a call to a never-returning function or an unconditional raise.
*/
predicate neverReturns(Function f) {
exists(f.getANormalExit()) and
forall(BasicBlock exit | exit = f.getANormalExit().getBasicBlock() |
exists(BasicBlock raising |
raising.dominates(exit) and
(
isCallToNeverReturningFunction(raising.getLastNode())
or
raising.getLastNode().getNode() instanceof Raise
)
)
)
}
/**
* Holds if `node` is unlikely to raise an exception. This includes entry nodes
* and simple name lookups.
*/
private predicate unlikelyToRaise(ControlFlowNode node) {
exists(node.getAnExceptionalSuccessor()) and
(
node.getNode() instanceof Name
or
exists(Scope s | s.getEntryNode() = node)
)
}
/**
* Holds if it is highly unlikely for control to flow from `node` to `succ`.
*/
predicate unlikelySuccessor(ControlFlowNode node, ControlFlowNode succ) {
// Exceptional edge where the raised type doesn't match the handler
unlikelyExceptionEdge(node, succ)
or
// Normal successor of a never-returning call
isCallToNeverReturningFunction(node) and
succ = node.getASuccessor() and
not succ = node.getAnExceptionalSuccessor() and
not succ.getNode() instanceof Yield
or
// Exception edge from a node that is unlikely to raise
unlikelyToRaise(node) and
succ = node.getAnExceptionalSuccessor()
or
// True branch of `if False:` or `if TYPE_CHECKING:`
isAlwaysFalseGuard(node) and
succ = node.getATrueSuccessor()
}
/**
* Holds if `node` is a condition that is always `False` at runtime.
* This covers `if False:` and `if typing.TYPE_CHECKING:`.
*/
private predicate isAlwaysFalseGuard(ControlFlowNode node) {
node.getNode() instanceof False
or
node =
API::moduleImport("typing")
.getMember("TYPE_CHECKING")
.getAValueReachableFromSource()
.asCfgNode()
}
private predicate startBbLikelyReachable(BasicBlock b) {
exists(Scope s | s.getEntryNode() = b.getNode(_))
or
exists(BasicBlock pred |
pred = b.getAPredecessor() and
endBbLikelyReachable(pred) and
not unlikelySuccessor(pred.getLastNode(), b)
)
}
private predicate endBbLikelyReachable(BasicBlock b) {
startBbLikelyReachable(b) and
not exists(ControlFlowNode p, ControlFlowNode s |
unlikelySuccessor(p, s) and
p = b.getNode(_) and
s = b.getNode(_) and
not p = b.getLastNode()
)
}
/**
* Holds if basic block `b` is likely to be reachable from the entry of its
* enclosing scope.
*/
predicate likelyReachable(BasicBlock b) { startBbLikelyReachable(b) }
/**
* Holds if it is unlikely that `node` can be reached during execution.
*/
predicate unlikelyReachable(ControlFlowNode node) {
not startBbLikelyReachable(node.getBasicBlock())
or
exists(BasicBlock b |
startBbLikelyReachable(b) and
not endBbLikelyReachable(b) and
exists(ControlFlowNode p, int i, int j |
unlikelySuccessor(p, _) and
p = b.getNode(i) and
node = b.getNode(j) and
i < j
)
)
}
}

View File

@@ -377,4 +377,30 @@ module ImportResolution {
} }
Module getModule(DataFlow::CfgNode node) { node = getModuleReference(result) } Module getModule(DataFlow::CfgNode node) { node = getModuleReference(result) }
/** Holds if module `importer` directly imports module `imported`. */
predicate imports(Module importer, Module imported) {
getImmediateModuleReference(imported).getScope() = importer
}
/**
* Holds if the import statement `i` causes module `imported` to be imported.
* For `from pkg import submodule`, both `pkg` and `pkg.submodule` are considered imported.
*/
predicate importedBy(ImportingStmt i, Module imported) {
exists(Alias a | a = i.(Import).getAName() |
getImmediateModuleReference(imported).asExpr() = a.getAsname()
)
or
exists(ImportMember im | im = i.(Import).getAName().getValue() |
getImmediateModuleReference(imported).asExpr() = im.getModule()
)
or
getImmediateModuleReference(imported).asExpr() = i.(ImportStar).getModule().(ImportExpr)
}
/** Gets a user-friendly name for module `m`, using the package name for `__init__` modules. */
string moduleName(Module m) {
if m.isPackageInit() then result = m.getPackageName() else result = m.getName()
}
} }

View File

@@ -12,19 +12,24 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.DataFlowDispatch
ClassObject left_base(ClassObject type, ClassObject base) { /**
exists(int i | i > 0 and type.getBaseType(i) = base and result = type.getBaseType(i - 1)) * Gets the `i`th base class of `cls`, if it can be resolved to a user-defined class.
*/
Class getBaseType(Class cls, int i) { cls.getBase(i) = classTracker(result).asExpr() }
Class left_base(Class type, Class base) {
exists(int i | i > 0 and getBaseType(type, i) = base and result = getBaseType(type, i - 1))
} }
predicate invalid_mro(ClassObject t, ClassObject left, ClassObject right) { predicate invalid_mro(Class t, Class left, Class right) {
t.isNewStyle() and DuckTyping::isNewStyle(t) and
left = left_base(t, right) and left = left_base(t, right) and
left = right.getAnImproperSuperType() left = getADirectSuperclass*(right)
} }
from ClassObject t, ClassObject left, ClassObject right from Class t, Class left, Class right
where invalid_mro(t, left, right) where invalid_mro(t, left, right)
select t, select t,
"Construction of class " + t.getName() + "Construction of class " + t.getName() +

View File

@@ -11,10 +11,13 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.DataFlowDispatch
from PropertyObject prop, ClassObject cls from Function prop, Class cls
where cls.declaredAttribute(_) = prop and not cls.failedInference() and not cls.isNewStyle() where
prop.getScope() = cls and
prop.getADecorator().(Name).getId() = "property" and
not DuckTyping::isNewStyle(cls)
select prop, select prop,
"Property " + prop.getName() + " will not work properly, as class " + cls.getName() + "Property " + prop.getName() + " will not work properly, as class " + cls.getName() +
" is an old-style class." " is an old-style class."

View File

@@ -14,10 +14,12 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.DataFlowDispatch
from ClassValue c from Class c
where not c.isBuiltin() and not c.isContextManager() and exists(c.declaredAttribute("__del__")) where
not DuckTyping::isContextManager(c) and
DuckTyping::hasMethod(c, "__del__")
select c, select c,
"Class " + c.getName() + "Class " + c.getName() +
" implements __del__ (presumably to release some resource). Consider making it a context manager." " implements __del__ (presumably to release some resource). Consider making it a context manager."

View File

@@ -12,9 +12,11 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.DataFlowDispatch
from ClassObject c from Class c
where not c.isNewStyle() and c.declaresAttribute("__slots__") and not c.failedInference() where
not DuckTyping::isNewStyle(c) and
DuckTyping::declaresAttribute(c, "__slots__")
select c, select c,
"Using '__slots__' in an old style class just creates a class attribute called '__slots__'." "Using '__slots__' in an old style class just creates a class attribute called '__slots__'."

View File

@@ -11,14 +11,13 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.DataFlowDispatch
predicate uses_of_super_in_old_style_class(Call s) { predicate uses_of_super_in_old_style_class(Call s) {
exists(Function f, ClassObject c | exists(Function f, Class c |
s.getScope() = f and s.getScope() = f and
f.getScope() = c.getPyClass() and f.getScope() = c and
not c.failedInference() and not DuckTyping::isNewStyle(c) and
not c.isNewStyle() and
s.getFunc().(Name).getId() = "super" s.getFunc().(Name).getId() = "super"
) )
} }

View File

@@ -13,7 +13,7 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.DataFlowDispatch
predicate fewer_than_two_public_methods(Class cls, int methods) { predicate fewer_than_two_public_methods(Class cls, int methods) {
(methods = 0 or methods = 1) and (methods = 0 or methods = 1) and
@@ -25,13 +25,8 @@ predicate does_not_define_special_method(Class cls) {
} }
predicate no_inheritance(Class c) { predicate no_inheritance(Class c) {
not exists(ClassValue cls, ClassValue other | not exists(getADirectSubclass(c)) and
cls.getScope() = c and not exists(getADirectSuperclass(c)) and
other != ClassValue::object()
|
other.getABaseType() = cls or
cls.getABaseType() = other
) and
not exists(Expr base | base = c.getABase() | not exists(Expr base | base = c.getABase() |
not base instanceof Name or base.(Name).getId() != "object" not base instanceof Name or base.(Name).getId() != "object"
) )

View File

@@ -15,12 +15,34 @@
*/ */
import python import python
import Expressions.CallArgs private import semmle.python.dataflow.new.internal.DataFlowDispatch
private import LegacyPointsTo
from Call call, ClassValue cls, string name, FunctionValue init /**
* Holds if `name` is a legal argument name for calling `init`.
*/
bindingset[name]
predicate isLegalArgumentName(Function init, string name) {
exists(init.getArgByName(name))
or
init.hasKwArg()
}
/**
* Holds if `call` constructs class `cls` and passes a keyword argument `name`
* that does not correspond to any parameter of `cls.__init__`.
*/
predicate illegally_named_parameter(Call call, Class cls, string name) {
exists(Function init |
resolveClassCall(call.getAFlowNode(), cls) and
init = DuckTyping::getInit(cls) and
name = call.getANamedArgumentName() and
not isLegalArgumentName(init, name)
)
}
from Call call, Class cls, string name, Function init
where where
illegally_named_parameter(call, cls, name) and illegally_named_parameter(call, cls, name) and
init = get_function_or_initializer(cls) init = DuckTyping::getInit(cls)
select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init, select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init,
init.getQualifiedName() init.getQualifiedName()

View File

@@ -14,10 +14,60 @@
*/ */
import python import python
import Expressions.CallArgs private import semmle.python.dataflow.new.internal.DataFlowDispatch
private import LegacyPointsTo
from Call call, ClassValue cls, string too, string should, int limit, FunctionValue init /**
* Gets the number of positional arguments in `call`, including elements of any
* literal list passed as `*args`, plus keyword arguments that don't match
* keyword-only parameters (when the function doesn't accept `**kwargs`).
*/
int positional_arg_count(Call call, Class cls, Function init) {
resolveClassCall(call.getAFlowNode(), cls) and
init = DuckTyping::getInit(cls) and
exists(int positional_keywords |
if init.hasKwArg()
then positional_keywords = 0
else
positional_keywords =
count(Keyword kw |
kw = call.getAKeyword() and
not init.getAKeywordOnlyArg().getId() = kw.getArg()
)
|
result =
count(call.getAnArg()) + count(call.getStarargs().(List).getAnElt()) + positional_keywords
)
}
/**
* Holds if `call` constructs `cls` with too many arguments, where `limit` is the maximum.
*/
predicate too_many_args(Call call, Class cls, int limit) {
exists(Function init |
not init.hasVarArg() and
// Subtract 1 from max to account for `self` parameter
limit = init.getMaxPositionalArguments() - 1 and
limit >= 0 and
positional_arg_count(call, cls, init) > limit
)
}
/**
* Holds if `call` constructs `cls` with too few arguments, where `limit` is the minimum.
*/
predicate too_few_args(Call call, Class cls, int limit) {
resolveClassCall(call.getAFlowNode(), cls) and
exists(Function init |
init = DuckTyping::getInit(cls) and
not exists(call.getStarargs()) and
not exists(call.getKwargs()) and
// Subtract 1 from min to account for `self` parameter
limit = init.getMinPositionalArguments() - 1 and
count(call.getAnArg()) + count(call.getAKeyword()) < limit
)
}
from Call call, Class cls, string too, string should, int limit, Function init
where where
( (
too_many_args(call, cls, limit) and too_many_args(call, cls, limit) and
@@ -28,6 +78,6 @@ where
too = "too few arguments" and too = "too few arguments" and
should = "no fewer than " should = "no fewer than "
) and ) and
init = get_function_or_initializer(cls) init = DuckTyping::getInit(cls)
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init, select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init,
init.getQualifiedName() init.getQualifiedName()

View File

@@ -12,20 +12,38 @@
*/ */
import python import python
private import LegacyPointsTo import semmle.python.dataflow.new.internal.DataFlowDispatch
private import ExceptionTypes
from ExceptFlowNodeWithPointsTo ex, Value t, ClassValue c, ControlFlowNode origin, string what /**
* Gets an expression used as a handler type in the `except` clause at `ex`,
* either directly or as an element of a tuple.
*/
Expr handlerExpr(ExceptStmt ex) {
result = ex.getType() or
result = ex.getType().(Tuple).getAnElt()
}
/**
* Gets an exception type used in the `except` clause at `ex`,
* where that type is not a legal exception type.
*/
ExceptType illegalHandlerType(ExceptStmt ex) {
result.getAUse().asExpr() = handlerExpr(ex) and
not result.isLegalExceptionType()
}
from ExceptStmt ex, string msg
where where
ex.handledException(t, c, origin) and exists(ExceptType t | t = illegalHandlerType(ex) |
( msg =
exists(ClassValue x | x = t | "Non-exception class '" + t.getName() +
not x.isLegalExceptionType() and "' in exception handler which will never match raised exception."
not x.failedInference(_) and
what = "class '" + x.getName() + "'"
) )
or or
not t instanceof ClassValue and exists(ImmutableLiteral lit | lit = handlerExpr(ex) and not lit instanceof None |
what = "instance of '" + c.getName() + "'" msg =
"Non-exception class '" + DuckTyping::getClassName(lit) +
"' in exception handler which will never match raised exception."
) )
select ex.getNode(), select ex, msg
"Non-exception $@ in exception handler which will never match raised exception.", origin, what

View File

@@ -12,15 +12,48 @@
*/ */
import python import python
import Raising import semmle.python.dataflow.new.internal.DataFlowDispatch
import Exceptions.NotImplemented import semmle.python.ApiGraphs
private import LegacyPointsTo private import ExceptionTypes
from Raise r, ClassValue t /**
* Holds if `r` raises an instance of a builtin non-exception class named `name`.
*/
private predicate raisesNonExceptionBuiltin(Raise r, string name) {
exists(Expr raised | raised = r.getRaised() |
API::builtin(name).getAValueReachableFromSource().asExpr() = raised
or
API::builtin(name).getAValueReachableFromSource().asExpr() = raised.(Call).getFunc() and
// Exclude `type` since `type(x)` returns the class of `x`, not a `type` instance
not name = "type"
) and
not builtinException(name)
}
from Raise r, string msg
where where
type_or_typeof(r, t, _) and not raisesNonExceptionBuiltin(r, "NotImplemented") and
(
exists(ExceptType t |
t.isRaisedBy(r) and
not t.isLegalExceptionType() and not t.isLegalExceptionType() and
not t.failedInference(_) and not t.getName() = "None" and
not use_of_not_implemented_in_raise(r, _) msg =
select r, "Illegal class '" + t.getName() +
"Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead." "' 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

View File

@@ -15,74 +15,7 @@
import python import python
import semmle.python.dataflow.new.internal.DataFlowDispatch import semmle.python.dataflow.new.internal.DataFlowDispatch
import semmle.python.ApiGraphs private import ExceptionTypes
import semmle.python.frameworks.data.internal.ApiGraphModels
predicate builtinException(string name) {
typeModel("builtins.BaseException~Subclass", "builtins." + name, "")
}
predicate builtinExceptionSubclass(string base, string sub) {
typeModel("builtins." + base + "~Subclass", "builtins." + sub, "")
}
newtype TExceptType =
TClass(Class c) or
TBuiltin(string name) { builtinException(name) }
class ExceptType extends TExceptType {
Class asClass() { this = TClass(result) }
string asBuiltinName() { this = TBuiltin(result) }
predicate isBuiltin() { this = TBuiltin(_) }
string getName() {
result = this.asClass().getName()
or
result = this.asBuiltinName()
}
string toString() { result = this.getName() }
DataFlow::Node getAUse() {
result = classTracker(this.asClass())
or
API::builtin(this.asBuiltinName()).asSource().flowsTo(result)
}
ExceptType getADirectSuperclass() {
result.asClass() = getADirectSuperclass(this.asClass())
or
result.isBuiltin() and
result.getAUse().asExpr() = this.asClass().getABase()
or
builtinExceptionSubclass(result.asBuiltinName(), this.asBuiltinName()) and
this != result
}
/**
* Holds if this element is at the specified location.
* The location spans column `startColumn` of line `startLine` to
* column `endColumn` of line `endLine` in file `filepath`.
* For more information, see
* [Providing locations in CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filePath, int startLine, int startColumn, int endLine, int endColumn
) {
this.asClass()
.getLocation()
.hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn)
or
this.isBuiltin() and
filePath = "" and
startLine = 0 and
startColumn = 0 and
endLine = 0 and
endColumn = 0
}
}
predicate incorrectExceptOrder(ExceptStmt ex1, ExceptType cls1, ExceptStmt ex2, ExceptType cls2) { predicate incorrectExceptOrder(ExceptStmt ex1, ExceptType cls1, ExceptStmt ex2, ExceptType cls2) {
exists(int i, int j, Try t | exists(int i, int j, Try t |

View File

@@ -12,25 +12,21 @@
*/ */
import python import python
private import LegacyPointsTo import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.internal.DataFlowDispatch
predicate rhs_in_expr(ControlFlowNode rhs, Compare cmp) { predicate rhs_in_expr(Expr rhs, Compare cmp) {
exists(Cmpop op, int i | cmp.getOp(i) = op and cmp.getComparator(i) = rhs.getNode() | exists(Cmpop op, int i | cmp.getOp(i) = op and cmp.getComparator(i) = rhs |
op instanceof In or op instanceof NotIn op instanceof In or op instanceof NotIn
) )
} }
from from Compare cmp, DataFlow::LocalSourceNode origin, DataFlow::Node rhs, Class cls
ControlFlowNodeWithPointsTo non_seq, Compare cmp, Value v, ClassValue cls, ControlFlowNode origin
where where
rhs_in_expr(non_seq, cmp) and origin = classInstanceTracker(cls) and
non_seq.pointsTo(_, v, origin) and origin.flowsTo(rhs) and
v.getClass() = cls and not DuckTyping::isContainer(cls) and
not Types::failedInference(cls, _) and not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(cls)) and
not cls.hasAttribute("__contains__") and rhs_in_expr(rhs.asExpr(), cmp)
not cls.hasAttribute("__iter__") and
not cls.hasAttribute("__getitem__") and
not cls = ClassValue::nonetype() and
not cls = Value::named("types.MappingProxyType")
select cmp, "This test may raise an Exception as the $@ may be of non-container class $@.", origin, select cmp, "This test may raise an Exception as the $@ may be of non-container class $@.", origin,
"target", cls, cls.getName() "target", cls, cls.getName()

View File

@@ -12,76 +12,97 @@
*/ */
import python import python
private import LegacyPointsTo import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.internal.DataFlowDispatch
private import semmle.python.ApiGraphs
/* /**
* This assumes that any indexing operation where the value is not a sequence or numpy array involves hashing. * Holds if `cls` explicitly sets `__hash__` to `None`, making instances unhashable.
* For sequences, the index must be an int, which are hashable, so we don't need to treat them specially.
* For numpy arrays, the index may be a list, which are not hashable and needs to be treated specially.
*/ */
predicate setsHashToNone(Class cls) {
predicate numpy_array_type(ClassValue na) { DuckTyping::getAnAttributeValue(cls, "__hash__") instanceof None
exists(ModuleValue np | np.getName() = "numpy" or np.getName() = "numpy.core" |
na.getASuperType() = np.attr("ndarray")
)
} }
predicate has_custom_getitem(Value v) { /**
v.getClass().lookup("__getitem__") instanceof PythonFunctionValue * Holds if `cls` is a user-defined class whose instances are unhashable.
* A new-style class without `__hash__` is unhashable, as is one that explicitly
* sets `__hash__ = None`.
*/
predicate isUnhashableUserClass(Class cls) {
DuckTyping::isNewStyle(cls) and
not DuckTyping::hasMethod(cls, "__hash__") and
not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(cls))
or or
numpy_array_type(v.getClass()) setsHashToNone(cls)
} }
predicate explicitly_hashed(ControlFlowNode f) { /**
exists(CallNode c, GlobalVariable hash | * Gets the name of a builtin type whose instances are unhashable.
c.getArg(0) = f and c.getFunction().(NameNode).uses(hash) and hash.getId() = "hash" */
) string getUnhashableBuiltinName() { result = ["list", "set", "dict", "bytearray"] }
}
predicate unhashable_subscript(ControlFlowNode f, ClassValue c, ControlFlowNode origin) { /**
is_unhashable(f, c, origin) and * Holds if `origin` is a local source node tracking an unhashable instance that
exists(SubscriptNode sub | sub.getIndex() = f | * flows to `node`, with `clsName` describing the class for the alert.
exists(Value custom_getitem | */
sub.getObject().(ControlFlowNodeWithPointsTo).pointsTo(custom_getitem) and predicate isUnhashable(DataFlow::LocalSourceNode origin, DataFlow::Node node, string clsName) {
not has_custom_getitem(custom_getitem) exists(Class c |
isUnhashableUserClass(c) and
origin = classInstanceTracker(c) and
origin.flowsTo(node) and
clsName = c.getName()
) )
)
}
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 or
cls.lookup("__hash__") = Value::named("None") 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(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: * Holds if `e` is inside a `try` that catches `TypeError`.
*
* try:
* ... f ...
* except TypeError:
* ...
*
* This predicate is used to eliminate false positive results. If `hash`
* is called on an unhashable object then a `TypeError` will be thrown.
* But this is not a bug if the code catches the `TypeError` and handles
* it.
*/ */
predicate typeerror_is_caught(ControlFlowNode f) { predicate typeerror_is_caught(Expr e) {
exists(Try try | exists(Try try |
try.getBody().contains(f.getNode()) and try.getBody().contains(e) and
try.getAHandler().getType().(ExprWithPointsTo).pointsTo(ClassValue::typeError()) try.getAHandler().getType() = API::builtin("TypeError").getAValueReachableFromSource().asExpr()
) )
} }
from ControlFlowNode f, ClassValue c, ControlFlowNode origin from DataFlow::LocalSourceNode origin, DataFlow::Node node, string clsName
where where
not typeerror_is_caught(f) and not typeerror_is_caught(node.asExpr()) and
( (
explicitly_hashed(f) and is_unhashable(f, c, origin) explicitly_hashed(node) and isUnhashable(origin, node, clsName)
or or
unhashable_subscript(f, c, origin) unhashable_subscript(origin, node, clsName)
) )
select f.getNode(), "This $@ of $@ is unhashable.", origin, "instance", c, c.getQualifiedName() select node, "This $@ of $@ is unhashable.", origin, "instance", origin, clsName

View File

@@ -10,9 +10,10 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.ApiGraphs
private import semmle.python.types.Builtins
from CallNode call, ControlFlowNodeWithPointsTo func from CallNode call
where major_version() = 2 and call.getFunction() = func and func.pointsTo(Value::named("apply")) where
major_version() = 2 and
call = API::builtin("apply").getACall().asCfgNode()
select call, "Call to the obsolete builtin function 'apply'." select call, "Call to the obsolete builtin function 'apply'."

View File

@@ -12,7 +12,7 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.DataFlowDispatch
predicate explicitly_returns_non_none(Function func) { predicate explicitly_returns_non_none(Function func) {
exists(Return return | exists(Return return |
@@ -22,8 +22,8 @@ predicate explicitly_returns_non_none(Function func) {
} }
predicate has_implicit_return(Function func) { predicate has_implicit_return(Function func) {
exists(ControlFlowNodeWithPointsTo fallthru | exists(ControlFlowNode fallthru |
fallthru = func.getFallthroughNode() and not fallthru.unlikelyReachable() fallthru = func.getFallthroughNode() and not Reachability::unlikelyReachable(fallthru)
) )
or or
exists(Return return | return.getScope() = func and not exists(return.getValue())) exists(Return return | return.getScope() = func and not exists(return.getValue()))

View File

@@ -10,16 +10,17 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.DataFlowDispatch
predicate slice_method_name(string name) { predicate slice_method_name(string name) {
name = "__getslice__" or name = "__setslice__" or name = "__delslice__" name = "__getslice__" or name = "__setslice__" or name = "__delslice__"
} }
from PythonFunctionValue f, string meth from Function f, string meth
where where
f.getScope().isMethod() and f.isMethod() and
not f.isOverridingMethod() and
slice_method_name(meth) and slice_method_name(meth) and
f.getName() = meth f.getName() = meth and
not DuckTyping::overridesMethod(f) and
not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(f.getScope()))
select f, meth + " method has been deprecated since Python 2.0." select f, meth + " method has been deprecated since Python 2.0."

View File

@@ -12,12 +12,11 @@
*/ */
import python import python
private import LegacyPointsTo import semmle.python.Metrics
from FunctionValue method from FunctionMetrics method
where where
exists(ClassValue c | method.getName() = "__del__" and
c.declaredAttribute("__del__") = method and method.isMethod() and
method.getScope().(FunctionMetricsWithPointsTo).getCyclomaticComplexity() > 3 method.getCyclomaticComplexity() > 3
)
select method, "Overly complex '__del__' method." select method, "Overly complex '__del__' method."

View File

@@ -12,16 +12,16 @@
*/ */
import python import python
import Cyclic import CyclicImports
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.ImportResolution
from ModuleValue m1, ModuleValue m2, Stmt imp from Module m1, Module m2, Stmt imp
where where
imp.getEnclosingModule() = m1.getScope() and imp.getEnclosingModule() = m1 and
stmt_imports(imp) = m2 and stmt_imports(imp) = m2 and
circular_import(m1, m2) and circular_import(m1, m2) and
m1 != m2 and m1 != m2 and
// this query finds all cyclic imports that are *not* flagged by ModuleLevelCyclicImport // this query finds all cyclic imports that are *not* flagged by ModuleLevelCyclicImport
not failing_import_due_to_cycle(m2, m1, _, _, _, _) and not failing_import_due_to_cycle(m2, m1, _, _, _, _) and
not exists(If i | i.isNameEqMain() and i.contains(imp)) not exists(If i | i.isNameEqMain() and i.contains(imp))
select imp, "Import of module $@ begins an import cycle.", m2, m2.getName() select imp, "Import of module $@ begins an import cycle.", m2, ImportResolution::moduleName(m2)

View 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))
}

View File

@@ -11,7 +11,7 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.ApiGraphs
/** /**
* Holds if the module `name` was deprecated in Python version `major`.`minor`, * Holds if the module `name` was deprecated in Python version `major`.`minor`,
@@ -80,7 +80,7 @@ where
name = imp.getName() and name = imp.getName() and
deprecated_module(name, instead, _, _) and deprecated_module(name, instead, _, _) and
not exists(Try try, ExceptStmt except | except = try.getAHandler() | not exists(Try try, ExceptStmt except | except = try.getAHandler() |
except.getType().(ExprWithPointsTo).pointsTo(ClassValue::importError()) and except.getType() = API::builtin("ImportError").getAValueReachableFromSource().asExpr() and
except.containsInScope(imp) except.containsInScope(imp)
) )
select imp, deprecation_message(name) + replacement_message(name) select imp, deprecation_message(name) + replacement_message(name)

View File

@@ -12,24 +12,26 @@
*/ */
import python import python
private import LegacyPointsTo import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.internal.ImportResolution
import semmle.python.filters.Tests import semmle.python.filters.Tests
from ImportMember im, ModuleValue m, AttrNode store_attr, string name from ImportMember im, Module m, DataFlow::AttrWrite store_attr, string name
where where
m.importedAs(im.getModule().(ImportExpr).getImportedModuleName()) and ImportResolution::getImmediateModuleReference(m).asExpr() = im.getModule() and
im.getName() = name and im.getName() = name and
/* Modification must be in a function, so it can occur during lifetime of the import value */ /* Modification must be in a function, so it can occur during lifetime of the import value */
store_attr.getScope() instanceof Function and store_attr.getObject().getScope() instanceof Function and
/* variable resulting from import must have a long lifetime */ /* variable resulting from import must have a long lifetime */
not im.getScope() instanceof Function and not im.getScope() instanceof Function and
store_attr.isStore() and store_attr.getAttributeName() = name and
store_attr.getObject(name).(ControlFlowNodeWithPointsTo).pointsTo(m) and ImportResolution::getModuleReference(m) = store_attr.getObject() and
/* Import not in same module as modification. */ /* Import not in same module as modification. */
not im.getEnclosingModule() = store_attr.getScope().getEnclosingModule() and not im.getEnclosingModule() = store_attr.getObject().getScope().getEnclosingModule() and
/* Modification is not in a test */ /* Modification is not in a test */
not store_attr.getScope().getScope*() instanceof TestScope not store_attr.getObject().getScope().getScope*() instanceof TestScope
select im, select im,
"Importing the value of '" + name + "Importing the value of '" + name +
"' from $@ means that any change made to $@ will be not be observed locally.", m, "' from $@ means that any change made to $@ will be not be observed locally.", m,
"module " + m.getName(), store_attr, m.getName() + "." + store_attr.getName() "module " + ImportResolution::moduleName(m), store_attr,
ImportResolution::moduleName(m) + "." + store_attr.getAttributeName()

View File

@@ -12,19 +12,19 @@
*/ */
import python import python
private import LegacyPointsTo import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.internal.ImportResolution
predicate modules_imports_itself(ImportingStmt i, ModuleValue m) { predicate modules_imports_itself(ImportingStmt i, Module m) {
i.getEnclosingModule() = m.getScope() and m = i.getEnclosingModule() and
m = ImportResolution::importedBy(i, m) and
max(string s, ModuleValue m_ | // Exclude `from m import submodule` where the imported member is a submodule of m
s = i.getAnImportedModuleName() and not exists(ImportMember im | im = i.(Import).getAName().getValue() |
m_.importedAs(s) ImportResolution::getImmediateModuleReference(m).asExpr() = im.getModule() and
| ImportResolution::importedBy(i, any(Module sub | sub != m))
m_ order by s.length()
) )
} }
from ImportingStmt i, ModuleValue m from ImportingStmt i, Module m
where modules_imports_itself(i, m) where modules_imports_itself(i, m)
select i, "The module '" + m.getName() + "' imports itself." select i, "The module '" + ImportResolution::moduleName(m) + "' imports itself."

View File

@@ -13,8 +13,8 @@
*/ */
import python import python
import Cyclic import CyclicImports
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.ImportResolution
// This is a potentially crashing bug if // This is a potentially crashing bug if
// 1. the imports in the whole cycle are lexically outside a def (and so executed at import time) // 1. the imports in the whole cycle are lexically outside a def (and so executed at import time)
@@ -22,10 +22,11 @@ private import LegacyPointsTo
// 3. 'foo' is defined in M after the import in M which completes the cycle. // 3. 'foo' is defined in M after the import in M which completes the cycle.
// then if we import the 'used' module, we will reach the cyclic import, start importing the 'using' // then if we import the 'used' module, we will reach the cyclic import, start importing the 'using'
// module, hit the 'use', and then crash due to the imported symbol not having been defined yet // module, hit the 'use', and then crash due to the imported symbol not having been defined yet
from ModuleValue m1, Stmt imp, ModuleValue m2, string attr, Expr use, ControlFlowNode defn from Module m1, Stmt imp, Module m2, string attr, Expr use, ControlFlowNode defn
where failing_import_due_to_cycle(m1, m2, imp, defn, use, attr) where failing_import_due_to_cycle(m1, m2, imp, defn, use, attr)
select use, select use,
"'" + attr + "' may not be defined if module $@ is imported before module $@, as the $@ of " + "'" + attr + "' may not be defined if module $@ is imported before module $@, as the $@ of " +
attr + " occurs after the cyclic $@ of " + m2.getName() + ".", attr + " occurs after the cyclic $@ of " + ImportResolution::moduleName(m2) + ".",
// Arguments for the placeholders in the above message: // Arguments for the placeholders in the above message:
m1, m1.getName(), m2, m2.getName(), defn, "definition", imp, "import" m1, ImportResolution::moduleName(m1), m2, ImportResolution::moduleName(m2), defn, "definition",
imp, "import"

View File

@@ -13,23 +13,19 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.ImportResolution
private import semmle.python.types.ImportTime private import semmle.python.types.ImportTime
predicate import_star(ImportStar imp, ModuleValue exporter) { predicate all_defined(Module exporter) {
exporter.importedAs(imp.getImportedModuleName()) exporter.(ImportTimeScope).definesName("__all__")
or
exporter.getInitModule().(ImportTimeScope).definesName("__all__")
} }
predicate all_defined(ModuleValue exporter) { from ImportStar imp, Module exporter
exporter.isBuiltin() where
or exporter = ImportResolution::getModuleImportedByImportStar(imp) and
exporter.getScope().(ImportTimeScope).definesName("__all__") not all_defined(exporter)
or
exporter.getScope().getInitModule().(ImportTimeScope).definesName("__all__")
}
from ImportStar imp, ModuleValue exporter
where import_star(imp, exporter) and not all_defined(exporter) and not exporter.isAbsent()
select imp, select imp,
"Import pollutes the enclosing namespace, as the imported module $@ does not define '__all__'.", "Import pollutes the enclosing namespace, as the imported module $@ does not define '__all__'.",
exporter, exporter.getName() exporter, exporter.getName()

View File

@@ -12,9 +12,10 @@
*/ */
import python import python
private import LegacyPointsTo
import Variables.Definition import Variables.Definition
import semmle.python.ApiGraphs import semmle.python.ApiGraphs
private import semmle.python.dataflow.new.internal.ImportResolution
private import semmle.python.dataflow.new.internal.DataFlowDispatch
private predicate is_pytest_fixture(Import imp, Variable name) { private predicate is_pytest_fixture(Import imp, Variable name) {
exists(Alias a, API::Node pytest_fixture, API::Node decorator | exists(Alias a, API::Node pytest_fixture, API::Node decorator |
@@ -95,7 +96,7 @@ private string typehint_annotation_in_module(Module module_scope) {
or or
annotation = any(FunctionExpr f).getReturns().getASubExpression*() annotation = any(FunctionExpr f).getReturns().getASubExpression*()
| |
annotation.(ExprWithPointsTo).pointsTo(Value::forString(result)) and annotation.getText() = result and
annotation.getEnclosingModule() = module_scope annotation.getEnclosingModule() = module_scope
) )
} }
@@ -143,9 +144,8 @@ predicate unused_import(Import imp, Variable name) {
not imported_module_used_in_doctest(imp) and not imported_module_used_in_doctest(imp) and
not imported_alias_used_in_typehint(imp, name) and not imported_alias_used_in_typehint(imp, name) and
not is_pytest_fixture(imp, name) and not is_pytest_fixture(imp, name) and
// Only consider import statements that actually point-to something (possibly an unknown module). // Only consider import statements in reachable code.
// If this is not the case, it's likely that the import statement never gets executed. Reachability::likelyReachable(imp.getAName().getValue().getAFlowNode().getBasicBlock())
imp.getAName().getValue().(ExprWithPointsTo).pointsTo(_)
} }
from Stmt s, Variable name from Stmt s, Variable name

View File

@@ -13,8 +13,8 @@
*/ */
import python import python
private import LegacyPointsTo import semmle.python.Metrics
from FunctionMetricsWithPointsTo func, int complexity from FunctionMetrics func, int complexity
where complexity = func.getCyclomaticComplexity() where complexity = func.getCyclomaticComplexity()
select func, complexity order by complexity desc select func, complexity order by complexity desc

View File

@@ -11,8 +11,8 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.ImportResolution
from ModuleValue m, int n from Module m, int n
where n = count(ModuleValue imp | imp = m.getAnImportedModule()) where n = count(Module imp | ImportResolution::imports(m, imp))
select m.getScope(), n select m, n

View File

@@ -11,8 +11,8 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.ImportResolution
from ModuleValue m, int n from Module m, int n
where n = count(ModuleValue imp | imp = m.getAnImportedModule+() and imp != m) where n = count(Module imp | ImportResolution::imports+(m, imp) and imp != m)
select m.getScope(), n select m, n

View File

@@ -17,7 +17,7 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.DataFlowDispatch
predicate needs_docstring(Scope s) { predicate needs_docstring(Scope s) {
s.isPublic() and s.isPublic() and
@@ -29,15 +29,15 @@ predicate needs_docstring(Scope s) {
} }
predicate function_needs_docstring(FunctionMetrics f) { predicate function_needs_docstring(FunctionMetrics f) {
not exists(FunctionValue fo, FunctionValue base | fo.overrides(base) and fo.getScope() = f | not exists(Function base |
not function_needs_docstring(base.getScope()) DuckTyping::overridesMethod(f) and
base.getScope() = getADirectSuperclass+(f.getScope()) and
base.getName() = f.getName() and
not function_needs_docstring(base)
) and ) and
f.getName() != "lambda" and f.getName() != "lambda" and
(f.getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 and (f.getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 and
not exists(PythonPropertyObject p | not DuckTyping::isPropertyAccessor(f)
p.getGetter().getFunction() = f or
p.getSetter().getFunction() = f
)
} }
string scope_type(Scope s) { string scope_type(Scope s) {

View File

@@ -12,10 +12,10 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.ApiGraphs
predicate originIsLocals(ControlFlowNodeWithPointsTo n) { predicate originIsLocals(ControlFlowNode n) {
n.pointsTo(_, _, Value::named("locals").getACall()) API::builtin("locals").getReturn().getAValueReachableFromSource().asCfgNode() = n
} }
predicate modification_of_locals(ControlFlowNode f) { predicate modification_of_locals(ControlFlowNode f) {
@@ -37,5 +37,5 @@ where
// in module level scope `locals() == globals()` // in module level scope `locals() == globals()`
// see https://docs.python.org/3/library/functions.html#locals // see https://docs.python.org/3/library/functions.html#locals
// FP report in https://github.com/github/codeql/issues/6674 // FP report in https://github.com/github/codeql/issues/6674
not a.getScope() instanceof ModuleScope not a.getScope() instanceof Module
select a, "Modification of the locals() dictionary will have no effect on the local variables." select a, "Modification of the locals() dictionary will have no effect on the local variables."

View File

@@ -12,16 +12,15 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.DataFlowDispatch
from For loop, ControlFlowNodeWithPointsTo iter, Value v, ClassValue t, ControlFlowNode origin from For loop, Expr iter, Class cls
where where
loop.getIter().getAFlowNode() = iter and iter = loop.getIter() and
iter.pointsTo(_, v, origin) and classInstanceTracker(cls).asExpr() = iter and
v.getClass() = t and not DuckTyping::isIterable(cls) and
not t.isIterable() and not DuckTyping::isDescriptor(cls) and
not t.failedInference(_) and not (loop.isAsync() and DuckTyping::hasMethod(cls, "__aiter__")) and
not v = Value::named("None") and not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(cls))
not t.isDescriptorType() select loop, "This for-loop may attempt to iterate over a $@ of class $@.", iter,
select loop, "This for-loop may attempt to iterate over a $@ of class $@.", origin, "non-iterable instance", cls, cls.getName()
"non-iterable instance", t, t.getName()

View File

@@ -13,7 +13,7 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.DataFlowDispatch
predicate calls_close(Call c) { exists(Attribute a | c.getFunc() = a and a.getName() = "close") } predicate calls_close(Call c) { exists(Attribute a | c.getFunc() = a and a.getName() = "close") }
@@ -23,18 +23,12 @@ predicate only_stmt_in_finally(Try t, Call c) {
) )
} }
predicate points_to_context_manager(ControlFlowNodeWithPointsTo f, ClassValue cls) { from Call close, Try t, Class cls
forex(Value v | f.pointsTo(v) | v.getClass() = cls) and
cls.isContextManager()
}
from Call close, Try t, ClassValue cls
where where
only_stmt_in_finally(t, close) and only_stmt_in_finally(t, close) and
calls_close(close) and calls_close(close) and
exists(ControlFlowNode f | f = close.getFunc().getAFlowNode().(AttrNode).getObject() | classInstanceTracker(cls).asExpr() = close.getFunc().(Attribute).getObject() and
points_to_context_manager(f, cls) DuckTyping::isContextManager(cls)
)
select close, select close,
"Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement.", "Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement.",
cls, cls.getName() cls, cls.getName()

View File

@@ -13,7 +13,7 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.ApiGraphs
predicate func_with_side_effects(Expr e) { predicate func_with_side_effects(Expr e) {
exists(string name | name = e.(Attribute).getName() or name = e.(Name).getId() | exists(string name | name = e.(Attribute).getName() or name = e.(Name).getId() |
@@ -24,11 +24,11 @@ predicate func_with_side_effects(Expr e) {
} }
predicate call_with_side_effect(Call e) { predicate call_with_side_effect(Call e) {
e.getAFlowNode() = Value::named("subprocess.call").getACall() e.getAFlowNode() =
or API::moduleImport("subprocess")
e.getAFlowNode() = Value::named("subprocess.check_call").getACall() .getMember(["call", "check_call", "check_output"])
or .getACall()
e.getAFlowNode() = Value::named("subprocess.check_output").getACall() .asCfgNode()
} }
predicate probable_side_effect(Expr e) { predicate probable_side_effect(Expr e) {

View File

@@ -12,7 +12,6 @@
*/ */
import python import python
private import LegacyPointsTo
predicate main_eq_name(If i) { predicate main_eq_name(If i) {
exists(Name n, StringLiteral m, Compare c | exists(Name n, StringLiteral m, Compare c |
@@ -32,10 +31,19 @@ predicate is_print_stmt(Stmt s) {
) )
} }
/**
* Holds if module `m` is likely used as a module (imported by another module),
* as opposed to being exclusively used as a script.
*/
predicate is_used_as_module(Module m) {
m.isPackageInit()
or
exists(ImportingStmt i | i.getAnImportedModuleName() = m.getName())
}
from Stmt p from Stmt p
where where
is_print_stmt(p) and is_print_stmt(p) and
// TODO: Need to discuss how we would like to handle ModuleObject.getKind in the glorious future is_used_as_module(p.getScope()) and
exists(ModuleValue m | m.getScope() = p.getScope() and m.isUsedAsModule()) and
not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p) not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p)
select p, "Print statement may execute during import." select p, "Print statement may execute during import."

View File

@@ -13,7 +13,7 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.ApiGraphs
predicate isInsideLoop(AstNode node) { predicate isInsideLoop(AstNode node) {
node.getParentNode() instanceof While node.getParentNode() instanceof While
@@ -33,9 +33,9 @@ where
not isInsideLoop(del) and not isInsideLoop(del) and
// False positive: calling `sys.exc_info` within a function results in a // False positive: calling `sys.exc_info` within a function results in a
// reference cycle, and an explicit call to `del` helps break this cycle. // reference cycle, and an explicit call to `del` helps break this cycle.
not exists(FunctionValue ex | not exists(API::CallNode call |
ex = Value::named("sys.exc_info") and call = API::moduleImport("sys").getMember("exc_info").getACall() and
ex.getACall().getScope() = f call.getScope() = f
) )
select del, "Unnecessary deletion of local variable $@ in function $@.", e, e.toString(), f, select del, "Unnecessary deletion of local variable $@ in function $@.", e, e.toString(), f,
f.getName() f.getName()

View File

@@ -13,7 +13,7 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.ApiGraphs
predicate typing_import(ImportingStmt is) { predicate typing_import(ImportingStmt is) {
exists(Module m | exists(Module m |
@@ -34,11 +34,7 @@ predicate unique_yield(Stmt s) {
/** Holds if `contextlib.suppress` may be used in the same scope as `s` */ /** Holds if `contextlib.suppress` may be used in the same scope as `s` */
predicate suppression_in_scope(Stmt s) { predicate suppression_in_scope(Stmt s) {
exists(With w | exists(With w |
w.getContextExpr() w.getContextExpr() = API::moduleImport("contextlib").getMember("suppress").getACall().asExpr() and
.(Call)
.getFunc()
.(ExprWithPointsTo)
.pointsTo(Value::named("contextlib.suppress")) and
w.getScope() = s.getScope() w.getScope() = s.getScope()
) )
} }

View File

@@ -12,11 +12,49 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.dataflow.new.internal.DataFlowDispatch
private import semmle.python.dataflow.new.internal.Builtins
private import semmle.python.ApiGraphs
from Call call, ClassValue ex /**
* Holds if `cls` is a user-defined exception class, i.e. it transitively
* extends one of the builtin exception base classes.
*/
predicate isUserDefinedExceptionClass(Class cls) {
cls.getABase() =
API::builtin(["BaseException", "Exception"]).getAValueReachableFromSource().asExpr()
or
isUserDefinedExceptionClass(getADirectSuperclass(cls))
}
/**
* Gets the name of a builtin exception class.
*/
string getBuiltinExceptionName() {
result = Builtins::getBuiltinName() and
(
result.matches("%Error") or
result.matches("%Exception") or
result.matches("%Warning") or
result =
["GeneratorExit", "KeyboardInterrupt", "StopIteration", "StopAsyncIteration", "SystemExit"]
)
}
/**
* Holds if `call` is an instantiation of an exception class.
*/
predicate isExceptionInstantiation(Call call) {
exists(Class cls |
classTracker(cls).asExpr() = call.getFunc() and
isUserDefinedExceptionClass(cls)
)
or
call.getFunc() = API::builtin(getBuiltinExceptionName()).getAValueReachableFromSource().asExpr()
}
from Call call
where where
call.getFunc().(ExprWithPointsTo).pointsTo(ex) and isExceptionInstantiation(call) and
ex.getASuperType() = ClassValue::exception() and
exists(ExprStmt s | s.getValue() = call) exists(ExprStmt s | s.getValue() = call)
select call, "Instantiating an exception, but not raising it, has no effect." select call, "Instantiating an exception, but not raising it, has no effect."

View File

@@ -12,10 +12,12 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.ApiGraphs
from CallNode call, string name from CallNode call, string name
where call.getFunction().(ControlFlowNodeWithPointsTo).pointsTo(Value::siteQuitter(name)) where
name = ["exit", "quit"] and
call = API::builtin(name).getACall().asCfgNode()
select call, select call,
"The '" + name + "The '" + name +
"' site.Quitter object may not exist if the 'site' module is not loaded or is modified." "' site.Quitter object may not exist if the 'site' module is not loaded or is modified."

View File

@@ -12,7 +12,7 @@
*/ */
import python import python
private import LegacyPointsTo private import semmle.python.ApiGraphs
import Definition import Definition
predicate is_increment(Stmt s) { predicate is_increment(Stmt s) {
@@ -41,23 +41,16 @@ predicate one_item_only(For f) {
) )
} }
predicate points_to_call_to_range(ControlFlowNode f) { /** Holds if `node` is a call to `range`, `xrange`, or `list(range(...))`. */
/* (x)range is a function in Py2 and a class in Py3, so we must treat it as a plain object */ predicate call_to_range(DataFlow::Node node) {
exists(Value range | node = API::builtin(["range", "xrange"]).getACall()
range = Value::named("range") or
range = Value::named("xrange")
|
f = range.getACall()
)
or or
/* In case points-to fails due to 'from six.moves import range' or similar. */ /* Handle 'from six.moves import range' or similar. */
exists(string range | f.getNode().(Call).getFunc().(Name).getId() = range | node = API::moduleImport("six").getMember("moves").getMember(["range", "xrange"]).getACall()
range = "range" or range = "xrange"
)
or or
/* Handle list(range(...)) and list(list(range(...))) */ /* Handle list(range(...)) and list(list(range(...))) */
f.(CallNode).(ControlFlowNodeWithPointsTo).pointsTo().getClass() = ClassValue::list() and node = API::builtin("list").getACall() and
points_to_call_to_range(f.(CallNode).getArg(0)) call_to_range(node.(DataFlow::CallCfgNode).getArg(0))
} }
/** Whether n is a use of a variable that is a not effectively a constant. */ /** Whether n is a use of a variable that is a not effectively a constant. */
@@ -102,8 +95,8 @@ from For f, Variable v, string msg
where where
f.getTarget() = v.getAnAccess() and f.getTarget() = v.getAnAccess() and
not f.getAStmt().contains(v.getAnAccess()) and not f.getAStmt().contains(v.getAnAccess()) and
not points_to_call_to_range(f.getIter().getAFlowNode()) and not call_to_range(DataFlow::exprNode(f.getIter())) and
not points_to_call_to_range(get_comp_iterable(f)) and not call_to_range(DataFlow::exprNode(get_comp_iterable(f).getNode())) and
not name_acceptable_for_unused_variable(v) and not name_acceptable_for_unused_variable(v) and
not f.getScope().getName() = "genexpr" and not f.getScope().getName() = "genexpr" and
not empty_loop(f) and not empty_loop(f) and

View File

@@ -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 |

View File

@@ -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. |

View File

@@ -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__'. |

View File

@@ -1,2 +1 @@
| inconsistent_mro.py:9:1:9:14 | class Z | Construction of class Z can fail due to invalid method resolution order(MRO) for bases $@ and $@. | inconsistent_mro.py:3:1:3:16 | class X | X | inconsistent_mro.py:6:1:6:11 | class Y | Y | | inconsistent_mro.py:9:1:9:14 | Class Z | Construction of class Z can fail due to invalid method resolution order(MRO) for bases $@ and $@. | inconsistent_mro.py:3:1:3:16 | Class X | X | inconsistent_mro.py:6:1:6:11 | Class Y | Y |
| inconsistent_mro.py:16:1:16:19 | class N | Construction of class N can fail due to invalid method resolution order(MRO) for bases $@ and $@. | file://:Compiled Code:0:0:0:0 | builtin-class object | object | inconsistent_mro.py:12:1:12:8 | class O | O |

View File

@@ -1,2 +1 @@
| async_iterator.py:26:11:26:34 | For | This for-loop may attempt to iterate over a $@ of class $@. | async_iterator.py:26:20:26:33 | ControlFlowNode for MissingAiter() | non-iterable instance | async_iterator.py:13:1:13:19 | class MissingAiter | MissingAiter | | async_iterator.py:26:11:26:34 | For | This for-loop may attempt to iterate over a $@ of class $@. | async_iterator.py:26:20:26:33 | MissingAiter() | non-iterable instance | async_iterator.py:13:1:13:19 | Class MissingAiter | MissingAiter |
| statements_test.py:34:5:34:19 | For | This for-loop may attempt to iterate over a $@ of class $@. | statements_test.py:34:18:34:18 | ControlFlowNode for IntegerLiteral | non-iterable instance | file://:0:0:0:0 | builtin-class int | int |

View File

@@ -1,4 +1,4 @@
| wrong_arguments.py:65:1:65:7 | F0() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:4:5:4:26 | Function F0.__init__ | F0.__init__ | | wrong_arguments.py:65:1:65:7 | F0() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ |
| wrong_arguments.py:66:1:66:7 | F1() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:8:5:8:36 | Function F1.__init__ | F1.__init__ | | wrong_arguments.py:66:1:66:7 | F1() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ |
| wrong_arguments.py:67:1:67:12 | F2() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:12:5:12:30 | Function F2.__init__ | F2.__init__ | | wrong_arguments.py:67:1:67:12 | F2() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:12:5:12:30 | Function __init__ | F2.__init__ |
| wrong_arguments.py:92:1:92:27 | F6() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ | | wrong_arguments.py:92:1:92:27 | F6() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |

View File

@@ -1,15 +1,13 @@
| wrong_arguments.py:37:1:37:4 | F0() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:4:5:4:26 | Function F0.__init__ | F0.__init__ | | wrong_arguments.py:37:1:37:4 | F0() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ |
| wrong_arguments.py:38:1:38:4 | F1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:8:5:8:36 | Function F1.__init__ | F1.__init__ | | wrong_arguments.py:38:1:38:4 | F1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ |
| wrong_arguments.py:39:1:39:4 | F2() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:12:5:12:30 | Function F2.__init__ | F2.__init__ | | wrong_arguments.py:39:1:39:4 | F2() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:12:5:12:30 | Function __init__ | F2.__init__ |
| wrong_arguments.py:40:1:40:4 | F3() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:16:5:16:40 | Function F3.__init__ | F3.__init__ | | wrong_arguments.py:40:1:40:4 | F3() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:16:5:16:40 | Function __init__ | F3.__init__ |
| wrong_arguments.py:41:1:41:4 | F4() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:20:5:20:31 | Function F4.__init__ | F4.__init__ | | wrong_arguments.py:41:1:41:4 | F4() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:20:5:20:31 | Function __init__ | F4.__init__ |
| wrong_arguments.py:42:1:42:4 | F5() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:24:5:24:42 | Function F5.__init__ | F5.__init__ | | wrong_arguments.py:42:1:42:4 | F5() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:24:5:24:42 | Function __init__ | F5.__init__ |
| wrong_arguments.py:43:1:43:5 | F6() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ | | wrong_arguments.py:43:1:43:5 | F6() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
| wrong_arguments.py:44:1:44:7 | F7() | Call to $@ with too few arguments; should be no fewer than 3. | wrong_arguments.py:32:5:32:33 | Function F7.__init__ | F7.__init__ | | wrong_arguments.py:44:1:44:7 | F7() | Call to $@ with too few arguments; should be no fewer than 3. | wrong_arguments.py:32:5:32:33 | Function __init__ | F7.__init__ |
| wrong_arguments.py:48:1:48:7 | F0() | Call to $@ with too many arguments; should be no more than 1. | wrong_arguments.py:4:5:4:26 | Function F0.__init__ | F0.__init__ | | wrong_arguments.py:48:1:48:7 | F0() | Call to $@ with too many arguments; should be no more than 1. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ |
| wrong_arguments.py:49:1:49:9 | F1() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:8:5:8:36 | Function F1.__init__ | F1.__init__ | | wrong_arguments.py:49:1:49:9 | F1() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ |
| wrong_arguments.py:50:1:50:9 | F5() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:24:5:24:42 | Function F5.__init__ | F5.__init__ | | wrong_arguments.py:50:1:50:9 | F5() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:24:5:24:42 | Function __init__ | F5.__init__ |
| wrong_arguments.py:51:1:51:9 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ | | wrong_arguments.py:51:1:51:9 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
| wrong_arguments.py:52:1:52:11 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ | | wrong_arguments.py:52:1:52:11 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
| wrong_arguments.py:85:1:85:12 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
| wrong_arguments.py:86:1:86:7 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |

View File

@@ -1,2 +1,2 @@
| should_be_context_manager.py:3:1:3:22 | class MegaDel | Class MegaDel implements __del__ (presumably to release some resource). Consider making it a context manager. | | should_be_context_manager.py:3:1:3:22 | Class MegaDel | Class MegaDel implements __del__ (presumably to release some resource). Consider making it a context manager. |
| should_be_context_manager.py:16:1:16:22 | class MiniDel | Class MiniDel implements __del__ (presumably to release some resource). Consider making it a context manager. | | should_be_context_manager.py:16:1:16:22 | Class MiniDel | Class MiniDel implements __del__ (presumably to release some resource). Consider making it a context manager. |

View File

@@ -1,4 +1,3 @@
| exceptions_test.py:51:5:51:25 | ExceptStmt | Non-exception $@ in exception handler which will never match raised exception. | exceptions_test.py:33:1:33:28 | ControlFlowNode for ClassExpr | class 'NotException1' | | exceptions_test.py:51:5:51:25 | ExceptStmt | Non-exception class 'NotException1' in exception handler which will never match raised exception. |
| exceptions_test.py:54:5:54:25 | ExceptStmt | Non-exception $@ in exception handler which will never match raised exception. | exceptions_test.py:36:1:36:28 | ControlFlowNode for ClassExpr | class 'NotException2' | | exceptions_test.py:54:5:54:25 | ExceptStmt | Non-exception class 'NotException2' in exception handler which will never match raised exception. |
| exceptions_test.py:138:5:138:22 | ExceptStmt | Non-exception $@ in exception handler which will never match raised exception. | exceptions_test.py:133:12:133:14 | ControlFlowNode for FloatLiteral | instance of 'float' | | pypy_test.py:14:5:14:14 | ExceptStmt | Non-exception class 'int' in exception handler which will never match raised exception. |
| pypy_test.py:14:5:14:14 | ExceptStmt | Non-exception $@ in exception handler which will never match raised exception. | pypy_test.py:14:12:14:13 | ControlFlowNode for IntegerLiteral | instance of 'int' |

View File

@@ -1,2 +1,2 @@
| expressions_test.py:89:8:89:15 | Compare | This test may raise an Exception as the $@ may be of non-container class $@. | expressions_test.py:88:11:88:17 | ControlFlowNode for XIter() | target | expressions_test.py:77:1:77:20 | class XIter | XIter | | expressions_test.py:89:8:89:15 | Compare | This test may raise an Exception as the $@ may be of non-container class $@. | expressions_test.py:88:11:88:17 | ControlFlowNode for XIter() | target | expressions_test.py:77:1:77:20 | Class XIter | XIter |
| expressions_test.py:91:8:91:19 | Compare | This test may raise an Exception as the $@ may be of non-container class $@. | expressions_test.py:88:11:88:17 | ControlFlowNode for XIter() | target | expressions_test.py:77:1:77:20 | class XIter | XIter | | expressions_test.py:91:8:91:19 | Compare | This test may raise an Exception as the $@ may be of non-container class $@. | expressions_test.py:88:11:88:17 | ControlFlowNode for XIter() | target | expressions_test.py:77:1:77:20 | Class XIter | XIter |

View File

@@ -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 |

View File

@@ -1,3 +1,3 @@
| functions_test.py:95:5:95:40 | Function DeprecatedSliceMethods.__getslice__ | __getslice__ method has been deprecated since Python 2.0. | | functions_test.py:95:5:95:40 | Function __getslice__ | __getslice__ method has been deprecated since Python 2.0. |
| functions_test.py:98:5:98:47 | Function DeprecatedSliceMethods.__setslice__ | __setslice__ method has been deprecated since Python 2.0. | | functions_test.py:98:5:98:47 | Function __setslice__ | __setslice__ method has been deprecated since Python 2.0. |
| functions_test.py:101:5:101:40 | Function DeprecatedSliceMethods.__delslice__ | __delslice__ method has been deprecated since Python 2.0. | | functions_test.py:101:5:101:40 | Function __delslice__ | __delslice__ method has been deprecated since Python 2.0. |

View File

@@ -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. |

View File

@@ -1,5 +1,6 @@
| imports_test.py:8:1:8:19 | Import | The module 'imports_test' imports itself. | | imports_test.py:8:1:8:19 | Import | The module 'imports_test' imports itself. |
| pkg_notok/__init__.py:4:1:4:16 | Import | The module 'pkg_notok' imports itself. | | pkg_notok/__init__.py:4:1:4:16 | Import | The module 'pkg_notok' imports itself. |
| pkg_notok/__init__.py:10:1:10:20 | Import | The module 'pkg_notok' imports itself. |
| pkg_notok/__init__.py:12:1:12:25 | Import | The module 'pkg_notok' imports itself. | | pkg_notok/__init__.py:12:1:12:25 | Import | The module 'pkg_notok' imports itself. |
| pkg_notok/__init__.py:13:1:13:37 | Import | The module 'pkg_notok' imports itself. | | pkg_notok/__init__.py:13:1:13:37 | Import | The module 'pkg_notok' imports itself. |
| pkg_notok/__init__.py:14:1:14:23 | from pkg_notok import * | The module 'pkg_notok' imports itself. | | pkg_notok/__init__.py:14:1:14:23 | from pkg_notok import * | The module 'pkg_notok' imports itself. |

View File

@@ -1,6 +1,6 @@
| code.py:35:1:35:21 | Function exceptions | 5 |
| code.py:23:1:23:17 | Function nested | 4 | | code.py:23:1:23:17 | Function nested | 4 |
| code.py:12:1:12:21 | Function two_branch | 3 | | code.py:12:1:12:21 | Function two_branch | 3 |
| code.py:6:1:6:18 | Function one_branch | 2 | | code.py:6:1:6:18 | Function one_branch | 2 |
| code.py:35:1:35:21 | Function exceptions | 2 |
| code.py:1:1:1:16 | Function f_linear | 1 | | code.py:1:1:1:16 | Function f_linear | 1 |
| code.py:45:1:45:39 | Function must_be_positive | 1 | | code.py:45:1:45:39 | Function must_be_positive | 1 |

View File

@@ -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 |

View File

@@ -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 |