Compare commits

...

45 Commits

Author SHA1 Message Date
Taus
3277671428 Python: Port ShadowGlobal.ql
No test changes.
2026-03-09 17:22:13 +00:00
Taus
10739ec90a Python: Port UndefinedExport.ql
No test changes.
2026-03-09 17:22:13 +00:00
Taus
27bcd490c5 Python: Port UndefinedGlobal.ql
Uses most of the machinery we have built up by now to implement this, so
hopefully it should be an easy comparison.

Also, extends the `monkeyPatchedBuiltin` with an additional way to patch
built-ins (because this was needed to get a test to pass).

No test changes.
2026-03-09 17:22:13 +00:00
Taus
39049e3fcc Python: Model undefinedness
Adds `maybeUndefined` to the reachability module, modelling which
names/variables may be undefined at runtime. The approach is very close
to the one used in points-to, though it of course relies on our new
modelling of exceptions/reachability instead.
2026-03-09 17:22:12 +00:00
Taus
7a6777b558 Python: Port UnusedParameter.ql
No test changes.
2026-03-09 17:22:12 +00:00
Taus
9a8fd457f6 Python: Port UndefinedPlaceHolder.ql
I'll be honest -- I have no idea what this query is for. However, the
ported version seems no less reasonable than the original. No test
changes.
2026-03-09 17:22:12 +00:00
Taus
4c4fd32a5a Python: Port UnusedModuleVariable.ql
A simple use of the ImportResolution module. No test changes.
2026-03-09 17:22:12 +00:00
Taus
5ba6211ec2 Python: Port UninitializedLocal.ql
Uses the new ExceptionTypes machinery. No test changes.
2026-03-09 17:22:12 +00:00
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
68 changed files with 1382 additions and 538 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,9 +15,9 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch
private import semmle.python.ApiGraphs
import Shadowing
import semmle.python.types.Builtins
predicate shadows(Name d, GlobalVariable g, Function scope, int line) {
g.getScope() = scope.getScope() and
@@ -28,34 +28,19 @@ predicate shadows(Name d, GlobalVariable g, Function scope, int line) {
) and
not exists(Import il, Import ig, Name gd | il.contains(d) and gd.defines(g) and ig.contains(gd)) and
not exists(Assign a | a.getATarget() = d and a.getValue() = g.getAnAccess()) and
not exists(Builtin::builtin(g.getId())) and
not DuckTyping::globallyDefinedName(g.getId()) and
d.getLocation().getStartLine() = line and
exists(Name defn | defn.defines(g) | not exists(If i | i.isNameEqMain() | i.contains(defn))) and
not optimizing_parameter(d)
}
/* pytest dynamically populates its namespace so, we cannot look directly for the pytest.fixture function */
AttrNode pytest_fixture_attr() {
exists(ModuleValue pytest |
result.getObject("fixture").(ControlFlowNodeWithPointsTo).pointsTo(pytest)
)
}
Value pytest_fixture() {
exists(CallNode call |
call.getFunction() = pytest_fixture_attr()
or
call.getFunction().(CallNode).getFunction() = pytest_fixture_attr()
|
call.(ControlFlowNodeWithPointsTo).pointsTo(result)
)
}
/* pytest fixtures require that the parameter name is also a global */
predicate assigned_pytest_fixture(GlobalVariable v) {
exists(NameNode def |
exists(NameNode def, API::Node fixture |
fixture = API::moduleImport("pytest").getMember("fixture") and
def.defines(v) and
def.(DefinitionNode).getValue().(ControlFlowNodeWithPointsTo).pointsTo(pytest_fixture())
def.(DefinitionNode).getValue() =
[fixture.getACall(), fixture.getReturn().getACall()].asCfgNode()
)
}

View File

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

View File

@@ -13,7 +13,9 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.ImportResolution
private import semmle.python.dataflow.new.internal.DataFlowDispatch
private import semmle.python.ApiGraphs
/** Whether name is declared in the __all__ list of this module */
predicate declaredInAll(Module m, StringLiteral name) {
@@ -25,62 +27,42 @@ predicate declaredInAll(Module m, StringLiteral name) {
)
}
predicate mutates_globals(ModuleValue m) {
predicate mutates_globals(Module m) {
exists(CallNode globals |
globals = Value::named("globals").(FunctionValue).getACall() and
globals.getScope() = m.getScope()
globals = API::builtin("globals").getACall().asCfgNode() and
globals.getScope() = m
|
exists(AttrNode attr | attr.getObject() = globals)
or
exists(SubscriptNode sub | sub.getObject() = globals and sub.isStore())
)
or
// Enum (added in 3.4) has method `_convert_` that alters globals
// This was called `_convert` until 3.8, but that name will be removed in 3.9
exists(ClassValue enum_class |
enum_class.getASuperType() = Value::named("enum.Enum") and
(
// In Python < 3.8, Enum._convert can be found with points-to
exists(Value enum_convert |
enum_convert = enum_class.attr("_convert") and
exists(CallNode call | call.getScope() = m.getScope() |
enum_convert.getACall() = call or
call.getFunction().(ControlFlowNodeWithPointsTo).pointsTo(enum_convert)
)
)
or
// In Python 3.8, Enum._convert_ is implemented using a metaclass, and our points-to
// analysis doesn't handle that well enough. So we need a special case for this
not exists(enum_class.attr("_convert")) and
exists(CallNode call | call.getScope() = m.getScope() |
call.getFunction()
.(AttrNode)
.getObject(["_convert", "_convert_"])
.(ControlFlowNodeWithPointsTo)
.pointsTo() = enum_class
)
)
// Enum._convert_ is a metaclass method that alters the module's globals.
// It was called `_convert` until Python 3.8, when it was renamed to `_convert_`.
API::moduleImport("enum")
.getMember("Enum")
.getASubclass*()
.getMember(["_convert", "_convert_"])
.getACall()
.getScope() = m
}
predicate is_exported_submodule_name(Module m, string exported_name) {
m.getShortName() = "__init__" and
exists(m.getPackage().getSubModule(exported_name))
}
predicate contains_unknown_import_star(Module m) {
exists(ImportStar imp | imp.getScope() = m |
not exists(ImportResolution::getModuleImportedByImportStar(imp))
)
}
predicate is_exported_submodule_name(ModuleValue m, string exported_name) {
m.getScope().getShortName() = "__init__" and
exists(m.getScope().getPackage().getSubModule(exported_name))
}
predicate contains_unknown_import_star(ModuleValue m) {
exists(ImportStarNode imp | imp.getEnclosingModule() = m.getScope() |
imp.getModule().(ControlFlowNodeWithPointsTo).pointsTo().isAbsent()
or
not exists(imp.getModule().(ControlFlowNodeWithPointsTo).pointsTo())
)
}
from ModuleValue m, StringLiteral name, string exported_name
from Module m, StringLiteral name, string exported_name
where
declaredInAll(m.getScope(), name) and
declaredInAll(m, name) and
exported_name = name.getText() and
not m.hasAttribute(exported_name) and
not ImportResolution::module_export(m, exported_name, _) and
not is_exported_submodule_name(m, exported_name) and
not contains_unknown_import_star(m) and
not mutates_globals(m)

View File

@@ -11,9 +11,10 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.ImportResolution
private import semmle.python.dataflow.new.internal.DataFlowDispatch
private import semmle.python.ApiGraphs
private import semmle.python.types.ImportTime
import Variables.MonkeyPatched
import Loop
predicate guarded_against_name_error(Name u) {
@@ -32,10 +33,13 @@ predicate guarded_against_name_error(Name u) {
}
predicate contains_unknown_import_star(Module m) {
exists(ImportStar imp | imp.getScope() = m |
exists(ModuleValue imported | imported.importedAs(imp.getImportedModuleName()) |
not imported.hasCompleteExportInfo()
)
exists(ImportStar imp, Module imported |
imp.getScope() = m and
ImportResolution::getModuleImportedByImportStar(imp) = imported
|
// The imported module dynamically creates attributes, so we can't
// enumerate its exports.
exists(Function f | f.getName() = "__getattr__" and f.getScope() = imported)
)
}
@@ -60,9 +64,9 @@ predicate undefined_use_in_function(Name u) {
)
) and
not u.getEnclosingModule().(ImportTimeScope).definesName(u.getId()) and
not exists(ModuleValue m | m.getScope() = u.getEnclosingModule() | m.hasAttribute(u.getId())) and
not globallyDefinedName(u.getId()) and
not exists(SsaVariableWithPointsTo var | var.getAUse().getNode() = u and not var.maybeUndefined()) and
not ImportResolution::module_export(u.getEnclosingModule(), u.getId(), _) and
not DuckTyping::globallyDefinedName(u.getId()) and
not Reachability::maybeUndefined(u) and
not guarded_against_name_error(u) and
not (u.getEnclosingModule().isPackageInit() and u.getId() = "__path__")
}
@@ -70,20 +74,18 @@ predicate undefined_use_in_function(Name u) {
predicate undefined_use_in_class_or_module(Name u) {
exists(GlobalVariable v | u.uses(v)) and
not u.getScope().getScope*() instanceof Function and
exists(SsaVariableWithPointsTo var | var.getAUse().getNode() = u | var.maybeUndefined()) and
Reachability::maybeUndefined(u) and
not guarded_against_name_error(u) and
not exists(ModuleValue m | m.getScope() = u.getEnclosingModule() | m.hasAttribute(u.getId())) and
not u.getEnclosingModule().(ImportTimeScope).definesName(u.getId()) and
not ImportResolution::module_export(u.getEnclosingModule(), u.getId(), _) and
not (u.getEnclosingModule().isPackageInit() and u.getId() = "__path__") and
not globallyDefinedName(u.getId())
not DuckTyping::globallyDefinedName(u.getId())
}
predicate use_of_exec(Module m) {
exists(Exec exec | exec.getScope() = m)
or
exists(CallNode call, FunctionValue exec | exec.getACall() = call and call.getScope() = m |
exec = Value::named("exec") or
exec = Value::named("execfile")
)
API::builtin(["exec", "execfile"]).getACall().getScope() = m
}
predicate undefined_use(Name u) {
@@ -92,11 +94,10 @@ predicate undefined_use(Name u) {
or
undefined_use_in_function(u)
) and
not monkey_patched_builtin(u.getId()) and
not DuckTyping::monkeyPatchedBuiltin(u.getId()) and
not contains_unknown_import_star(u.getEnclosingModule()) and
not use_of_exec(u.getEnclosingModule()) and
not exists(u.getVariable().getAStore()) and
not u.(ExprWithPointsTo).pointsTo(_) and
not probably_defined_in_loop(u)
}

View File

@@ -12,17 +12,23 @@
*/
import python
import Variables.MonkeyPatched
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.ImportResolution
private import semmle.python.dataflow.new.internal.DataFlowDispatch
private import semmle.python.types.ImportTime
/* Global Stuff */
predicate not_a_global(PlaceHolder use) {
not ImportResolution::module_export(use.getEnclosingModule(), use.getId(), _) and
not DuckTyping::globallyDefinedName(use.getId()) and
not DuckTyping::monkeyPatchedBuiltin(use.getId())
}
/* Local variable part */
predicate initialized_as_local(PlaceHolder use) {
exists(SsaVariableWithPointsTo l, Function f |
f = use.getScope() and l.getAUse() = use.getAFlowNode()
|
exists(SsaVariable l, Function f | f = use.getScope() and l.getAUse() = use.getAFlowNode() |
l.getVariable() instanceof LocalVariable and
not l.maybeUndefined()
exists(l.getDefinition()) and
not l.getDefinition().isDelete()
)
}
@@ -33,16 +39,6 @@ predicate template_attribute(PlaceHolder use) {
exists(ImportTimeScope cls | cls = enclosing_class(use) | cls.definesName(use.getId()))
}
/* Global Stuff */
predicate not_a_global(PlaceHolder use) {
not exists(PythonModuleObject mo |
mo.hasAttribute(use.getId()) and mo.getModule() = use.getEnclosingModule()
) and
not globallyDefinedName(use.getId()) and
not monkey_patched_builtin(use.getId()) and
not globallyDefinedName(use.getId())
}
from PlaceHolder p
where
not initialized_as_local(p) and

View File

@@ -12,7 +12,7 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch
import Undefined
predicate uninitialized_local(NameNode use) {
@@ -21,16 +21,16 @@ predicate uninitialized_local(NameNode use) {
) and
(
any(Uninitialized uninit).taints(use) and
PointsToInternal::reachableBlock(use.getBasicBlock(), _)
Reachability::likelyReachable(use.getBasicBlock())
or
not exists(EssaVariable var | var.getASourceUse() = use)
)
}
predicate explicitly_guarded(NameNode u) {
exists(Try t |
exists(Try t, ExceptionTypes::NameError nameError |
t.getBody().contains(u.getNode()) and
t.getAHandler().getType().(ExprWithPointsTo).pointsTo(ClassValue::nameError())
nameError.getAUse().asExpr() = t.getAHandler().getType()
)
}

View File

@@ -13,9 +13,21 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.ImportResolution
import Definition
/**
* Whether `name` is exported from module `m`, following Python's convention:
* if `__all__` is defined, use its entries; otherwise, export public names (not starting with `_`).
*/
private predicate isExported(Module m, string name) {
py_exports(m, name)
or
not py_exports(m, _) and
ImportResolution::module_export(m, name, _) and
not name.charAt(0) = "_"
}
/**
* Whether the module contains an __all__ definition,
* but it is more complex than a simple list of strings
@@ -59,7 +71,7 @@ predicate unused_global(Name unused, GlobalVariable v) {
// indirectly
defn.getBasicBlock().reachesExit() and u.getScope() != unused.getScope()
) and
not unused.getEnclosingModule().(ModuleWithPointsTo).getAnExport() = v.getId() and
not isExported(unused.getEnclosingModule(), v.getId()) and
not exists(unused.getParentNode().(ClassDef).getDefinedClass().getADecorator()) and
not exists(unused.getParentNode().(FunctionDef).getDefinedFunction().getADecorator()) and
unused.defines(v) and

View File

@@ -14,11 +14,11 @@
import python
import Definition
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch
predicate unused_parameter(FunctionValue f, LocalVariable v) {
predicate unused_parameter(Function f, LocalVariable v) {
v.isParameter() and
v.getScope() = f.getScope() and
v.getScope() = f and
not name_acceptable_for_unused_variable(v) and
not exists(NameNode u | u.uses(v)) and
not exists(Name inner, LocalVariable iv |
@@ -26,15 +26,19 @@ predicate unused_parameter(FunctionValue f, LocalVariable v) {
)
}
predicate is_abstract(FunctionValue func) {
func.getScope().getADecorator().(Name).getId().matches("%abstract%")
predicate is_abstract(Function func) { func.getADecorator().(Name).getId().matches("%abstract%") }
predicate is_overridden(Function f) {
exists(Class cls | f.getScope() = cls |
DuckTyping::hasMethod(getADirectSubclass(cls), f.getName())
)
}
from PythonFunctionValue f, LocalVariable v
from Function f, LocalVariable v
where
v.getId() != "self" and
unused_parameter(f, v) and
not f.isOverridingMethod() and
not f.isOverriddenMethod() and
not DuckTyping::overridesMethod(f) and
not is_overridden(f) and
not is_abstract(f)
select f, "The parameter '" + v.getId() + "' is never used."

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

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

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

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

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:16:1:16:22 | class MiniDel | Class MiniDel implements __del__ (presumably to release some resource). Consider making it a context manager. |
| should_be_context_manager.py:3:1:3:22 | Class MegaDel | Class MegaDel implements __del__ (presumably to release some resource). Consider making it a context manager. |
| should_be_context_manager.py:16:1:16:22 | Class MiniDel | Class MiniDel implements __del__ (presumably to release some resource). Consider making it a context manager. |

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

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

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:98:5:98:47 | Function DeprecatedSliceMethods.__setslice__ | __setslice__ method has been deprecated since Python 2.0. |
| functions_test.py:101:5:101:40 | Function DeprecatedSliceMethods.__delslice__ | __delslice__ method has been deprecated since Python 2.0. |
| functions_test.py:95:5:95:40 | Function __getslice__ | __getslice__ method has been deprecated since Python 2.0. |
| functions_test.py:98:5:98:47 | Function __setslice__ | __setslice__ method has been deprecated since Python 2.0. |
| functions_test.py:101:5:101:40 | Function __delslice__ | __delslice__ method has been deprecated since Python 2.0. |

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. |
| pkg_notok/__init__.py:4:1:4:16 | Import | The module 'pkg_notok' imports itself. |
| pkg_notok/__init__.py:10:1:10:20 | Import | The module 'pkg_notok' imports itself. |
| pkg_notok/__init__.py:12:1:12:25 | Import | The module 'pkg_notok' imports itself. |
| pkg_notok/__init__.py:13:1:13:37 | Import | The module 'pkg_notok' imports itself. |
| pkg_notok/__init__.py:14:1:14:23 | from pkg_notok import * | The module 'pkg_notok' imports itself. |

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:12:1:12:21 | Function two_branch | 3 |
| code.py:6:1:6:18 | Function one_branch | 2 |
| code.py:35:1:35:21 | Function exceptions | 2 |
| code.py:1:1:1:16 | Function f_linear | 1 |
| code.py:45:1:45:39 | Function must_be_positive | 1 |

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 |