mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Python: Autoformat all .ql files.
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
* builtin
|
||||
* object
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from Expr e, string name
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @tags call
|
||||
* function
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from Value len, CallNode call
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* try
|
||||
* exception
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from ExceptStmt ex, ClassValue cls
|
||||
where
|
||||
where
|
||||
cls.getName() = "MyExceptionClass" and
|
||||
ex.getType().pointsTo(cls)
|
||||
select ex
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
import python
|
||||
|
||||
from IfExp e, ClassObject cls1, ClassObject cls2
|
||||
where
|
||||
e.getBody().refersTo(_, cls1, _) and e.getOrelse().refersTo(_, cls2, _) and
|
||||
where
|
||||
e.getBody().refersTo(_, cls1, _) and
|
||||
e.getOrelse().refersTo(_, cls2, _) and
|
||||
cls1 != cls2
|
||||
select e
|
||||
select e
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* block
|
||||
* statement
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from StmtList blk
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @id py/examples/emptythen
|
||||
* @name If statements with empty then branch
|
||||
* @description Finds 'if' statements where the "then" branch
|
||||
* @description Finds 'if' statements where the "then" branch
|
||||
* consists entirely of Pass statements
|
||||
* @tags if
|
||||
* then
|
||||
@@ -9,13 +9,13 @@
|
||||
* conditional
|
||||
* branch
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from If i
|
||||
where
|
||||
not exists(Stmt s |
|
||||
i.getStmt(_) = s and
|
||||
i.getStmt(_) = s and
|
||||
not s instanceof Pass
|
||||
)
|
||||
select i
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* test
|
||||
* boolean
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from Compare eq
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* equality
|
||||
* expression statement
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from ExprStmt e, Compare eq
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
* subtype
|
||||
* supertype
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from ClassObject sub, ClassObject base
|
||||
where
|
||||
where
|
||||
base.getName() = "MyClass" and
|
||||
sub.getABaseType() = base
|
||||
select sub
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @description Finds files called `spam.py`
|
||||
* @tags file
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from File f
|
||||
|
||||
@@ -10,4 +10,4 @@ import python
|
||||
|
||||
from Function f
|
||||
where f.isGenerator()
|
||||
select f
|
||||
select f
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @tags integer
|
||||
* literal
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from IntegerLiteral literal
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
* @tags call
|
||||
* method
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from AstNode call, PythonFunctionValue method
|
||||
where
|
||||
where
|
||||
method.getQualifiedName() = "MyClass.methodName" and
|
||||
method.getACall().getNode() = call
|
||||
select call
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* constructor
|
||||
* new
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from Call new, ClassValue cls
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @tags method
|
||||
* override
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from FunctionObject override, FunctionObject base
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
* @description Find print statements or calls to the builtin function 'print'
|
||||
* @tags print
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from AstNode print
|
||||
where
|
||||
where
|
||||
/* Python 2 without `from __future__ import print_function` */
|
||||
print instanceof Print
|
||||
or
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
/**
|
||||
* @id py/examples/private-access
|
||||
* @name Private access
|
||||
* @description Find accesses to "private" attributes (those starting with an underscore)
|
||||
* @description Find accesses to "private" attributes (those starting with an underscore)
|
||||
* @tags access
|
||||
* private
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
predicate is_private(Attribute a) {
|
||||
a.getName().matches("\\_%") and
|
||||
a.getName().matches("\\_%") and
|
||||
not a.getName().matches("\\_\\_%\\_\\_")
|
||||
}
|
||||
|
||||
from Attribute access
|
||||
where is_private(access) and
|
||||
not access.getObject().(Name).getId() = "self"
|
||||
where
|
||||
is_private(access) and
|
||||
not access.getObject().(Name).getId() = "self"
|
||||
select access
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
* raise
|
||||
* exception
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from Raise raise, ClassValue ex
|
||||
where
|
||||
ex.getName() = "AnException" and
|
||||
(
|
||||
raise.getException().pointsTo(ex.getASuperType())
|
||||
)
|
||||
raise.getException().pointsTo(ex.getASuperType())
|
||||
select raise, "Don't raise instances of 'AnException'"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* @id py/examples/raw-string
|
||||
* @name Raw string literals
|
||||
* @description Finds string literals with an 'r' prefix
|
||||
* @description Finds string literals with an 'r' prefix
|
||||
* @tags string
|
||||
* raw
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from StrConst s
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
* collection
|
||||
* add
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
|
||||
from SubscriptNode store
|
||||
where
|
||||
where
|
||||
store.isStore() and
|
||||
store.getIndex().pointsTo(Value::named("None"))
|
||||
select store
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import python
|
||||
|
||||
from Try t
|
||||
where exists(t.getFinalbody())
|
||||
and not exists(t.getAHandler())
|
||||
where
|
||||
exists(t.getFinalbody()) and
|
||||
not exists(t.getAHandler())
|
||||
select t
|
||||
|
||||
@@ -15,13 +15,13 @@ import python
|
||||
|
||||
predicate does_nothing(PyFunctionObject f) {
|
||||
not exists(Stmt s | s.getScope() = f.getFunction() |
|
||||
not s instanceof Pass and not ((ExprStmt)s).getValue() = f.getFunction().getDocString()
|
||||
not s instanceof Pass and not s.(ExprStmt).getValue() = f.getFunction().getDocString()
|
||||
)
|
||||
}
|
||||
|
||||
/* If a method performs a super() call then it is OK as the 'overridden' method will get called */
|
||||
predicate calls_super(FunctionObject f) {
|
||||
exists(Call sup, Call meth, Attribute attr, GlobalVariable v |
|
||||
exists(Call sup, Call meth, Attribute attr, GlobalVariable v |
|
||||
meth.getScope() = f.getFunction() and
|
||||
meth.getFunc() = attr and
|
||||
attr.getObject() = sup and
|
||||
@@ -33,25 +33,29 @@ predicate calls_super(FunctionObject f) {
|
||||
|
||||
/** Holds if the given name is white-listed for some reason */
|
||||
predicate whitelisted(string name) {
|
||||
/* The standard library specifically recommends this :(
|
||||
* See https://docs.python.org/3/library/socketserver.html#asynchronous-mixins */
|
||||
/*
|
||||
* The standard library specifically recommends this :(
|
||||
* See https://docs.python.org/3/library/socketserver.html#asynchronous-mixins
|
||||
*/
|
||||
|
||||
name = "process_request"
|
||||
}
|
||||
|
||||
from ClassObject c, ClassObject b1, ClassObject b2, string name,
|
||||
int i1, int i2, Object o1, Object o2
|
||||
where c.getBaseType(i1) = b1 and
|
||||
c.getBaseType(i2) = b2 and
|
||||
i1 < i2 and o1 != o2 and
|
||||
o1 = b1.lookupAttribute(name) and
|
||||
o2 = b2.lookupAttribute(name) and
|
||||
not name.matches("\\_\\_%\\_\\_") and
|
||||
not calls_super(o1) and
|
||||
not does_nothing(o2) and
|
||||
not whitelisted(name) and
|
||||
not o1.overrides(o2) and
|
||||
not o2.overrides(o1) and
|
||||
not c.declaresAttribute(name)
|
||||
|
||||
select c, "Base classes have conflicting values for attribute '" + name + "': $@ and $@.", o1, o1.toString(), o2, o2.toString()
|
||||
|
||||
from
|
||||
ClassObject c, ClassObject b1, ClassObject b2, string name, int i1, int i2, Object o1, Object o2
|
||||
where
|
||||
c.getBaseType(i1) = b1 and
|
||||
c.getBaseType(i2) = b2 and
|
||||
i1 < i2 and
|
||||
o1 != o2 and
|
||||
o1 = b1.lookupAttribute(name) and
|
||||
o2 = b2.lookupAttribute(name) and
|
||||
not name.matches("\\_\\_%\\_\\_") and
|
||||
not calls_super(o1) and
|
||||
not does_nothing(o2) and
|
||||
not whitelisted(name) and
|
||||
not o1.overrides(o2) and
|
||||
not o2.overrides(o1) and
|
||||
not c.declaresAttribute(name)
|
||||
select c, "Base classes have conflicting values for attribute '" + name + "': $@ and $@.", o1,
|
||||
o1.toString(), o2, o2.toString()
|
||||
|
||||
@@ -15,7 +15,9 @@ import semmle.python.SelfAttribute
|
||||
import Equality
|
||||
|
||||
predicate class_stores_to_attribute(ClassObject cls, SelfAttributeStore store, string name) {
|
||||
exists(FunctionObject f | f = cls.declaredAttribute(_) and store.getScope() = f.getFunction() and store.getName() = name) and
|
||||
exists(FunctionObject f |
|
||||
f = cls.declaredAttribute(_) and store.getScope() = f.getFunction() and store.getName() = name
|
||||
) and
|
||||
/* Exclude classes used as metaclasses */
|
||||
not cls.getASuperType() = theTypeType()
|
||||
}
|
||||
@@ -30,23 +32,26 @@ predicate should_override_eq(ClassObject cls, Object base_eq) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Does the non-overridden __eq__ method access the attribute,
|
||||
/**
|
||||
* Does the non-overridden __eq__ method access the attribute,
|
||||
* which implies that the __eq__ method does not need to be overridden.
|
||||
*/
|
||||
predicate superclassEqExpectsAttribute(ClassObject cls, PyFunctionObject base_eq, string attrname) {
|
||||
not cls.declaresAttribute("__eq__") and
|
||||
exists(ClassObject sup | sup = cls.getABaseType() and sup.declaredAttribute("__eq__") = base_eq |
|
||||
exists(SelfAttributeRead store |
|
||||
store.getName() = attrname |
|
||||
exists(SelfAttributeRead store | store.getName() = attrname |
|
||||
store.getScope() = base_eq.getFunction()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
from ClassObject cls, SelfAttributeStore store, Object base_eq
|
||||
where class_stores_to_attribute(cls, store, _) and should_override_eq(cls, base_eq) and
|
||||
/* Don't report overridden unittest.TestCase. -- TestCase overrides __eq__, but subclasses do not really need to. */
|
||||
not cls.getASuperType().getName() = "TestCase" and
|
||||
not superclassEqExpectsAttribute(cls, base_eq, store.getName())
|
||||
|
||||
select cls, "The class '" + cls.getName() + "' does not override $@, but adds the new attribute $@.", base_eq, "'__eq__'", store, store.getName()
|
||||
where
|
||||
class_stores_to_attribute(cls, store, _) and
|
||||
should_override_eq(cls, base_eq) and
|
||||
/* Don't report overridden unittest.TestCase. -- TestCase overrides __eq__, but subclasses do not really need to. */
|
||||
not cls.getASuperType().getName() = "TestCase" and
|
||||
not superclassEqExpectsAttribute(cls, base_eq, store.getName())
|
||||
select cls,
|
||||
"The class '" + cls.getName() + "' does not override $@, but adds the new attribute $@.", base_eq,
|
||||
"'__eq__'", store, store.getName()
|
||||
|
||||
@@ -14,8 +14,11 @@
|
||||
import python
|
||||
|
||||
CallableValue defines_equality(ClassValue c, string name) {
|
||||
(name = "__eq__" or major_version() = 2 and name = "__cmp__")
|
||||
and
|
||||
(
|
||||
name = "__eq__"
|
||||
or
|
||||
major_version() = 2 and name = "__cmp__"
|
||||
) and
|
||||
result = c.declaredAttribute(name)
|
||||
}
|
||||
|
||||
@@ -26,8 +29,12 @@ CallableValue implemented_method(ClassValue c, string name) {
|
||||
}
|
||||
|
||||
string unimplemented_method(ClassValue c) {
|
||||
not exists(defines_equality(c, _)) and
|
||||
(result = "__eq__" and major_version() = 3 or major_version() = 2 and result = "__eq__ or __cmp__")
|
||||
not exists(defines_equality(c, _)) and
|
||||
(
|
||||
result = "__eq__" and major_version() = 3
|
||||
or
|
||||
major_version() = 2 and result = "__eq__ or __cmp__"
|
||||
)
|
||||
or
|
||||
/* Python 3 automatically makes classes unhashable if __eq__ is defined, but __hash__ is not */
|
||||
not c.declaresAttribute(result) and result = "__hash__" and major_version() = 2
|
||||
@@ -41,13 +48,15 @@ predicate unhashable(ClassValue cls) {
|
||||
}
|
||||
|
||||
predicate violates_hash_contract(ClassValue c, string present, string missing, Value method) {
|
||||
not unhashable(c) and
|
||||
missing = unimplemented_method(c) and
|
||||
method = implemented_method(c, present) and
|
||||
not c.failedInference(_)
|
||||
not unhashable(c) and
|
||||
missing = unimplemented_method(c) and
|
||||
method = implemented_method(c, present) and
|
||||
not c.failedInference(_)
|
||||
}
|
||||
|
||||
from ClassValue c, string present, string missing, CallableValue method
|
||||
where violates_hash_contract(c, present, missing, method) and
|
||||
exists(c.getScope()) // Suppress results that aren't from source
|
||||
select method, "Class $@ implements " + present + " but does not define " + missing + ".", c, c.getName()
|
||||
where
|
||||
violates_hash_contract(c, present, missing, method) and
|
||||
exists(c.getScope()) // Suppress results that aren't from source
|
||||
select method, "Class $@ implements " + present + " but does not define " + missing + ".", c,
|
||||
c.getName()
|
||||
|
||||
@@ -13,16 +13,12 @@
|
||||
import python
|
||||
import Equality
|
||||
|
||||
string equals_or_ne() {
|
||||
result = "__eq__" or result = "__ne__"
|
||||
}
|
||||
string equals_or_ne() { result = "__eq__" or result = "__ne__" }
|
||||
|
||||
predicate total_ordering(Class cls) {
|
||||
exists(Attribute a | a = cls.getADecorator() |
|
||||
a.getName() = "total_ordering")
|
||||
exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering")
|
||||
or
|
||||
exists(Name n | n = cls.getADecorator() |
|
||||
n.getId() = "total_ordering")
|
||||
exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering")
|
||||
}
|
||||
|
||||
CallableValue implemented_method(ClassValue c, string name) {
|
||||
@@ -33,18 +29,20 @@ string unimplemented_method(ClassValue c) {
|
||||
not c.declaresAttribute(result) and result = equals_or_ne()
|
||||
}
|
||||
|
||||
predicate violates_equality_contract(ClassValue c, string present, string missing, CallableValue method) {
|
||||
missing = unimplemented_method(c) and
|
||||
method = implemented_method(c, present) and
|
||||
not c.failedInference(_) and
|
||||
not total_ordering(c.getScope()) and
|
||||
/* Python 3 automatically implements __ne__ if __eq__ is defined, but not vice-versa */
|
||||
not (major_version() = 3 and present = "__eq__" and missing = "__ne__") and
|
||||
not method.getScope() instanceof DelegatingEqualityMethod and
|
||||
not c.lookup(missing).(CallableValue).getScope() instanceof DelegatingEqualityMethod
|
||||
predicate violates_equality_contract(
|
||||
ClassValue c, string present, string missing, CallableValue method
|
||||
) {
|
||||
missing = unimplemented_method(c) and
|
||||
method = implemented_method(c, present) and
|
||||
not c.failedInference(_) and
|
||||
not total_ordering(c.getScope()) and
|
||||
/* Python 3 automatically implements __ne__ if __eq__ is defined, but not vice-versa */
|
||||
not (major_version() = 3 and present = "__eq__" and missing = "__ne__") and
|
||||
not method.getScope() instanceof DelegatingEqualityMethod and
|
||||
not c.lookup(missing).(CallableValue).getScope() instanceof DelegatingEqualityMethod
|
||||
}
|
||||
|
||||
from ClassValue c, string present, string missing, CallableValue method
|
||||
where violates_equality_contract(c, present, missing, method)
|
||||
|
||||
select method, "Class $@ implements " + present + " but does not implement " + missing + ".", c, c.getName()
|
||||
select method, "Class $@ implements " + present + " but does not implement " + missing + ".", c,
|
||||
c.getName()
|
||||
|
||||
@@ -13,17 +13,18 @@
|
||||
import python
|
||||
|
||||
predicate total_ordering(Class cls) {
|
||||
exists(Attribute a | a = cls.getADecorator() |
|
||||
a.getName() = "total_ordering")
|
||||
exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering")
|
||||
or
|
||||
exists(Name n | n = cls.getADecorator() |
|
||||
n.getId() = "total_ordering")
|
||||
exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering")
|
||||
}
|
||||
|
||||
string ordering_name(int n) {
|
||||
result = "__lt__" and n = 1 or
|
||||
result = "__le__" and n = 2 or
|
||||
result = "__gt__" and n = 3 or
|
||||
result = "__lt__" and n = 1
|
||||
or
|
||||
result = "__le__" and n = 2
|
||||
or
|
||||
result = "__gt__" and n = 3
|
||||
or
|
||||
result = "__ge__" and n = 4
|
||||
}
|
||||
|
||||
@@ -32,8 +33,7 @@ predicate overrides_ordering_method(ClassValue c, string name) {
|
||||
(
|
||||
c.declaresAttribute(name)
|
||||
or
|
||||
exists(ClassValue sup |
|
||||
sup = c.getASuperType() and not sup = Value::named("object") |
|
||||
exists(ClassValue sup | sup = c.getASuperType() and not sup = Value::named("object") |
|
||||
sup.declaresAttribute(name)
|
||||
)
|
||||
)
|
||||
@@ -41,15 +41,14 @@ predicate overrides_ordering_method(ClassValue c, string name) {
|
||||
|
||||
string unimplemented_ordering(ClassValue c, int n) {
|
||||
not c = Value::named("object") and
|
||||
not overrides_ordering_method(c, result) and
|
||||
not overrides_ordering_method(c, result) and
|
||||
result = ordering_name(n)
|
||||
}
|
||||
|
||||
string unimplemented_ordering_methods(ClassValue c, int n) {
|
||||
n = 0 and result = "" and exists(unimplemented_ordering(c, _))
|
||||
or
|
||||
exists(string prefix, int nm1 |
|
||||
n = nm1 + 1 and prefix = unimplemented_ordering_methods(c, nm1) |
|
||||
exists(string prefix, int nm1 | n = nm1 + 1 and prefix = unimplemented_ordering_methods(c, nm1) |
|
||||
prefix = "" and result = unimplemented_ordering(c, n)
|
||||
or
|
||||
result = prefix and not exists(unimplemented_ordering(c, n)) and n < 5
|
||||
@@ -60,16 +59,15 @@ string unimplemented_ordering_methods(ClassValue c, int n) {
|
||||
|
||||
Value ordering_method(ClassValue c, string name) {
|
||||
/* If class doesn't declare a method then don't blame this class (the superclass will be blamed). */
|
||||
name = ordering_name(_) and result = c.declaredAttribute(name)
|
||||
name = ordering_name(_) and result = c.declaredAttribute(name)
|
||||
}
|
||||
|
||||
from ClassValue c, Value ordering, string name
|
||||
where not c.failedInference(_) and
|
||||
not total_ordering(c.getScope())
|
||||
and ordering = ordering_method(c, name) and
|
||||
exists(unimplemented_ordering(c, _))
|
||||
|
||||
select c, "Class " + c.getName() + " implements $@, but does not implement " + unimplemented_ordering_methods(c, 4) + ".",
|
||||
ordering, name
|
||||
|
||||
|
||||
where
|
||||
not c.failedInference(_) and
|
||||
not total_ordering(c.getScope()) and
|
||||
ordering = ordering_method(c, name) and
|
||||
exists(unimplemented_ordering(c, _))
|
||||
select c,
|
||||
"Class " + c.getName() + " implements $@, but does not implement " +
|
||||
unimplemented_ordering_methods(c, 4) + ".", ordering, name
|
||||
|
||||
@@ -13,15 +13,18 @@
|
||||
import python
|
||||
|
||||
ClassObject left_base(ClassObject type, ClassObject base) {
|
||||
exists(int i | i > 0 and type.getBaseType(i) = base and result = type.getBaseType(i-1))
|
||||
exists(int i | i > 0 and type.getBaseType(i) = base and result = type.getBaseType(i - 1))
|
||||
}
|
||||
|
||||
predicate invalid_mro(ClassObject t, ClassObject left, ClassObject right) {
|
||||
t.isNewStyle() and
|
||||
left = left_base(t, right) and left = right.getAnImproperSuperType()
|
||||
left = left_base(t, right) and
|
||||
left = right.getAnImproperSuperType()
|
||||
}
|
||||
|
||||
from ClassObject t, ClassObject left, ClassObject right
|
||||
where invalid_mro(t, left, right)
|
||||
select t, "Construction of class " + t.getName() + " can fail due to invalid method resolution order(MRO) for bases $@ and $@.",
|
||||
left, left.getName(), right, right.getName()
|
||||
select t,
|
||||
"Construction of class " + t.getName() +
|
||||
" can fail due to invalid method resolution order(MRO) for bases $@ and $@.", left,
|
||||
left.getName(), right, right.getName()
|
||||
|
||||
@@ -13,23 +13,18 @@
|
||||
|
||||
import python
|
||||
|
||||
|
||||
from ClassObject supercls, string method, Call call,
|
||||
FunctionObject overriding, FunctionObject overridden
|
||||
|
||||
from
|
||||
ClassObject supercls, string method, Call call, FunctionObject overriding,
|
||||
FunctionObject overridden
|
||||
where
|
||||
exists(FunctionObject init, SelfAttribute sa |
|
||||
supercls.declaredAttribute("__init__") = init and
|
||||
call.getScope() = init.getFunction() and call.getFunc() = sa |
|
||||
sa.getName() = method and
|
||||
overridden = supercls.declaredAttribute(method) and
|
||||
overriding.overrides(overridden)
|
||||
)
|
||||
|
||||
select call, "Call to self.$@ in __init__ method, which is overridden by $@.",
|
||||
overridden, method,
|
||||
overriding, overriding.descriptiveString()
|
||||
|
||||
|
||||
|
||||
|
||||
exists(FunctionObject init, SelfAttribute sa |
|
||||
supercls.declaredAttribute("__init__") = init and
|
||||
call.getScope() = init.getFunction() and
|
||||
call.getFunc() = sa
|
||||
|
|
||||
sa.getName() = method and
|
||||
overridden = supercls.declaredAttribute(method) and
|
||||
overriding.overrides(overridden)
|
||||
)
|
||||
select call, "Call to self.$@ in __init__ method, which is overridden by $@.", overridden, method,
|
||||
overriding, overriding.descriptiveString()
|
||||
|
||||
@@ -16,26 +16,29 @@ import ClassAttributes
|
||||
|
||||
predicate guarded_by_other_attribute(SelfAttributeRead a, CheckClass c) {
|
||||
c.sometimesDefines(a.getName()) and
|
||||
exists(SelfAttributeRead guard, If i |
|
||||
exists(SelfAttributeRead guard, If i |
|
||||
i.contains(a) and
|
||||
c.assignedInInit(guard.getName()) |
|
||||
c.assignedInInit(guard.getName())
|
||||
|
|
||||
i.getTest() = guard
|
||||
or
|
||||
i.getTest().contains(guard)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
predicate maybe_undefined_class_attribute(SelfAttributeRead a, CheckClass c) {
|
||||
c.sometimesDefines(a.getName()) and
|
||||
not c.alwaysDefines(a.getName()) and
|
||||
c.interestingUndefined(a) and
|
||||
c.interestingUndefined(a) and
|
||||
not guarded_by_other_attribute(a, c)
|
||||
}
|
||||
|
||||
from Attribute a, ClassObject c, SelfAttributeStore sa
|
||||
where maybe_undefined_class_attribute(a, c) and
|
||||
sa.getClass() = c.getPyClass() and sa.getName() = a.getName()
|
||||
select a, "Attribute '" + a.getName() +
|
||||
"' is not defined in the class body nor in the __init__() method, but it is defined $@", sa, "here"
|
||||
|
||||
where
|
||||
maybe_undefined_class_attribute(a, c) and
|
||||
sa.getClass() = c.getPyClass() and
|
||||
sa.getName() = a.getName()
|
||||
select a,
|
||||
"Attribute '" + a.getName() +
|
||||
"' is not defined in the class body nor in the __init__() method, but it is defined $@", sa,
|
||||
"here"
|
||||
|
||||
@@ -13,14 +13,12 @@
|
||||
import python
|
||||
import MethodCallOrder
|
||||
|
||||
|
||||
from ClassObject self, FunctionObject missing
|
||||
|
||||
where
|
||||
missing_call_to_superclass_method(self, _, missing, "__del__") and
|
||||
not missing.neverReturns() and
|
||||
not self.failedInference() and
|
||||
not missing.isBuiltin()
|
||||
select self, "Class " + self.getName() + " may not be cleaned up properly as $@ is not called during deletion.",
|
||||
missing, missing.descriptiveString()
|
||||
|
||||
select self,
|
||||
"Class " + self.getName() + " may not be cleaned up properly as $@ is not called during deletion.",
|
||||
missing, missing.descriptiveString()
|
||||
|
||||
@@ -14,7 +14,6 @@ import python
|
||||
import MethodCallOrder
|
||||
|
||||
from ClassObject self, FunctionObject initializer, FunctionObject missing
|
||||
|
||||
where
|
||||
self.lookupAttribute("__init__") = initializer and
|
||||
missing_call_to_superclass_method(self, initializer, missing, "__init__") and
|
||||
@@ -24,5 +23,6 @@ where
|
||||
not self.failedInference() and
|
||||
not missing.isBuiltin() and
|
||||
not self.isAbstract()
|
||||
select self, "Class " + self.getName() + " may not be initialized properly as $@ is not called from its $@.",
|
||||
missing, missing.descriptiveString(), initializer, "__init__ method"
|
||||
select self,
|
||||
"Class " + self.getName() + " may not be initialized properly as $@ is not called from its $@.",
|
||||
missing, missing.descriptiveString(), initializer, "__init__ method"
|
||||
|
||||
@@ -15,8 +15,7 @@ import python
|
||||
predicate mutates_descriptor(ClassObject cls, SelfAttributeStore s) {
|
||||
cls.isDescriptorType() and
|
||||
exists(PyFunctionObject f, PyFunctionObject get_set |
|
||||
exists(string name |
|
||||
cls.lookupAttribute(name) = get_set |
|
||||
exists(string name | cls.lookupAttribute(name) = get_set |
|
||||
name = "__get__" or name = "__set__" or name = "__delete__"
|
||||
) and
|
||||
cls.lookupAttribute(_) = f and
|
||||
@@ -27,7 +26,7 @@ predicate mutates_descriptor(ClassObject cls, SelfAttributeStore s) {
|
||||
}
|
||||
|
||||
from ClassObject cls, SelfAttributeStore s
|
||||
where
|
||||
mutates_descriptor(cls, s)
|
||||
|
||||
select s, "Mutation of descriptor $@ object may lead to action-at-a-distance effects or race conditions for properties.", cls, cls.getName()
|
||||
where mutates_descriptor(cls, s)
|
||||
select s,
|
||||
"Mutation of descriptor $@ object may lead to action-at-a-distance effects or race conditions for properties.",
|
||||
cls, cls.getName()
|
||||
|
||||
@@ -13,59 +13,80 @@
|
||||
|
||||
import python
|
||||
|
||||
|
||||
class InitCallStmt extends ExprStmt {
|
||||
|
||||
InitCallStmt() {
|
||||
exists(Call call, Attribute attr | call = this.getValue() and attr = call.getFunc() |
|
||||
attr.getName() = "__init__")
|
||||
attr.getName() = "__init__"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
predicate overwrites_which(Function subinit, AssignStmt write_attr, string which) {
|
||||
write_attr.getScope() = subinit and self_write_stmt(write_attr, _) and
|
||||
write_attr.getScope() = subinit and
|
||||
self_write_stmt(write_attr, _) and
|
||||
exists(Stmt top | top.contains(write_attr) or top = write_attr |
|
||||
(exists(int i, int j, InitCallStmt call | call.getScope() = subinit | i > j and top = subinit.getStmt(i) and call = subinit.getStmt(j) and which = "superclass")
|
||||
or
|
||||
exists(int i, int j, InitCallStmt call | call.getScope() = subinit | i < j and top = subinit.getStmt(i) and call = subinit.getStmt(j) and which = "subclass")
|
||||
)
|
||||
)
|
||||
(
|
||||
exists(int i, int j, InitCallStmt call | call.getScope() = subinit |
|
||||
i > j and top = subinit.getStmt(i) and call = subinit.getStmt(j) and which = "superclass"
|
||||
)
|
||||
or
|
||||
exists(int i, int j, InitCallStmt call | call.getScope() = subinit |
|
||||
i < j and top = subinit.getStmt(i) and call = subinit.getStmt(j) and which = "subclass"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate self_write_stmt(Stmt s, string attr) {
|
||||
exists(Attribute a, Name self | self = a.getObject() and s.contains(a) and self.getId() = "self" and a.getCtx() instanceof Store and a.getName() = attr)
|
||||
exists(Attribute a, Name self |
|
||||
self = a.getObject() and
|
||||
s.contains(a) and
|
||||
self.getId() = "self" and
|
||||
a.getCtx() instanceof Store and
|
||||
a.getName() = attr
|
||||
)
|
||||
}
|
||||
|
||||
predicate both_assign_attribute(Stmt s1, Stmt s2, Function f1, Function f2) {
|
||||
exists(string name | s1.getScope() = f1 and s2.getScope() = f2 and self_write_stmt(s1, name) and self_write_stmt(s2, name))
|
||||
exists(string name |
|
||||
s1.getScope() = f1 and
|
||||
s2.getScope() = f2 and
|
||||
self_write_stmt(s1, name) and
|
||||
self_write_stmt(s2, name)
|
||||
)
|
||||
}
|
||||
|
||||
predicate attribute_overwritten(AssignStmt overwrites, AssignStmt overwritten, string name, string classtype, string classname)
|
||||
{
|
||||
exists(FunctionObject superinit, FunctionObject subinit, ClassObject superclass, ClassObject subclass, AssignStmt subattr, AssignStmt superattr |
|
||||
(classtype = "superclass" and classname = superclass.getName() and overwrites = subattr and overwritten = superattr or
|
||||
classtype = "subclass" and classname = subclass.getName() and overwrites = superattr and overwritten = subattr)
|
||||
and
|
||||
/* OK if overwritten in subclass and is a class attribute */
|
||||
(not exists(superclass.declaredAttribute(name)) or classtype = "subclass")
|
||||
and
|
||||
superclass.declaredAttribute("__init__") = superinit
|
||||
and
|
||||
subclass.declaredAttribute("__init__") = subinit
|
||||
and
|
||||
superclass = subclass.getASuperType()
|
||||
and
|
||||
overwrites_which(subinit.getFunction(), subattr, classtype)
|
||||
and
|
||||
both_assign_attribute(subattr, superattr, subinit.getFunction(), superinit.getFunction())
|
||||
and
|
||||
self_write_stmt(superattr, name)
|
||||
)
|
||||
predicate attribute_overwritten(
|
||||
AssignStmt overwrites, AssignStmt overwritten, string name, string classtype, string classname
|
||||
) {
|
||||
exists(
|
||||
FunctionObject superinit, FunctionObject subinit, ClassObject superclass, ClassObject subclass,
|
||||
AssignStmt subattr, AssignStmt superattr
|
||||
|
|
||||
(
|
||||
classtype = "superclass" and
|
||||
classname = superclass.getName() and
|
||||
overwrites = subattr and
|
||||
overwritten = superattr
|
||||
or
|
||||
classtype = "subclass" and
|
||||
classname = subclass.getName() and
|
||||
overwrites = superattr and
|
||||
overwritten = subattr
|
||||
) and
|
||||
/* OK if overwritten in subclass and is a class attribute */
|
||||
(not exists(superclass.declaredAttribute(name)) or classtype = "subclass") and
|
||||
superclass.declaredAttribute("__init__") = superinit and
|
||||
subclass.declaredAttribute("__init__") = subinit and
|
||||
superclass = subclass.getASuperType() and
|
||||
overwrites_which(subinit.getFunction(), subattr, classtype) and
|
||||
both_assign_attribute(subattr, superattr, subinit.getFunction(), superinit.getFunction()) and
|
||||
self_write_stmt(superattr, name)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
from string classtype, AssignStmt overwrites, AssignStmt overwritten, string name, string classname
|
||||
where attribute_overwritten(overwrites, overwritten, name, classtype, classname)
|
||||
|
||||
select overwrites, "Assignment overwrites attribute " + name + ", which was previously defined in " + classtype + " $@.", overwritten, classname
|
||||
select overwrites,
|
||||
"Assignment overwrites attribute " + name + ", which was previously defined in " + classtype +
|
||||
" $@.", overwritten, classname
|
||||
|
||||
@@ -14,4 +14,6 @@ import python
|
||||
|
||||
from PropertyObject prop, ClassObject cls
|
||||
where cls.declaredAttribute(_) = prop and not cls.failedInference() and not cls.isNewStyle()
|
||||
select prop, "Property " + prop.getName() + " will not work properly, as class " + cls.getName() + " is an old-style class."
|
||||
select prop,
|
||||
"Property " + prop.getName() + " will not work properly, as class " + cls.getName() +
|
||||
" is an old-style class."
|
||||
|
||||
@@ -16,4 +16,6 @@ import python
|
||||
|
||||
from ClassObject c
|
||||
where not c.isC() and not c.isContextManager() and exists(c.declaredAttribute("__del__"))
|
||||
select c, "Class " + c.getName() + " implements __del__ (presumably to release some resource). Consider making it a context manager."
|
||||
select c,
|
||||
"Class " + c.getName() +
|
||||
" implements __del__ (presumably to release some resource). Consider making it a context manager."
|
||||
|
||||
@@ -11,30 +11,36 @@
|
||||
* @id py/attribute-shadows-method
|
||||
*/
|
||||
|
||||
/* Determine if a class defines a method that is shadowed by an attribute
|
||||
defined in a super-class
|
||||
*/
|
||||
/*
|
||||
* Determine if a class defines a method that is shadowed by an attribute
|
||||
* defined in a super-class
|
||||
*/
|
||||
|
||||
/* Need to find attributes defined in superclass (only in __init__?) */
|
||||
|
||||
import python
|
||||
|
||||
predicate shadowed_by_super_class(ClassObject c, ClassObject supercls, Assign assign, FunctionObject f)
|
||||
{
|
||||
c.getASuperType() = supercls and c.declaredAttribute(_) = f and
|
||||
predicate shadowed_by_super_class(
|
||||
ClassObject c, ClassObject supercls, Assign assign, FunctionObject f
|
||||
) {
|
||||
c.getASuperType() = supercls and
|
||||
c.declaredAttribute(_) = f and
|
||||
exists(FunctionObject init, Attribute attr |
|
||||
supercls.declaredAttribute("__init__") = init and
|
||||
attr = assign.getATarget() and
|
||||
((Name)attr.getObject()).getId() = "self" and
|
||||
attr.getObject().(Name).getId() = "self" and
|
||||
attr.getName() = f.getName() and
|
||||
assign.getScope() = ((FunctionExpr)init.getOrigin()).getInnerScope()
|
||||
assign.getScope() = init.getOrigin().(FunctionExpr).getInnerScope()
|
||||
) and
|
||||
/* It's OK if the super class defines the method as well.
|
||||
* We assume that the original method must have been defined for a reason. */
|
||||
/*
|
||||
* It's OK if the super class defines the method as well.
|
||||
* We assume that the original method must have been defined for a reason.
|
||||
*/
|
||||
|
||||
not supercls.hasAttribute(f.getName())
|
||||
}
|
||||
|
||||
from ClassObject c, ClassObject supercls, Assign assign, FunctionObject shadowed
|
||||
where shadowed_by_super_class(c, supercls, assign, shadowed)
|
||||
select shadowed.getOrigin(), "Method " + shadowed.getName() + " is shadowed by $@ in super class '"+ supercls.getName() + "'.", assign, "an attribute"
|
||||
|
||||
select shadowed.getOrigin(),
|
||||
"Method " + shadowed.getName() + " is shadowed by $@ in super class '" + supercls.getName() + "'.",
|
||||
assign, "an attribute"
|
||||
|
||||
@@ -13,10 +13,15 @@
|
||||
import python
|
||||
|
||||
predicate uses_of_super_in_old_style_class(Call s) {
|
||||
exists(Function f, ClassObject c | s.getScope() = f and f.getScope() = c.getPyClass() and not c.failedInference() and
|
||||
not c.isNewStyle() and ((Name)s.getFunc()).getId() = "super")
|
||||
exists(Function f, ClassObject c |
|
||||
s.getScope() = f and
|
||||
f.getScope() = c.getPyClass() and
|
||||
not c.failedInference() and
|
||||
not c.isNewStyle() and
|
||||
s.getFunc().(Name).getId() = "super"
|
||||
)
|
||||
}
|
||||
|
||||
from Call c
|
||||
where uses_of_super_in_old_style_class(c)
|
||||
select c, "super() will not work in old-style classes"
|
||||
select c, "super() will not work in old-style classes"
|
||||
|
||||
@@ -13,15 +13,16 @@
|
||||
import python
|
||||
import MethodCallOrder
|
||||
|
||||
|
||||
from ClassObject self, FunctionObject multi
|
||||
where
|
||||
multiple_calls_to_superclass_method(self, multi, "__del__") and
|
||||
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__del__") and
|
||||
not exists(FunctionObject better |
|
||||
multiple_calls_to_superclass_method(self, better, "__del__") and
|
||||
better.overrides(multi)
|
||||
) and
|
||||
not self.failedInference()
|
||||
select self, "Class " + self.getName() + " may not be cleaned up properly as $@ may be called multiple times during destruction.",
|
||||
multi, multi.descriptiveString()
|
||||
where
|
||||
multiple_calls_to_superclass_method(self, multi, "__del__") and
|
||||
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__del__") and
|
||||
not exists(FunctionObject better |
|
||||
multiple_calls_to_superclass_method(self, better, "__del__") and
|
||||
better.overrides(multi)
|
||||
) and
|
||||
not self.failedInference()
|
||||
select self,
|
||||
"Class " + self.getName() +
|
||||
" may not be cleaned up properly as $@ may be called multiple times during destruction.", multi,
|
||||
multi.descriptiveString()
|
||||
|
||||
@@ -14,13 +14,16 @@ import python
|
||||
import MethodCallOrder
|
||||
|
||||
from ClassObject self, FunctionObject multi
|
||||
where multi != theObjectType().lookupAttribute("__init__") and
|
||||
multiple_calls_to_superclass_method(self, multi, "__init__") and
|
||||
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__init__") and
|
||||
not exists(FunctionObject better |
|
||||
multiple_calls_to_superclass_method(self, better, "__init__") and
|
||||
better.overrides(multi)
|
||||
) and
|
||||
not self.failedInference()
|
||||
select self, "Class " + self.getName() + " may not be initialized properly as $@ may be called multiple times during initialization.",
|
||||
multi, multi.descriptiveString()
|
||||
where
|
||||
multi != theObjectType().lookupAttribute("__init__") and
|
||||
multiple_calls_to_superclass_method(self, multi, "__init__") and
|
||||
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__init__") and
|
||||
not exists(FunctionObject better |
|
||||
multiple_calls_to_superclass_method(self, better, "__init__") and
|
||||
better.overrides(multi)
|
||||
) and
|
||||
not self.failedInference()
|
||||
select self,
|
||||
"Class " + self.getName() +
|
||||
" may not be initialized properly as $@ may be called multiple times during initialization.",
|
||||
multi, multi.descriptiveString()
|
||||
|
||||
@@ -32,4 +32,3 @@ predicate report_undefined_class_attribute(Attribute a, ClassObject c, string na
|
||||
from Attribute a, ClassObject c, string name
|
||||
where report_undefined_class_attribute(a, c, name)
|
||||
select a, "Attribute '" + name + "' is not defined in either the class body or in any method"
|
||||
|
||||
|
||||
@@ -21,63 +21,68 @@ predicate does_not_define_special_method(Class cls) {
|
||||
not exists(Function f | f = cls.getAMethod() and f.isSpecialMethod())
|
||||
}
|
||||
|
||||
|
||||
predicate no_inheritance(Class c) {
|
||||
not exists(ClassObject cls, ClassObject other |
|
||||
cls.getPyClass() = c and
|
||||
other != theObjectType() |
|
||||
cls.getPyClass() = c and
|
||||
other != theObjectType()
|
||||
|
|
||||
other.getABaseType() = cls or
|
||||
cls.getABaseType() = other
|
||||
)
|
||||
and
|
||||
) and
|
||||
not exists(Expr base | base = c.getABase() |
|
||||
not base instanceof Name or ((Name)base).getId() != "object"
|
||||
not base instanceof Name or base.(Name).getId() != "object"
|
||||
)
|
||||
}
|
||||
|
||||
predicate is_decorated(Class c) {
|
||||
exists(c.getADecorator())
|
||||
}
|
||||
predicate is_decorated(Class c) { exists(c.getADecorator()) }
|
||||
|
||||
predicate is_stateful(Class c) {
|
||||
exists(Function method, ExprContext ctx |
|
||||
method.getScope() = c and (ctx instanceof Store or ctx instanceof AugStore) |
|
||||
exists(Function method, ExprContext ctx |
|
||||
method.getScope() = c and
|
||||
(ctx instanceof Store or ctx instanceof AugStore)
|
||||
|
|
||||
exists(Subscript s | s.getScope() = method and s.getCtx() = ctx)
|
||||
or
|
||||
exists(Attribute a | a.getScope() = method and a.getCtx() = ctx)
|
||||
)
|
||||
or
|
||||
exists(Function method, Call call, Attribute a, string name |
|
||||
method.getScope() = c and call.getScope() = method and
|
||||
call.getFunc() = a and a.getName() = name |
|
||||
name = "pop" or name = "remove" or name = "discard" or
|
||||
name = "extend" or name = "append"
|
||||
exists(Function method, Call call, Attribute a, string name |
|
||||
method.getScope() = c and
|
||||
call.getScope() = method and
|
||||
call.getFunc() = a and
|
||||
a.getName() = name
|
||||
|
|
||||
name = "pop" or
|
||||
name = "remove" or
|
||||
name = "discard" or
|
||||
name = "extend" or
|
||||
name = "append"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
predicate useless_class(Class c, int methods) {
|
||||
c.isTopLevel()
|
||||
and
|
||||
c.isPublic()
|
||||
and
|
||||
no_inheritance(c)
|
||||
and
|
||||
fewer_than_two_public_methods(c, methods)
|
||||
and
|
||||
does_not_define_special_method(c)
|
||||
and
|
||||
not c.isProbableMixin()
|
||||
and
|
||||
not is_decorated(c)
|
||||
and
|
||||
c.isTopLevel() and
|
||||
c.isPublic() and
|
||||
no_inheritance(c) and
|
||||
fewer_than_two_public_methods(c, methods) and
|
||||
does_not_define_special_method(c) and
|
||||
not c.isProbableMixin() and
|
||||
not is_decorated(c) and
|
||||
not is_stateful(c)
|
||||
}
|
||||
|
||||
from Class c, int methods, string msg
|
||||
where useless_class(c, methods) and
|
||||
(methods = 1 and msg = "Class " + c.getName() + " defines only one public method, which should be replaced by a function."
|
||||
or
|
||||
methods = 0 and msg = "Class " + c.getName() + " defines no public methods and could be replaced with a namedtuple or dictionary."
|
||||
)
|
||||
where
|
||||
useless_class(c, methods) and
|
||||
(
|
||||
methods = 1 and
|
||||
msg =
|
||||
"Class " + c.getName() +
|
||||
" defines only one public method, which should be replaced by a function."
|
||||
or
|
||||
methods = 0 and
|
||||
msg =
|
||||
"Class " + c.getName() +
|
||||
" defines no public methods and could be replaced with a namedtuple or dictionary."
|
||||
)
|
||||
select c, msg
|
||||
|
||||
@@ -14,14 +14,11 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
import Expressions.CallArgs
|
||||
|
||||
|
||||
from Call call, ClassObject cls, string name, FunctionObject init
|
||||
where
|
||||
illegally_named_parameter_objectapi(call, cls, name)
|
||||
and init = get_function_or_initializer_objectapi(cls)
|
||||
select
|
||||
call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init, init.getQualifiedName()
|
||||
|
||||
illegally_named_parameter_objectapi(call, cls, name) and
|
||||
init = get_function_or_initializer_objectapi(cls)
|
||||
select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init,
|
||||
init.getQualifiedName()
|
||||
|
||||
@@ -17,9 +17,15 @@ import Expressions.CallArgs
|
||||
|
||||
from Call call, ClassObject cls, string too, string should, int limit, FunctionObject init
|
||||
where
|
||||
(
|
||||
too_many_args_objectapi(call, cls, limit) and too = "too many arguments" and should = "no more than "
|
||||
or
|
||||
too_few_args_objectapi(call, cls, limit) and too = "too few arguments" and should = "no fewer than "
|
||||
) and init = get_function_or_initializer_objectapi(cls)
|
||||
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init, init.getQualifiedName()
|
||||
(
|
||||
too_many_args_objectapi(call, cls, limit) and
|
||||
too = "too many arguments" and
|
||||
should = "no more than "
|
||||
or
|
||||
too_few_args_objectapi(call, cls, limit) and
|
||||
too = "too few arguments" and
|
||||
should = "no fewer than "
|
||||
) and
|
||||
init = get_function_or_initializer_objectapi(cls)
|
||||
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init,
|
||||
init.getQualifiedName()
|
||||
|
||||
@@ -14,17 +14,16 @@
|
||||
|
||||
import python
|
||||
|
||||
predicate doesnt_reraise(ExceptStmt ex) {
|
||||
ex.getAFlowNode().getBasicBlock().reachesExit()
|
||||
}
|
||||
predicate doesnt_reraise(ExceptStmt ex) { ex.getAFlowNode().getBasicBlock().reachesExit() }
|
||||
|
||||
predicate catches_base_exception(ExceptStmt ex) {
|
||||
ex.getType().pointsTo(ClassValue::baseException())
|
||||
or
|
||||
not exists(ex.getType())
|
||||
ex.getType().pointsTo(ClassValue::baseException())
|
||||
or
|
||||
not exists(ex.getType())
|
||||
}
|
||||
|
||||
from ExceptStmt ex
|
||||
where catches_base_exception(ex) and
|
||||
doesnt_reraise(ex)
|
||||
where
|
||||
catches_base_exception(ex) and
|
||||
doesnt_reraise(ex)
|
||||
select ex, "Except block directly handles BaseException."
|
||||
|
||||
@@ -13,21 +13,18 @@
|
||||
|
||||
import python
|
||||
|
||||
predicate
|
||||
empty_except(ExceptStmt ex) {
|
||||
predicate empty_except(ExceptStmt ex) {
|
||||
not exists(Stmt s | s = ex.getAStmt() and not s instanceof Pass)
|
||||
}
|
||||
|
||||
predicate no_else(ExceptStmt ex) {
|
||||
not exists(ex.getTry().getOrelse())
|
||||
}
|
||||
predicate no_else(ExceptStmt ex) { not exists(ex.getTry().getOrelse()) }
|
||||
|
||||
predicate no_comment(ExceptStmt ex) {
|
||||
not exists(Comment c |
|
||||
c.getLocation().getFile() = ex.getLocation().getFile() and
|
||||
c.getLocation().getStartLine() >= ex.getLocation().getStartLine() and
|
||||
c.getLocation().getEndLine() <= ex.getBody().getLastItem().getLocation().getEndLine()
|
||||
)
|
||||
not exists(Comment c |
|
||||
c.getLocation().getFile() = ex.getLocation().getFile() and
|
||||
c.getLocation().getStartLine() >= ex.getLocation().getStartLine() and
|
||||
c.getLocation().getEndLine() <= ex.getBody().getLastItem().getLocation().getEndLine()
|
||||
)
|
||||
}
|
||||
|
||||
predicate non_local_control_flow(ExceptStmt ex) {
|
||||
@@ -38,7 +35,8 @@ predicate try_has_normal_exit(Try try) {
|
||||
exists(ControlFlowNode pred, ControlFlowNode succ |
|
||||
/* Exists a non-exception predecessor, successor pair */
|
||||
pred.getASuccessor() = succ and
|
||||
not pred.getAnExceptionalSuccessor() = succ |
|
||||
not pred.getAnExceptionalSuccessor() = succ
|
||||
|
|
||||
/* Successor is either a normal flow node or a fall-through exit */
|
||||
not exists(Scope s | s.getReturnNode() = succ) and
|
||||
/* Predecessor is in try body and successor is not */
|
||||
@@ -50,8 +48,7 @@ predicate try_has_normal_exit(Try try) {
|
||||
predicate attribute_access(Stmt s) {
|
||||
s.(ExprStmt).getValue() instanceof Attribute
|
||||
or
|
||||
exists(string name |
|
||||
s.(ExprStmt).getValue().(Call).getFunc().(Name).getId() = name |
|
||||
exists(string name | s.(ExprStmt).getValue().(Call).getFunc().(Name).getId() = name |
|
||||
name = "getattr" or name = "setattr" or name = "delattr"
|
||||
)
|
||||
or
|
||||
@@ -65,8 +62,7 @@ predicate subscript(Stmt s) {
|
||||
}
|
||||
|
||||
predicate encode_decode(Call ex, ClassValue type) {
|
||||
exists(string name |
|
||||
ex.getFunc().(Attribute).getName() = name |
|
||||
exists(string name | ex.getFunc().(Attribute).getName() = name |
|
||||
name = "encode" and type = ClassValue::unicodeEncodeError()
|
||||
or
|
||||
name = "decode" and type = ClassValue::unicodeDecodeError()
|
||||
@@ -80,8 +76,7 @@ predicate small_handler(ExceptStmt ex, Stmt s, ClassValue type) {
|
||||
}
|
||||
|
||||
predicate focussed_handler(ExceptStmt ex) {
|
||||
exists(Stmt s, ClassValue type |
|
||||
small_handler(ex, s, type) |
|
||||
exists(Stmt s, ClassValue type | small_handler(ex, s, type) |
|
||||
subscript(s) and type.getASuperType() = ClassValue::lookupError()
|
||||
or
|
||||
attribute_access(s) and type = ClassValue::attributeError()
|
||||
@@ -92,12 +87,15 @@ predicate focussed_handler(ExceptStmt ex) {
|
||||
)
|
||||
}
|
||||
|
||||
Try try_return() {
|
||||
not exists(result.getStmt(1)) and result.getStmt(0) instanceof Return
|
||||
}
|
||||
Try try_return() { not exists(result.getStmt(1)) and result.getStmt(0) instanceof Return }
|
||||
|
||||
from ExceptStmt ex
|
||||
where empty_except(ex) and no_else(ex) and no_comment(ex) and not non_local_control_flow(ex)
|
||||
and not ex.getTry() = try_return() and try_has_normal_exit(ex.getTry()) and
|
||||
not focussed_handler(ex)
|
||||
where
|
||||
empty_except(ex) and
|
||||
no_else(ex) and
|
||||
no_comment(ex) and
|
||||
not non_local_control_flow(ex) and
|
||||
not ex.getTry() = try_return() and
|
||||
try_has_normal_exit(ex.getTry()) and
|
||||
not focussed_handler(ex)
|
||||
select ex, "'except' clause does nothing but pass and there is no explanatory comment."
|
||||
|
||||
@@ -16,6 +16,10 @@ import Raising
|
||||
import Exceptions.NotImplemented
|
||||
|
||||
from Raise r, ClassValue t
|
||||
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."
|
||||
|
||||
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."
|
||||
|
||||
@@ -15,5 +15,4 @@ import Exceptions.NotImplemented
|
||||
|
||||
from Expr notimpl
|
||||
where use_of_not_implemented_in_raise(_, notimpl)
|
||||
|
||||
select notimpl, "NotImplemented is not an Exception. Did you mean NotImplementedError?"
|
||||
|
||||
@@ -12,8 +12,11 @@
|
||||
import python
|
||||
|
||||
from Raise r, Value v, AstNode origin
|
||||
where r.getException().pointsTo(v, origin) and
|
||||
v.getClass() = ClassValue::tuple() and
|
||||
major_version() = 2 /* Raising a tuple is a type error in Python 3, so is handled by the IllegalRaise query. */
|
||||
|
||||
select r, "Raising $@ will result in the first element (recursively) being raised and all other elements being discarded.", origin, "a tuple"
|
||||
where
|
||||
r.getException().pointsTo(v, origin) and
|
||||
v.getClass() = ClassValue::tuple() and
|
||||
major_version() = 2
|
||||
/* Raising a tuple is a type error in Python 3, so is handled by the IllegalRaise query. */
|
||||
select r,
|
||||
"Raising $@ will result in the first element (recursively) being raised and all other elements being discarded.",
|
||||
origin, "a tuple"
|
||||
|
||||
@@ -12,13 +12,9 @@
|
||||
|
||||
import python
|
||||
|
||||
FunctionValue iter() {
|
||||
result = Value::named("iter")
|
||||
}
|
||||
FunctionValue iter() { result = Value::named("iter") }
|
||||
|
||||
BuiltinFunctionValue next() {
|
||||
result = Value::named("next")
|
||||
}
|
||||
BuiltinFunctionValue next() { result = Value::named("next") }
|
||||
|
||||
predicate call_to_iter(CallNode call, EssaVariable sequence) {
|
||||
sequence.getAUse() = iter().getArgumentForCall(call, 0)
|
||||
@@ -52,14 +48,14 @@ predicate stop_iteration_handled(CallNode call) {
|
||||
}
|
||||
|
||||
from CallNode call
|
||||
where call_to_next(call, _) and
|
||||
not call_to_next_has_default(call) and
|
||||
not exists(EssaVariable iterator |
|
||||
call_to_next(call, iterator.getAUse()) and
|
||||
iter_not_exhausted(iterator)
|
||||
) and
|
||||
call.getNode().getScope().(Function).isGenerator() and
|
||||
not exists(Comp comp | comp.contains(call.getNode())) and
|
||||
not stop_iteration_handled(call)
|
||||
|
||||
where
|
||||
call_to_next(call, _) and
|
||||
not call_to_next_has_default(call) and
|
||||
not exists(EssaVariable iterator |
|
||||
call_to_next(call, iterator.getAUse()) and
|
||||
iter_not_exhausted(iterator)
|
||||
) and
|
||||
call.getNode().getScope().(Function).isGenerator() and
|
||||
not exists(Comp comp | comp.contains(call.getNode())) and
|
||||
not stop_iteration_handled(call)
|
||||
select call, "Call to next() in a generator"
|
||||
|
||||
@@ -16,14 +16,14 @@ import python
|
||||
|
||||
from CallNode call_to_super, string name
|
||||
where
|
||||
exists(GlobalVariable gv, ControlFlowNode cn |
|
||||
call_to_super = ClassValue::super_().getACall() and
|
||||
gv.getId() = "super" and
|
||||
cn = call_to_super.getArg(0) and
|
||||
name = call_to_super.getScope().getScope().(Class).getName() and
|
||||
exists(ClassValue other |
|
||||
cn.pointsTo(other) and
|
||||
not other.getScope().getName() = name
|
||||
exists(GlobalVariable gv, ControlFlowNode cn |
|
||||
call_to_super = ClassValue::super_().getACall() and
|
||||
gv.getId() = "super" and
|
||||
cn = call_to_super.getArg(0) and
|
||||
name = call_to_super.getScope().getScope().(Class).getName() and
|
||||
exists(ClassValue other |
|
||||
cn.pointsTo(other) and
|
||||
not other.getScope().getName() = name
|
||||
)
|
||||
)
|
||||
)
|
||||
select call_to_super.getNode(), "First argument to super() should be " + name + "."
|
||||
|
||||
@@ -16,6 +16,8 @@ import python
|
||||
|
||||
from Compare comparison, Expr left, Expr right
|
||||
where
|
||||
comparison.compares(left, _, right) and left.isConstant() and right.isConstant() and
|
||||
comparison.compares(left, _, right) and
|
||||
left.isConstant() and
|
||||
right.isConstant() and
|
||||
not exists(Assert a | a.getTest() = comparison)
|
||||
select comparison, "Comparison of constants; use 'True' or 'False' instead."
|
||||
|
||||
@@ -16,6 +16,5 @@ import python
|
||||
import Expressions.RedundantComparison
|
||||
|
||||
from RedundantComparison comparison
|
||||
where
|
||||
comparison.maybeMissingSelf()
|
||||
where comparison.maybeMissingSelf()
|
||||
select comparison, "Comparison of identical values; may be missing 'self'."
|
||||
|
||||
@@ -15,16 +15,19 @@
|
||||
import python
|
||||
import semmle.python.Comparisons
|
||||
|
||||
/* Holds if the comparison `comp` is of the complex form `a op b op c` and not of
|
||||
/*
|
||||
* Holds if the comparison `comp` is of the complex form `a op b op c` and not of
|
||||
* the simple form `a op b`.
|
||||
*/
|
||||
|
||||
private predicate is_complex(Expr comp) {
|
||||
exists(comp.(Compare).getOp(1))
|
||||
or
|
||||
is_complex(comp.(UnaryExpr).getOperand())
|
||||
}
|
||||
|
||||
/** A test is useless if for every block that it controls there is another test that is at least as
|
||||
/**
|
||||
* A test is useless if for every block that it controls there is another test that is at least as
|
||||
* strict and also controls that block.
|
||||
*/
|
||||
private predicate useless_test(Comparison comp, ComparisonControlBlock controls, boolean isTrue) {
|
||||
@@ -34,17 +37,15 @@ private predicate useless_test(Comparison comp, ComparisonControlBlock controls,
|
||||
}
|
||||
|
||||
private predicate useless_test_ast(AstNode comp, AstNode previous, boolean isTrue) {
|
||||
forex(Comparison compnode, ConditionBlock block|
|
||||
forex(Comparison compnode, ConditionBlock block |
|
||||
compnode.getNode() = comp and
|
||||
block.getLastNode().getNode() = previous
|
||||
|
|
||||
|
|
||||
useless_test(compnode, block, isTrue)
|
||||
)
|
||||
}
|
||||
|
||||
from Expr test, Expr other, boolean isTrue
|
||||
where
|
||||
useless_test_ast(test, other, isTrue) and not useless_test_ast(test.getAChildNode+(), other, _)
|
||||
|
||||
|
||||
where
|
||||
useless_test_ast(test, other, isTrue) and not useless_test_ast(test.getAChildNode+(), other, _)
|
||||
select test, "Test is always " + isTrue + ", because of $@", other, "this condition"
|
||||
|
||||
@@ -17,13 +17,12 @@ import semmle.python.strings
|
||||
predicate dict_key(Dict d, Expr k, string s) {
|
||||
k = d.getAKey() and
|
||||
(
|
||||
s = ((Num)k).getN()
|
||||
s = k.(Num).getN()
|
||||
or
|
||||
// We use <20> to mark unrepresentable characters
|
||||
// so two instances of <20> may represent different strings in the source code
|
||||
not "<22>" = s.charAt(_) and
|
||||
exists(StrConst c |
|
||||
c = k |
|
||||
exists(StrConst c | c = k |
|
||||
s = "u\"" + c.getText() + "\"" and c.isUnicode()
|
||||
or
|
||||
s = "b\"" + c.getText() + "\"" and not c.isUnicode()
|
||||
@@ -32,13 +31,15 @@ predicate dict_key(Dict d, Expr k, string s) {
|
||||
}
|
||||
|
||||
from Dict d, Expr k1, Expr k2
|
||||
where exists(string s | dict_key(d, k1, s) and dict_key(d, k2, s) and k1 != k2) and
|
||||
(
|
||||
exists(BasicBlock b, int i1, int i2 |
|
||||
k1.getAFlowNode() = b.getNode(i1) and
|
||||
k2.getAFlowNode() = b.getNode(i2) and
|
||||
i1 < i2
|
||||
) or
|
||||
k1.getAFlowNode().getBasicBlock().strictlyDominates(k2.getAFlowNode().getBasicBlock())
|
||||
)
|
||||
where
|
||||
exists(string s | dict_key(d, k1, s) and dict_key(d, k2, s) and k1 != k2) and
|
||||
(
|
||||
exists(BasicBlock b, int i1, int i2 |
|
||||
k1.getAFlowNode() = b.getNode(i1) and
|
||||
k2.getAFlowNode() = b.getNode(i2) and
|
||||
i1 < i2
|
||||
)
|
||||
or
|
||||
k1.getAFlowNode().getBasicBlock().strictlyDominates(k2.getAFlowNode().getBasicBlock())
|
||||
)
|
||||
select k1, "Dictionary key " + repr(k1) + " is subsequently $@.", k2, "overwritten"
|
||||
|
||||
@@ -13,23 +13,22 @@
|
||||
import python
|
||||
|
||||
class DelCall extends Call {
|
||||
DelCall() {
|
||||
((Attribute)this.getFunc()).getName() = "__del__"
|
||||
}
|
||||
|
||||
predicate isSuperCall() {
|
||||
exists(Function f | f = this.getScope() and f.getName() = "__del__" |
|
||||
// We pass in `self` as the first argument...
|
||||
f.getArg(0).asName().getVariable() = ((Name)this.getArg(0)).getVariable() or
|
||||
// ... or the call is of the form `super(Type, self).__del__()`, or the equivalent
|
||||
// Python 3: `super().__del__()`.
|
||||
exists(Call superCall | superCall = ((Attribute)this.getFunc()).getObject() |
|
||||
((Name)superCall.getFunc()).getId() = "super"
|
||||
)
|
||||
)
|
||||
}
|
||||
DelCall() { this.getFunc().(Attribute).getName() = "__del__" }
|
||||
|
||||
predicate isSuperCall() {
|
||||
exists(Function f | f = this.getScope() and f.getName() = "__del__" |
|
||||
// We pass in `self` as the first argument...
|
||||
f.getArg(0).asName().getVariable() = this.getArg(0).(Name).getVariable()
|
||||
or
|
||||
// ... or the call is of the form `super(Type, self).__del__()`, or the equivalent
|
||||
// Python 3: `super().__del__()`.
|
||||
exists(Call superCall | superCall = this.getFunc().(Attribute).getObject() |
|
||||
superCall.getFunc().(Name).getId() = "super"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from DelCall del
|
||||
where not del.isSuperCall()
|
||||
select del, "The __del__ special method is called explicitly."
|
||||
select del, "The __del__ special method is called explicitly."
|
||||
|
||||
@@ -15,4 +15,4 @@ import AdvancedFormatting
|
||||
|
||||
from AdvancedFormattingCall call, AdvancedFormatString fmt
|
||||
where call.getAFormat() = fmt and fmt.isImplicitlyNumbered() and fmt.isExplicitlyNumbered()
|
||||
select fmt, "Formatting string mixes implicitly and explicitly numbered fields."
|
||||
select fmt, "Formatting string mixes implicitly and explicitly numbered fields."
|
||||
|
||||
@@ -11,16 +11,18 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
|
||||
import python
|
||||
import AdvancedFormatting
|
||||
|
||||
int field_count(AdvancedFormatString fmt) { result = max(fmt.getFieldNumber(_, _)) + 1 }
|
||||
|
||||
from AdvancedFormattingCall call, AdvancedFormatString fmt, int arg_count, int max_field
|
||||
where arg_count = call.providedArgCount() and max_field = field_count(fmt) and
|
||||
call.getAFormat() = fmt and not exists(call.getStarargs()) and
|
||||
forall(AdvancedFormatString other | other = call.getAFormat() | field_count(other) < arg_count)
|
||||
select call, "Too many arguments for string format. Format $@ requires only " + max_field + ", but " +
|
||||
arg_count.toString() + " are provided.", fmt, "\"" + fmt.getText() + "\""
|
||||
where
|
||||
arg_count = call.providedArgCount() and
|
||||
max_field = field_count(fmt) and
|
||||
call.getAFormat() = fmt and
|
||||
not exists(call.getStarargs()) and
|
||||
forall(AdvancedFormatString other | other = call.getAFormat() | field_count(other) < arg_count)
|
||||
select call,
|
||||
"Too many arguments for string format. Format $@ requires only " + max_field + ", but " +
|
||||
arg_count.toString() + " are provided.", fmt, "\"" + fmt.getText() + "\""
|
||||
|
||||
@@ -14,14 +14,18 @@ import python
|
||||
import AdvancedFormatting
|
||||
|
||||
from AdvancedFormattingCall call, AdvancedFormatString fmt, string name, string fmt_repr
|
||||
where call.getAFormat() = fmt and
|
||||
name = call.getAKeyword().getArg() and
|
||||
forall(AdvancedFormatString format | format = call.getAFormat() | not format.getFieldName(_, _) = name)
|
||||
and not exists(call.getKwargs()) and
|
||||
(strictcount(call.getAFormat()) = 1 and fmt_repr = "format \"" + fmt.getText() + "\""
|
||||
or
|
||||
strictcount(call.getAFormat()) != 1 and fmt_repr = "any format used."
|
||||
)
|
||||
|
||||
select call, "Surplus named argument for string format. An argument named '" + name +
|
||||
"' is provided, but it is not required by $@.", fmt, fmt_repr
|
||||
where
|
||||
call.getAFormat() = fmt and
|
||||
name = call.getAKeyword().getArg() and
|
||||
forall(AdvancedFormatString format | format = call.getAFormat() |
|
||||
not format.getFieldName(_, _) = name
|
||||
) and
|
||||
not exists(call.getKwargs()) and
|
||||
(
|
||||
strictcount(call.getAFormat()) = 1 and fmt_repr = "format \"" + fmt.getText() + "\""
|
||||
or
|
||||
strictcount(call.getAFormat()) != 1 and fmt_repr = "any format used."
|
||||
)
|
||||
select call,
|
||||
"Surplus named argument for string format. An argument named '" + name +
|
||||
"' is provided, but it is not required by $@.", fmt, fmt_repr
|
||||
|
||||
@@ -15,9 +15,11 @@ import python
|
||||
import AdvancedFormatting
|
||||
|
||||
from AdvancedFormattingCall call, AdvancedFormatString fmt, string name
|
||||
where call.getAFormat() = fmt and
|
||||
not name = call.getAKeyword().getArg() and
|
||||
fmt.getFieldName(_, _) = name
|
||||
and not exists(call.getKwargs())
|
||||
select call, "Missing named argument for string format. Format $@ requires '" + name + "', but it is omitted.",
|
||||
fmt, "\"" + fmt.getText() + "\""
|
||||
where
|
||||
call.getAFormat() = fmt and
|
||||
not name = call.getAKeyword().getArg() and
|
||||
fmt.getFieldName(_, _) = name and
|
||||
not exists(call.getKwargs())
|
||||
select call,
|
||||
"Missing named argument for string format. Format $@ requires '" + name + "', but it is omitted.",
|
||||
fmt, "\"" + fmt.getText() + "\""
|
||||
|
||||
@@ -14,10 +14,16 @@
|
||||
import python
|
||||
import AdvancedFormatting
|
||||
|
||||
from AdvancedFormattingCall call, AdvancedFormatString fmt,
|
||||
int arg_count, int max_field, string provided
|
||||
where arg_count = call.providedArgCount() and max_field = max(fmt.getFieldNumber(_, _)) and
|
||||
call.getAFormat() = fmt and not exists(call.getStarargs()) and arg_count <= max_field and
|
||||
(if arg_count = 1 then provided = " is provided." else provided = " are provided.")
|
||||
select call, "Too few arguments for string format. Format $@ requires at least " + (max_field+1) + ", but " +
|
||||
arg_count.toString() + provided, fmt, "\"" + fmt.getText() + "\""
|
||||
from
|
||||
AdvancedFormattingCall call, AdvancedFormatString fmt, int arg_count, int max_field,
|
||||
string provided
|
||||
where
|
||||
arg_count = call.providedArgCount() and
|
||||
max_field = max(fmt.getFieldNumber(_, _)) and
|
||||
call.getAFormat() = fmt and
|
||||
not exists(call.getStarargs()) and
|
||||
arg_count <= max_field and
|
||||
(if arg_count = 1 then provided = " is provided." else provided = " are provided.")
|
||||
select call,
|
||||
"Too few arguments for string format. Format $@ requires at least " + (max_field + 1) + ", but " +
|
||||
arg_count.toString() + provided, fmt, "\"" + fmt.getText() + "\""
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
|
||||
import python
|
||||
|
||||
/* This assumes that any indexing operation where the value is not a sequence or numpy array involves hashing.
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
@@ -30,7 +31,9 @@ predicate has_custom_getitem(Value v) {
|
||||
}
|
||||
|
||||
predicate explicitly_hashed(ControlFlowNode f) {
|
||||
exists(CallNode c, GlobalVariable hash | c.getArg(0) = f and c.getFunction().(NameNode).uses(hash) and hash.getId() = "hash")
|
||||
exists(CallNode c, GlobalVariable hash |
|
||||
c.getArg(0) = f and c.getFunction().(NameNode).uses(hash) and hash.getId() = "hash"
|
||||
)
|
||||
}
|
||||
|
||||
predicate unhashable_subscript(ControlFlowNode f, ClassValue c, ControlFlowNode origin) {
|
||||
@@ -44,9 +47,7 @@ predicate unhashable_subscript(ControlFlowNode f, ClassValue c, ControlFlowNode
|
||||
}
|
||||
|
||||
predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origin) {
|
||||
exists(Value v |
|
||||
f.pointsTo(v, origin) and v.getClass() = cls
|
||||
|
|
||||
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")
|
||||
@@ -67,16 +68,18 @@ predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origi
|
||||
* it.
|
||||
*/
|
||||
predicate typeerror_is_caught(ControlFlowNode f) {
|
||||
exists (Try try |
|
||||
exists(Try try |
|
||||
try.getBody().contains(f.getNode()) and
|
||||
try.getAHandler().getType().pointsTo(ClassValue::typeError()))
|
||||
try.getAHandler().getType().pointsTo(ClassValue::typeError())
|
||||
)
|
||||
}
|
||||
|
||||
from ControlFlowNode f, ClassValue c, ControlFlowNode origin
|
||||
where
|
||||
not typeerror_is_caught(f)
|
||||
and
|
||||
(explicitly_hashed(f) and is_unhashable(f, c, origin)
|
||||
or
|
||||
unhashable_subscript(f, c, origin))
|
||||
not typeerror_is_caught(f) and
|
||||
(
|
||||
explicitly_hashed(f) and is_unhashable(f, c, origin)
|
||||
or
|
||||
unhashable_subscript(f, c, origin)
|
||||
)
|
||||
select f.getNode(), "This $@ of $@ is unhashable.", origin, "instance", c, c.getQualifiedName()
|
||||
|
||||
@@ -14,7 +14,14 @@ import python
|
||||
import IsComparisons
|
||||
|
||||
from Compare comp, Cmpop op, ClassValue c, string alt
|
||||
where invalid_portable_is_comparison(comp, op, c) and
|
||||
not cpython_interned_constant(comp.getASubExpression()) and
|
||||
(op instanceof Is and alt = "==" or op instanceof IsNot and alt = "!=")
|
||||
select comp, "Values compared using '" + op.getSymbol() + "' when equivalence is not the same as identity. Use '" + alt + "' instead."
|
||||
where
|
||||
invalid_portable_is_comparison(comp, op, c) and
|
||||
not cpython_interned_constant(comp.getASubExpression()) and
|
||||
(
|
||||
op instanceof Is and alt = "=="
|
||||
or
|
||||
op instanceof IsNot and alt = "!="
|
||||
)
|
||||
select comp,
|
||||
"Values compared using '" + op.getSymbol() +
|
||||
"' when equivalence is not the same as identity. Use '" + alt + "' instead."
|
||||
|
||||
@@ -15,10 +15,13 @@ import python
|
||||
import Exceptions.NotImplemented
|
||||
|
||||
from Call c, Value v, ClassValue t, Expr f, AstNode origin
|
||||
where f = c.getFunc() and f.pointsTo(v, origin) and t = v.getClass() and
|
||||
not t.isCallable() and not t.failedInference(_)
|
||||
and not t.hasAttribute("__get__")
|
||||
and not v = Value::named("None")
|
||||
and not use_of_not_implemented_in_raise(_, f)
|
||||
|
||||
where
|
||||
f = c.getFunc() and
|
||||
f.pointsTo(v, origin) and
|
||||
t = v.getClass() and
|
||||
not t.isCallable() and
|
||||
not t.failedInference(_) and
|
||||
not t.hasAttribute("__get__") and
|
||||
not v = Value::named("None") and
|
||||
not use_of_not_implemented_in_raise(_, f)
|
||||
select c, "Call to a $@ of $@.", origin, "non-callable", t, t.toString()
|
||||
|
||||
@@ -14,10 +14,12 @@ import python
|
||||
import IsComparisons
|
||||
|
||||
from Compare comp, Cmpop op, ClassValue c
|
||||
where invalid_portable_is_comparison(comp, op, c) and
|
||||
exists(Expr sub |
|
||||
sub = comp.getASubExpression() |
|
||||
cpython_interned_constant(sub) and
|
||||
not universally_interned_constant(sub)
|
||||
)
|
||||
select comp, "The result of this comparison with '" + op.getSymbol() + "' may differ between implementations of Python."
|
||||
where
|
||||
invalid_portable_is_comparison(comp, op, c) and
|
||||
exists(Expr sub | sub = comp.getASubExpression() |
|
||||
cpython_interned_constant(sub) and
|
||||
not universally_interned_constant(sub)
|
||||
)
|
||||
select comp,
|
||||
"The result of this comparison with '" + op.getSymbol() +
|
||||
"' may differ between implementations of Python."
|
||||
|
||||
@@ -14,9 +14,8 @@ import python
|
||||
import semmle.python.regex
|
||||
|
||||
from Regex r, int offset
|
||||
where r.escapingChar(offset) and r.getChar(offset+1) = "b" and
|
||||
exists(int start, int end |
|
||||
start < offset and end > offset |
|
||||
r.charSet(start, end)
|
||||
)
|
||||
select r, "Backspace escape in regular expression at offset " + offset + "."
|
||||
where
|
||||
r.escapingChar(offset) and
|
||||
r.getChar(offset + 1) = "b" and
|
||||
exists(int start, int end | start < offset and end > offset | r.charSet(start, end))
|
||||
select r, "Backspace escape in regular expression at offset " + offset + "."
|
||||
|
||||
@@ -15,20 +15,28 @@ import semmle.python.regex
|
||||
|
||||
predicate duplicate_char_in_class(Regex r, string char) {
|
||||
exists(int i, int j, int x, int y, int start, int end |
|
||||
i != x and j != y and
|
||||
start < i and j < end and
|
||||
start < x and y < end and
|
||||
r.character(i, j) and char = r.getText().substring(i, j) and
|
||||
r.character(x, y) and char = r.getText().substring(x, y) and
|
||||
i != x and
|
||||
j != y and
|
||||
start < i and
|
||||
j < end and
|
||||
start < x and
|
||||
y < end and
|
||||
r.character(i, j) and
|
||||
char = r.getText().substring(i, j) and
|
||||
r.character(x, y) and
|
||||
char = r.getText().substring(x, y) and
|
||||
r.charSet(start, end)
|
||||
) and
|
||||
/* Exclude <20> as we use it for any unencodable character */
|
||||
char != "<22>" and
|
||||
//Ignore whitespace in verbose mode
|
||||
not (r.getAMode() = "VERBOSE" and (char = " " or char = "\t" or char = "\r" or char = "\n"))
|
||||
not (
|
||||
r.getAMode() = "VERBOSE" and
|
||||
(char = " " or char = "\t" or char = "\r" or char = "\n")
|
||||
)
|
||||
}
|
||||
|
||||
from Regex r, string char
|
||||
where duplicate_char_in_class(r, char)
|
||||
select r, "This regular expression includes duplicate character '" + char + "' in a set of characters."
|
||||
|
||||
select r,
|
||||
"This regular expression includes duplicate character '" + char + "' in a set of characters."
|
||||
|
||||
@@ -16,5 +16,3 @@ import semmle.python.regex
|
||||
from Regex r, string missing, string part
|
||||
where r.getText().regexpMatch(".*\\(P<\\w+>.*") and missing = "?" and part = "named group"
|
||||
select r, "Regular expression is missing '" + missing + "' in " + part + "."
|
||||
|
||||
|
||||
|
||||
@@ -16,10 +16,11 @@ import semmle.python.regex
|
||||
predicate unmatchable_caret(Regex r, int start) {
|
||||
not r.getAMode() = "MULTILINE" and
|
||||
not r.getAMode() = "VERBOSE" and
|
||||
r.specialCharacter(start, start+1, "^") and
|
||||
not r.firstItem(start, start+1)
|
||||
r.specialCharacter(start, start + 1, "^") and
|
||||
not r.firstItem(start, start + 1)
|
||||
}
|
||||
|
||||
from Regex r, int offset
|
||||
where unmatchable_caret(r, offset)
|
||||
select r, "This regular expression includes an unmatchable caret at offset " + offset.toString() + "."
|
||||
select r,
|
||||
"This regular expression includes an unmatchable caret at offset " + offset.toString() + "."
|
||||
|
||||
@@ -16,11 +16,11 @@ import semmle.python.regex
|
||||
predicate unmatchable_dollar(Regex r, int start) {
|
||||
not r.getAMode() = "MULTILINE" and
|
||||
not r.getAMode() = "VERBOSE" and
|
||||
r.specialCharacter(start, start+1, "$")
|
||||
and
|
||||
not r.lastItem(start, start+1)
|
||||
r.specialCharacter(start, start + 1, "$") and
|
||||
not r.lastItem(start, start + 1)
|
||||
}
|
||||
|
||||
from Regex r, int offset
|
||||
where unmatchable_dollar(r, offset)
|
||||
select r, "This regular expression includes an unmatchable dollar at offset " + offset.toString() + "."
|
||||
select r,
|
||||
"This regular expression includes an unmatchable dollar at offset " + offset.toString() + "."
|
||||
|
||||
@@ -1,38 +1,37 @@
|
||||
/**
|
||||
* @name Result of integer division may be truncated
|
||||
* @description The arguments to a division statement may be integers, which
|
||||
* may cause the result to be truncated in Python 2.
|
||||
* @kind problem
|
||||
* @tags maintainability
|
||||
* correctness
|
||||
* @problem.severity warning
|
||||
* @sub-severity high
|
||||
* @precision very-high
|
||||
* @id py/truncated-division
|
||||
*/
|
||||
/**
|
||||
* @name Result of integer division may be truncated
|
||||
* @description The arguments to a division statement may be integers, which
|
||||
* may cause the result to be truncated in Python 2.
|
||||
* @kind problem
|
||||
* @tags maintainability
|
||||
* correctness
|
||||
* @problem.severity warning
|
||||
* @sub-severity high
|
||||
* @precision very-high
|
||||
* @id py/truncated-division
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
from BinaryExpr div, ControlFlowNode left, ControlFlowNode right
|
||||
where
|
||||
// Only relevant for Python 2, as all later versions implement true division
|
||||
major_version() = 2
|
||||
and
|
||||
major_version() = 2 and
|
||||
exists(BinaryExprNode bin, Value lval, Value rval |
|
||||
bin = div.getAFlowNode()
|
||||
and bin.getNode().getOp() instanceof Div
|
||||
and bin.getLeft().pointsTo(lval, left)
|
||||
and lval.getClass() = ClassValue::int_()
|
||||
and bin.getRight().pointsTo(rval, right)
|
||||
and rval.getClass() = ClassValue::int_()
|
||||
bin = div.getAFlowNode() and
|
||||
bin.getNode().getOp() instanceof Div and
|
||||
bin.getLeft().pointsTo(lval, left) and
|
||||
lval.getClass() = ClassValue::int_() and
|
||||
bin.getRight().pointsTo(rval, right) and
|
||||
rval.getClass() = ClassValue::int_() and
|
||||
// Ignore instances where integer division leaves no remainder
|
||||
and not lval.(NumericValue).getIntValue() % rval.(NumericValue).getIntValue() = 0
|
||||
and not bin.getNode().getEnclosingModule().hasFromFuture("division")
|
||||
not lval.(NumericValue).getIntValue() % rval.(NumericValue).getIntValue() = 0 and
|
||||
not bin.getNode().getEnclosingModule().hasFromFuture("division") and
|
||||
// Filter out results wrapped in `int(...)`
|
||||
and not exists(CallNode c |
|
||||
c = ClassValue::int_().getACall()
|
||||
and c.getAnArg() = bin
|
||||
not exists(CallNode c |
|
||||
c = ClassValue::int_().getACall() and
|
||||
c.getAnArg() = bin
|
||||
)
|
||||
)
|
||||
select div, "Result of division may be truncated as its $@ and $@ arguments may both be integers.",
|
||||
left.getLocation(), "left", right.getLocation(), "right"
|
||||
left.getLocation(), "left", right.getLocation(), "right"
|
||||
|
||||
@@ -22,14 +22,13 @@ predicate string_const(Expr s) {
|
||||
|
||||
from StrConst s
|
||||
where
|
||||
// Implicitly concatenated string is in a list and that list contains at least one other string.
|
||||
exists(List l, Expr other |
|
||||
not s = other and
|
||||
l.getAnElt() = s and
|
||||
l.getAnElt() = other and
|
||||
string_const(other)
|
||||
) and
|
||||
exists(s.getAnImplicitlyConcatenatedPart()) and
|
||||
not s.isParenthesized()
|
||||
|
||||
// Implicitly concatenated string is in a list and that list contains at least one other string.
|
||||
exists(List l, Expr other |
|
||||
not s = other and
|
||||
l.getAnElt() = s and
|
||||
l.getAnElt() = other and
|
||||
string_const(other)
|
||||
) and
|
||||
exists(s.getAnImplicitlyConcatenatedPart()) and
|
||||
not s.isParenthesized()
|
||||
select s, "Implicit string concatenation. Maybe missing a comma?"
|
||||
|
||||
@@ -15,43 +15,47 @@ import python
|
||||
/* f consists of a single return statement, whose value is a call. The arguments of the call are exactly the parameters of f */
|
||||
predicate simple_wrapper(Lambda l, Expr wrapped) {
|
||||
exists(Function f, Call c | f = l.getInnerScope() and c = l.getExpression() |
|
||||
wrapped = c.getFunc() and
|
||||
count(f.getAnArg()) = count(c.getAnArg()) and
|
||||
forall(int arg | exists(f.getArg(arg)) |
|
||||
f.getArgName(arg) = ((Name)c.getArg(arg)).getId()) and
|
||||
/* Either no **kwargs or they must match */
|
||||
(not exists(f.getKwarg()) and not exists(c.getKwargs()) or
|
||||
((Name)f.getKwarg()).getId() = ((Name)c.getKwargs()).getId()) and
|
||||
/* Either no *args or they must match */
|
||||
(not exists(f.getVararg()) and not exists(c.getStarargs()) or
|
||||
((Name)f.getVararg()).getId() = ((Name)c.getStarargs()).getId()) and
|
||||
/* No named parameters in call */
|
||||
not exists(c.getAKeyword())
|
||||
)
|
||||
and
|
||||
wrapped = c.getFunc() and
|
||||
count(f.getAnArg()) = count(c.getAnArg()) and
|
||||
forall(int arg | exists(f.getArg(arg)) | f.getArgName(arg) = c.getArg(arg).(Name).getId()) and
|
||||
/* Either no **kwargs or they must match */
|
||||
(
|
||||
not exists(f.getKwarg()) and not exists(c.getKwargs())
|
||||
or
|
||||
f.getKwarg().(Name).getId() = c.getKwargs().(Name).getId()
|
||||
) and
|
||||
/* Either no *args or they must match */
|
||||
(
|
||||
not exists(f.getVararg()) and not exists(c.getStarargs())
|
||||
or
|
||||
f.getVararg().(Name).getId() = c.getStarargs().(Name).getId()
|
||||
) and
|
||||
/* No named parameters in call */
|
||||
not exists(c.getAKeyword())
|
||||
) and
|
||||
// f is not necessarily a drop-in replacement for the lambda if there are default argument values
|
||||
not exists(l.getArgs().getADefault())
|
||||
}
|
||||
|
||||
/* The expression called will refer to the same object if evaluated when the lambda is created or when the lambda is executed. */
|
||||
predicate unnecessary_lambda(Lambda l, Expr e) {
|
||||
simple_wrapper(l, e) and
|
||||
simple_wrapper(l, e) and
|
||||
(
|
||||
/* plain class */
|
||||
exists(ClassValue c | e.pointsTo(c))
|
||||
or
|
||||
/* plain function */
|
||||
exists(FunctionValue f | e.pointsTo(f))
|
||||
or
|
||||
/* bound-method of enclosing instance */
|
||||
exists(ClassValue cls, Attribute a |
|
||||
cls.getScope() = l.getScope().getScope() and a = e |
|
||||
((Name)a.getObject()).getId() = "self" and
|
||||
cls.hasAttribute(a.getName())
|
||||
)
|
||||
/* plain class */
|
||||
exists(ClassValue c | e.pointsTo(c))
|
||||
or
|
||||
/* plain function */
|
||||
exists(FunctionValue f | e.pointsTo(f))
|
||||
or
|
||||
/* bound-method of enclosing instance */
|
||||
exists(ClassValue cls, Attribute a | cls.getScope() = l.getScope().getScope() and a = e |
|
||||
a.getObject().(Name).getId() = "self" and
|
||||
cls.hasAttribute(a.getName())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
from Lambda l, Expr e
|
||||
where unnecessary_lambda(l, e)
|
||||
select l, "This 'lambda' is just a simple wrapper around a callable object. Use that object directly."
|
||||
select l,
|
||||
"This 'lambda' is just a simple wrapper around a callable object. Use that object directly."
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
import python
|
||||
import Expressions.CallArgs
|
||||
|
||||
|
||||
from Call call, FunctionObject func, string name
|
||||
where
|
||||
illegally_named_parameter_objectapi(call, func, name) and
|
||||
not func.isAbstract() and
|
||||
not exists(FunctionObject overridden | func.overrides(overridden) and overridden.getFunction().getAnArg().(Name).getId() = name)
|
||||
select
|
||||
call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", func, func.descriptiveString()
|
||||
illegally_named_parameter_objectapi(call, func, name) and
|
||||
not func.isAbstract() and
|
||||
not exists(FunctionObject overridden |
|
||||
func.overrides(overridden) and overridden.getFunction().getAnArg().(Name).getId() = name
|
||||
)
|
||||
select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", func,
|
||||
func.descriptiveString()
|
||||
|
||||
@@ -18,29 +18,30 @@ import semmle.python.strings
|
||||
predicate string_format(BinaryExpr operation, StrConst str, Value args, AstNode origin) {
|
||||
operation.getOp() instanceof Mod and
|
||||
exists(Value fmt, Context ctx |
|
||||
operation.getLeft().pointsTo(ctx, fmt, str) and
|
||||
operation.getRight().pointsTo(ctx, args, origin)
|
||||
operation.getLeft().pointsTo(ctx, fmt, str) and
|
||||
operation.getRight().pointsTo(ctx, args, origin)
|
||||
)
|
||||
}
|
||||
|
||||
int sequence_length(Value args) {
|
||||
/* Guess length of sequence */
|
||||
exists(Tuple seq, AstNode origin |
|
||||
seq.pointsTo(args,origin) |
|
||||
exists(Tuple seq, AstNode origin | seq.pointsTo(args, origin) |
|
||||
result = strictcount(seq.getAnElt()) and
|
||||
not seq.getAnElt() instanceof Starred
|
||||
)
|
||||
or
|
||||
exists(ImmutableLiteral i |
|
||||
i.getLiteralValue() = args |
|
||||
result = 1
|
||||
)
|
||||
exists(ImmutableLiteral i | i.getLiteralValue() = args | result = 1)
|
||||
}
|
||||
|
||||
|
||||
from BinaryExpr operation, StrConst fmt, Value args, int slen, int alen, AstNode origin, string provided
|
||||
where string_format(operation, fmt, args, origin) and slen = sequence_length(args) and alen = format_items(fmt) and slen != alen and
|
||||
(if slen = 1 then provided = " is provided." else provided = " are provided.")
|
||||
select operation, "Wrong number of $@ for string format. Format $@ takes " + alen.toString() + ", but " + slen.toString() + provided,
|
||||
origin, "arguments",
|
||||
fmt, fmt.getText()
|
||||
from
|
||||
BinaryExpr operation, StrConst fmt, Value args, int slen, int alen, AstNode origin,
|
||||
string provided
|
||||
where
|
||||
string_format(operation, fmt, args, origin) and
|
||||
slen = sequence_length(args) and
|
||||
alen = format_items(fmt) and
|
||||
slen != alen and
|
||||
(if slen = 1 then provided = " is provided." else provided = " are provided.")
|
||||
select operation,
|
||||
"Wrong number of $@ for string format. Format $@ takes " + alen.toString() + ", but " +
|
||||
slen.toString() + provided, origin, "arguments", fmt, fmt.getText()
|
||||
|
||||
@@ -16,15 +16,20 @@ import CallArgs
|
||||
|
||||
from Call call, FunctionObject func, string too, string should, int limit
|
||||
where
|
||||
(
|
||||
too_many_args_objectapi(call, func, limit) and too = "too many arguments" and should = "no more than "
|
||||
or
|
||||
too_few_args_objectapi(call, func, limit) and too = "too few arguments" and should = "no fewer than "
|
||||
) and
|
||||
not func.isAbstract() and
|
||||
not exists(FunctionObject overridden | func.overrides(overridden) and correct_args_if_called_as_method_objectapi(call, overridden))
|
||||
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
|
||||
and not func.getName() = "__new__"
|
||||
|
||||
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func, func.descriptiveString()
|
||||
|
||||
(
|
||||
too_many_args_objectapi(call, func, limit) and
|
||||
too = "too many arguments" and
|
||||
should = "no more than "
|
||||
or
|
||||
too_few_args_objectapi(call, func, limit) and
|
||||
too = "too few arguments" and
|
||||
should = "no fewer than "
|
||||
) and
|
||||
not func.isAbstract() and
|
||||
not exists(FunctionObject overridden |
|
||||
func.overrides(overridden) and correct_args_if_called_as_method_objectapi(call, overridden)
|
||||
) and
|
||||
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
|
||||
not func.getName() = "__new__"
|
||||
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func,
|
||||
func.descriptiveString()
|
||||
|
||||
@@ -11,8 +11,9 @@ import semmle.python.filters.GeneratedCode
|
||||
import semmle.python.filters.Tests
|
||||
|
||||
predicate classify(File f, string tag) {
|
||||
f instanceof GeneratedFile and tag = "generated" or
|
||||
exists (TestScope t | t.getLocation().getFile() = f) and tag = "test"
|
||||
f instanceof GeneratedFile and tag = "generated"
|
||||
or
|
||||
exists(TestScope t | t.getLocation().getFile() = f) and tag = "test"
|
||||
}
|
||||
|
||||
from File f, string tag
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @kind problem
|
||||
* @id py/not-generated-file-filter
|
||||
*/
|
||||
|
||||
import python
|
||||
import external.DefectFilter
|
||||
import semmle.python.filters.GeneratedCode
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @kind problem
|
||||
* @id py/not-test-file-filter
|
||||
*/
|
||||
|
||||
import python
|
||||
import external.DefectFilter
|
||||
import semmle.python.filters.Tests
|
||||
|
||||
@@ -13,20 +13,21 @@
|
||||
import python
|
||||
|
||||
predicate explicitly_returns_non_none(Function func) {
|
||||
exists(Return return | return.getScope() = func and
|
||||
exists(Expr val |
|
||||
val= return.getValue() |
|
||||
not val instanceof None
|
||||
)
|
||||
exists(Return return |
|
||||
return.getScope() = func and
|
||||
exists(Expr val | val = return.getValue() | not val instanceof None)
|
||||
)
|
||||
}
|
||||
|
||||
predicate has_implicit_return(Function func) {
|
||||
exists(ControlFlowNode fallthru | fallthru = func.getFallthroughNode() and not fallthru.unlikelyReachable()) or
|
||||
exists(ControlFlowNode fallthru |
|
||||
fallthru = func.getFallthroughNode() and not fallthru.unlikelyReachable()
|
||||
)
|
||||
or
|
||||
exists(Return return | return.getScope() = func and not exists(return.getValue()))
|
||||
}
|
||||
|
||||
|
||||
from Function func
|
||||
where explicitly_returns_non_none(func) and has_implicit_return(func)
|
||||
select func, "Mixing implicit and explicit returns may indicate an error as implicit returns always return None."
|
||||
select func,
|
||||
"Mixing implicit and explicit returns may indicate an error as implicit returns always return None."
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
import python
|
||||
|
||||
predicate slice_method_name(string name) {
|
||||
name = "__getslice__" or name = "__setslice__" or name = "__delslice__"
|
||||
name = "__getslice__" or name = "__setslice__" or name = "__delslice__"
|
||||
}
|
||||
|
||||
from PythonFunctionValue f, string meth
|
||||
|
||||
where f.getScope().isMethod() and not f.isOverridingMethod() and
|
||||
slice_method_name(meth) and f.getName() = meth
|
||||
|
||||
|
||||
select f, meth + " method has been deprecated since Python 2.0"
|
||||
where
|
||||
f.getScope().isMethod() and
|
||||
not f.isOverridingMethod() and
|
||||
slice_method_name(meth) and
|
||||
f.getName() = meth
|
||||
select f, meth + " method has been deprecated since Python 2.0"
|
||||
|
||||
@@ -22,44 +22,87 @@ private predicate indexing_method(string name) {
|
||||
}
|
||||
|
||||
private predicate arithmetic_method(string name) {
|
||||
name = "__add__" or name = "__sub__" or name = "__div__" or
|
||||
name = "__pos__" or name = "__abs__" or name = "__floordiv__" or
|
||||
name = "__div__" or name = "__divmod__" or name = "__lshift__" or
|
||||
name = "__and__" or name = "__or__"or name = "__xor__" or name = "__rshift__" or
|
||||
name = "__pow__" or name = "__mul__" or name = "__neg__" or
|
||||
name = "__radd__" or name = "__rsub__" or name = "__rdiv__" or
|
||||
name = "__rfloordiv__" or name = "__rdiv__" or name = "__rlshift__" or
|
||||
name = "__rand__" or name = "__ror__"or name = "__rxor__" or name = "__rrshift__" or
|
||||
name = "__rpow__" or name = "__rmul__" or name = "__truediv__" or name = "__rtruediv__" or
|
||||
name = "__iadd__" or name = "__isub__" or name = "__idiv__" or
|
||||
name = "__ifloordiv__" or name = "__idiv__" or name = "__ilshift__" or
|
||||
name = "__iand__" or name = "__ior__"or name = "__ixor__" or name = "__irshift__" or
|
||||
name = "__ipow__" or name = "__imul__" or name = "__itruediv__"
|
||||
name = "__add__" or
|
||||
name = "__sub__" or
|
||||
name = "__div__" or
|
||||
name = "__pos__" or
|
||||
name = "__abs__" or
|
||||
name = "__floordiv__" or
|
||||
name = "__div__" or
|
||||
name = "__divmod__" or
|
||||
name = "__lshift__" or
|
||||
name = "__and__" or
|
||||
name = "__or__" or
|
||||
name = "__xor__" or
|
||||
name = "__rshift__" or
|
||||
name = "__pow__" or
|
||||
name = "__mul__" or
|
||||
name = "__neg__" or
|
||||
name = "__radd__" or
|
||||
name = "__rsub__" or
|
||||
name = "__rdiv__" or
|
||||
name = "__rfloordiv__" or
|
||||
name = "__rdiv__" or
|
||||
name = "__rlshift__" or
|
||||
name = "__rand__" or
|
||||
name = "__ror__" or
|
||||
name = "__rxor__" or
|
||||
name = "__rrshift__" or
|
||||
name = "__rpow__" or
|
||||
name = "__rmul__" or
|
||||
name = "__truediv__" or
|
||||
name = "__rtruediv__" or
|
||||
name = "__iadd__" or
|
||||
name = "__isub__" or
|
||||
name = "__idiv__" or
|
||||
name = "__ifloordiv__" or
|
||||
name = "__idiv__" or
|
||||
name = "__ilshift__" or
|
||||
name = "__iand__" or
|
||||
name = "__ior__" or
|
||||
name = "__ixor__" or
|
||||
name = "__irshift__" or
|
||||
name = "__ipow__" or
|
||||
name = "__imul__" or
|
||||
name = "__itruediv__"
|
||||
}
|
||||
|
||||
private predicate ordering_method(string name) {
|
||||
name = "__lt__" or name = "__le__" or name = "__gt__" or name = "__ge__" or
|
||||
name = "__lt__"
|
||||
or
|
||||
name = "__le__"
|
||||
or
|
||||
name = "__gt__"
|
||||
or
|
||||
name = "__ge__"
|
||||
or
|
||||
name = "__cmp__" and major_version() = 2
|
||||
}
|
||||
|
||||
private predicate cast_method(string name) {
|
||||
name = "__nonzero__" and major_version() = 2 or
|
||||
name = "__bool__" or
|
||||
name = "__int__" or name = "__float__" or
|
||||
name = "__long__" or
|
||||
name = "__trunc__" or
|
||||
name = "__nonzero__" and major_version() = 2
|
||||
or
|
||||
name = "__bool__"
|
||||
or
|
||||
name = "__int__"
|
||||
or
|
||||
name = "__float__"
|
||||
or
|
||||
name = "__long__"
|
||||
or
|
||||
name = "__trunc__"
|
||||
or
|
||||
name = "__complex__"
|
||||
}
|
||||
|
||||
predicate correct_raise(string name, ClassObject ex) {
|
||||
ex.getAnImproperSuperType() = theTypeErrorType()
|
||||
and
|
||||
ex.getAnImproperSuperType() = theTypeErrorType() and
|
||||
(
|
||||
name = "__copy__" or
|
||||
name = "__deepcopy__" or
|
||||
name = "__call__" or
|
||||
indexing_method(name) or
|
||||
attribute_method(name)
|
||||
name = "__copy__" or
|
||||
name = "__deepcopy__" or
|
||||
name = "__call__" or
|
||||
indexing_method(name) or
|
||||
attribute_method(name)
|
||||
)
|
||||
or
|
||||
preferred_raise(name, ex)
|
||||
@@ -84,11 +127,11 @@ predicate no_need_to_raise(string name, string message) {
|
||||
}
|
||||
|
||||
predicate is_abstract(FunctionObject func) {
|
||||
((Name)func.getFunction().getADecorator()).getId().matches("%abstract%")
|
||||
func.getFunction().getADecorator().(Name).getId().matches("%abstract%")
|
||||
}
|
||||
|
||||
predicate always_raises(FunctionObject f, ClassObject ex) {
|
||||
ex = f.getARaisedType() and
|
||||
ex = f.getARaisedType() and
|
||||
strictcount(f.getARaisedType()) = 1 and
|
||||
not exists(f.getFunction().getANormalExit()) and
|
||||
/* raising StopIteration is equivalent to a return in a generator */
|
||||
@@ -96,17 +139,17 @@ predicate always_raises(FunctionObject f, ClassObject ex) {
|
||||
}
|
||||
|
||||
from FunctionObject f, ClassObject cls, string message
|
||||
where f.getFunction().isSpecialMethod() and
|
||||
not is_abstract(f) and
|
||||
always_raises(f, cls) and
|
||||
(
|
||||
no_need_to_raise(f.getName(), message) and not cls.getName() = "NotImplementedError"
|
||||
or
|
||||
not correct_raise(f.getName(), cls) and not cls.getName() = "NotImplementedError"
|
||||
and
|
||||
exists(ClassObject preferred |
|
||||
preferred_raise(f.getName(), preferred) |
|
||||
message = "raise " + preferred.getName() + " instead"
|
||||
where
|
||||
f.getFunction().isSpecialMethod() and
|
||||
not is_abstract(f) and
|
||||
always_raises(f, cls) and
|
||||
(
|
||||
no_need_to_raise(f.getName(), message) and not cls.getName() = "NotImplementedError"
|
||||
or
|
||||
not correct_raise(f.getName(), cls) and
|
||||
not cls.getName() = "NotImplementedError" and
|
||||
exists(ClassObject preferred | preferred_raise(f.getName(), preferred) |
|
||||
message = "raise " + preferred.getName() + " instead"
|
||||
)
|
||||
)
|
||||
)
|
||||
select f, "Function always raises $@; " + message, cls, cls.toString()
|
||||
|
||||
@@ -14,14 +14,18 @@ import Expressions.CallArgs
|
||||
|
||||
from Call call, FunctionObject func, FunctionObject overridden, string problem
|
||||
where
|
||||
func.overrides(overridden) and (
|
||||
wrong_args_objectapi(call, func, _, problem) and correct_args_if_called_as_method_objectapi(call, overridden)
|
||||
or
|
||||
exists(string name |
|
||||
illegally_named_parameter_objectapi(call, func, name) and problem = "an argument named '" + name + "'" and
|
||||
overridden.getFunction().getAnArg().(Name).getId() = name
|
||||
func.overrides(overridden) and
|
||||
(
|
||||
wrong_args_objectapi(call, func, _, problem) and
|
||||
correct_args_if_called_as_method_objectapi(call, overridden)
|
||||
or
|
||||
exists(string name |
|
||||
illegally_named_parameter_objectapi(call, func, name) and
|
||||
problem = "an argument named '" + name + "'" and
|
||||
overridden.getFunction().getAnArg().(Name).getId() = name
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
select func, "Overriding method signature does not match $@, where it is passed " + problem + ". Overridden method $@ is correctly specified.",
|
||||
call, "here", overridden, overridden.descriptiveString()
|
||||
select func,
|
||||
"Overriding method signature does not match $@, where it is passed " + problem +
|
||||
". Overridden method $@ is correctly specified.", call, "here", overridden,
|
||||
overridden.descriptiveString()
|
||||
|
||||
@@ -15,21 +15,23 @@ import Expressions.CallArgs
|
||||
|
||||
from Call call, FunctionObject func, FunctionObject overriding, string problem
|
||||
where
|
||||
not func.getName() = "__init__" and
|
||||
overriding.overrides(func) and
|
||||
call = overriding.getAMethodCall().getNode() and
|
||||
correct_args_if_called_as_method_objectapi(call, overriding) and
|
||||
(
|
||||
arg_count_objectapi(call)+1 < func.minParameters() and problem = "too few arguments"
|
||||
or
|
||||
arg_count_objectapi(call) >= func.maxParameters() and problem = "too many arguments"
|
||||
or
|
||||
exists(string name | call.getAKeyword().getArg() = name and
|
||||
overriding.getFunction().getAnArg().(Name).getId() = name and
|
||||
not func.getFunction().getAnArg().(Name).getId() = name and
|
||||
problem = "an argument named '" + name + "'"
|
||||
not func.getName() = "__init__" and
|
||||
overriding.overrides(func) and
|
||||
call = overriding.getAMethodCall().getNode() and
|
||||
correct_args_if_called_as_method_objectapi(call, overriding) and
|
||||
(
|
||||
arg_count_objectapi(call) + 1 < func.minParameters() and problem = "too few arguments"
|
||||
or
|
||||
arg_count_objectapi(call) >= func.maxParameters() and problem = "too many arguments"
|
||||
or
|
||||
exists(string name |
|
||||
call.getAKeyword().getArg() = name and
|
||||
overriding.getFunction().getAnArg().(Name).getId() = name and
|
||||
not func.getFunction().getAnArg().(Name).getId() = name and
|
||||
problem = "an argument named '" + name + "'"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
select func, "Overridden method signature does not match $@, where it is passed " + problem + ". Overriding method $@ matches the call.",
|
||||
call, "call", overriding, overriding.descriptiveString()
|
||||
select func,
|
||||
"Overridden method signature does not match $@, where it is passed " + problem +
|
||||
". Overriding method $@ matches the call.", call, "call", overriding,
|
||||
overriding.descriptiveString()
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import python
|
||||
|
||||
from Function f
|
||||
where f.isInitMethod() and
|
||||
(exists(Yield y | y.getScope() = f) or exists(YieldFrom y| y.getScope() = f))
|
||||
where
|
||||
f.isInitMethod() and
|
||||
(exists(Yield y | y.getScope() = f) or exists(YieldFrom y | y.getScope() = f))
|
||||
select f, "__init__ method is a generator."
|
||||
|
||||
@@ -13,21 +13,20 @@
|
||||
import python
|
||||
|
||||
Function iter_method(ClassObject t) {
|
||||
result = ((FunctionObject)t.lookupAttribute("__iter__")).getFunction()
|
||||
result = t.lookupAttribute("__iter__").(FunctionObject).getFunction()
|
||||
}
|
||||
|
||||
predicate is_self(Name value, Function f) {
|
||||
value.getVariable() = ((Name)f.getArg(0)).getVariable()
|
||||
}
|
||||
predicate is_self(Name value, Function f) { value.getVariable() = f.getArg(0).(Name).getVariable() }
|
||||
|
||||
predicate returns_non_self(Function f) {
|
||||
exists(f.getFallthroughNode())
|
||||
or
|
||||
exists(Return r | r.getScope() = f and not is_self(r.getValue(), f))
|
||||
or
|
||||
exists(Return r | r.getScope() = f and not exists(r.getValue()))
|
||||
exists(f.getFallthroughNode())
|
||||
or
|
||||
exists(Return r | r.getScope() = f and not is_self(r.getValue(), f))
|
||||
or
|
||||
exists(Return r | r.getScope() = f and not exists(r.getValue()))
|
||||
}
|
||||
|
||||
from ClassObject t, Function iter
|
||||
where t.isIterator() and iter = iter_method(t) and returns_non_self(iter)
|
||||
select t, "Class " + t.getName() + " is an iterator but its $@ method does not return 'self'.", iter, iter.getName()
|
||||
select t, "Class " + t.getName() + " is an iterator but its $@ method does not return 'self'.",
|
||||
iter, iter.getName()
|
||||
|
||||
@@ -40,9 +40,11 @@ where
|
||||
(
|
||||
if exists(f.getArgName(0))
|
||||
then
|
||||
message = "Class methods or methods of a type deriving from type should have 'cls', rather than '"
|
||||
+ f.getArgName(0) + "', as their first parameter."
|
||||
message =
|
||||
"Class methods or methods of a type deriving from type should have 'cls', rather than '" +
|
||||
f.getArgName(0) + "', as their first parameter."
|
||||
else
|
||||
message = "Class methods or methods of a type deriving from type should have 'cls' as their first parameter."
|
||||
message =
|
||||
"Class methods or methods of a type deriving from type should have 'cls' as their first parameter."
|
||||
)
|
||||
select f, message
|
||||
|
||||
@@ -45,10 +45,12 @@ where
|
||||
(
|
||||
if exists(f.getArgName(0))
|
||||
then
|
||||
message = "Normal methods should have 'self', rather than '" + f.getArgName(0) +
|
||||
message =
|
||||
"Normal methods should have 'self', rather than '" + f.getArgName(0) +
|
||||
"', as their first parameter."
|
||||
else
|
||||
message = "Normal methods should have at least one parameter (the first of which should be 'self')."
|
||||
message =
|
||||
"Normal methods should have at least one parameter (the first of which should be 'self')."
|
||||
) and
|
||||
not f.hasVarArg()
|
||||
) and
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
import python
|
||||
|
||||
from FunctionObject method
|
||||
where exists(ClassObject c | c.declaredAttribute("__del__") = method and
|
||||
method.getFunction().getMetrics().getCyclomaticComplexity() > 3)
|
||||
where
|
||||
exists(ClassObject c |
|
||||
c.declaredAttribute("__del__") = method and
|
||||
method.getFunction().getMetrics().getCyclomaticComplexity() > 3
|
||||
)
|
||||
select method, "Overly complex '__del__' method."
|
||||
|
||||
@@ -15,15 +15,16 @@ import python
|
||||
predicate returns_tuple_of_size(Function func, int size, AstNode origin) {
|
||||
exists(Return return, TupleValue val |
|
||||
return.getScope() = func and
|
||||
return.getValue().pointsTo(val, origin) |
|
||||
return.getValue().pointsTo(val, origin)
|
||||
|
|
||||
size = val.length()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
from Function func, int s1, int s2, AstNode t1, AstNode t2
|
||||
where
|
||||
returns_tuple_of_size(func, s1, t1) and
|
||||
returns_tuple_of_size(func, s2, t2) and
|
||||
s1 < s2
|
||||
select func, func.getQualifiedName() + " returns $@ and $@.", t1, "tuple of size " + s1, t2, "tuple of size " + s2
|
||||
select func, func.getQualifiedName() + " returns $@ and $@.", t1, "tuple of size " + s1, t2,
|
||||
"tuple of size " + s2
|
||||
|
||||
@@ -21,28 +21,34 @@ predicate meaningful_return_value(Expr val) {
|
||||
or
|
||||
val instanceof BooleanLiteral
|
||||
or
|
||||
exists(FunctionObject callee | val = callee.getACall().getNode() and returns_meaningful_value(callee))
|
||||
exists(FunctionObject callee |
|
||||
val = callee.getACall().getNode() and returns_meaningful_value(callee)
|
||||
)
|
||||
or
|
||||
not exists(FunctionObject callee | val = callee.getACall().getNode()) and not val instanceof Name
|
||||
}
|
||||
|
||||
/* Value is used before returning, and thus its value is not lost if ignored */
|
||||
predicate used_value(Expr val) {
|
||||
exists(LocalVariable var, Expr other | var.getAnAccess() = val and other = var.getAnAccess() and not other = val)
|
||||
exists(LocalVariable var, Expr other |
|
||||
var.getAnAccess() = val and other = var.getAnAccess() and not other = val
|
||||
)
|
||||
}
|
||||
|
||||
predicate returns_meaningful_value(FunctionObject f) {
|
||||
not exists(f.getFunction().getFallthroughNode())
|
||||
and
|
||||
predicate returns_meaningful_value(FunctionObject f) {
|
||||
not exists(f.getFunction().getFallthroughNode()) and
|
||||
(
|
||||
exists(Return ret, Expr val | ret.getScope() = f.getFunction() and val = ret.getValue() |
|
||||
meaningful_return_value(val) and
|
||||
not used_value(val)
|
||||
)
|
||||
or
|
||||
/* Is f a builtin function that returns something other than None?
|
||||
* Ignore __import__ as it is often called purely for side effects */
|
||||
f.isC() and f.getAnInferredReturnType() != theNoneType() and not f.getName() = "__import__"
|
||||
exists(Return ret, Expr val | ret.getScope() = f.getFunction() and val = ret.getValue() |
|
||||
meaningful_return_value(val) and
|
||||
not used_value(val)
|
||||
)
|
||||
or
|
||||
/*
|
||||
* Is f a builtin function that returns something other than None?
|
||||
* Ignore __import__ as it is often called purely for side effects
|
||||
*/
|
||||
|
||||
f.isC() and f.getAnInferredReturnType() != theNoneType() and not f.getName() = "__import__"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -56,17 +62,19 @@ predicate wrapped_in_try_except(ExprStmt call) {
|
||||
}
|
||||
|
||||
from ExprStmt call, FunctionObject callee, float percentage_used, int total
|
||||
where call.getValue() = callee.getACall().getNode() and returns_meaningful_value(callee) and
|
||||
not wrapped_in_try_except(call) and
|
||||
exists(int unused |
|
||||
unused = count(ExprStmt e | e.getValue().getAFlowNode() = callee.getACall()) and
|
||||
total = count(callee.getACall()) |
|
||||
percentage_used = (100.0*(total-unused)/total).floor()
|
||||
) and
|
||||
/* Report an alert if we see at least 5 calls and the return value is used in at least 3/4 of those calls. */
|
||||
percentage_used >= 75 and
|
||||
total >= 5
|
||||
|
||||
select call, "Call discards return value of function $@. The result is used in " + percentage_used.toString() + "% of calls.",
|
||||
callee, callee.getName()
|
||||
|
||||
where
|
||||
call.getValue() = callee.getACall().getNode() and
|
||||
returns_meaningful_value(callee) and
|
||||
not wrapped_in_try_except(call) and
|
||||
exists(int unused |
|
||||
unused = count(ExprStmt e | e.getValue().getAFlowNode() = callee.getACall()) and
|
||||
total = count(callee.getACall())
|
||||
|
|
||||
percentage_used = (100.0 * (total - unused) / total).floor()
|
||||
) and
|
||||
/* Report an alert if we see at least 5 calls and the return value is used in at least 3/4 of those calls. */
|
||||
percentage_used >= 75 and
|
||||
total >= 5
|
||||
select call,
|
||||
"Call discards return value of function $@. The result is used in " + percentage_used.toString() +
|
||||
"% of calls.", callee, callee.getName()
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* @problem.severity warning
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* @problem.severity warning
|
||||
* @sub-severity high
|
||||
* @precision very-high
|
||||
* @id py/inheritance/signature-mismatch
|
||||
@@ -17,19 +16,20 @@ import Expressions.CallArgs
|
||||
|
||||
from FunctionObject base, PyFunctionObject derived
|
||||
where
|
||||
not exists(base.getACall()) and
|
||||
not exists(FunctionObject a_derived |
|
||||
a_derived.overrides(base) and
|
||||
exists(a_derived.getACall())
|
||||
) and
|
||||
not derived.getFunction().isSpecialMethod() and
|
||||
derived.getName() != "__init__" and
|
||||
derived.isNormalMethod() and
|
||||
not derived.getFunction().isSpecialMethod() and
|
||||
// call to overrides distributed for efficiency
|
||||
(
|
||||
(derived.overrides(base) and derived.minParameters() > base.maxParameters())
|
||||
or
|
||||
(derived.overrides(base) and derived.maxParameters() < base.minParameters())
|
||||
)
|
||||
select derived, "Overriding method '" + derived.getName() + "' has signature mismatch with $@.", base, "overridden method"
|
||||
not exists(base.getACall()) and
|
||||
not exists(FunctionObject a_derived |
|
||||
a_derived.overrides(base) and
|
||||
exists(a_derived.getACall())
|
||||
) and
|
||||
not derived.getFunction().isSpecialMethod() and
|
||||
derived.getName() != "__init__" and
|
||||
derived.isNormalMethod() and
|
||||
not derived.getFunction().isSpecialMethod() and
|
||||
// call to overrides distributed for efficiency
|
||||
(
|
||||
derived.overrides(base) and derived.minParameters() > base.maxParameters()
|
||||
or
|
||||
derived.overrides(base) and derived.maxParameters() < base.minParameters()
|
||||
)
|
||||
select derived, "Overriding method '" + derived.getName() + "' has signature mismatch with $@.",
|
||||
base, "overridden method"
|
||||
|
||||
@@ -12,105 +12,102 @@
|
||||
|
||||
import python
|
||||
|
||||
|
||||
predicate is_unary_op(string name) {
|
||||
name = "__del__" or
|
||||
name = "__repr__" or
|
||||
name = "__str__" or
|
||||
name = "__hash__" or
|
||||
name = "__bool__" or
|
||||
name = "__nonzero__" or
|
||||
name = "__unicode__" or
|
||||
name = "__len__" or
|
||||
name = "__iter__" or
|
||||
name = "__reversed__" or
|
||||
name = "__neg__" or
|
||||
name = "__pos__" or
|
||||
name = "__abs__" or
|
||||
name = "__invert__" or
|
||||
name = "__complex__" or
|
||||
name = "__int__" or
|
||||
name = "__float__" or
|
||||
name = "__long__" or
|
||||
name = "__oct__" or
|
||||
name = "__hex__" or
|
||||
name = "__index__" or
|
||||
name = "__enter__"
|
||||
name = "__del__" or
|
||||
name = "__repr__" or
|
||||
name = "__str__" or
|
||||
name = "__hash__" or
|
||||
name = "__bool__" or
|
||||
name = "__nonzero__" or
|
||||
name = "__unicode__" or
|
||||
name = "__len__" or
|
||||
name = "__iter__" or
|
||||
name = "__reversed__" or
|
||||
name = "__neg__" or
|
||||
name = "__pos__" or
|
||||
name = "__abs__" or
|
||||
name = "__invert__" or
|
||||
name = "__complex__" or
|
||||
name = "__int__" or
|
||||
name = "__float__" or
|
||||
name = "__long__" or
|
||||
name = "__oct__" or
|
||||
name = "__hex__" or
|
||||
name = "__index__" or
|
||||
name = "__enter__"
|
||||
}
|
||||
|
||||
predicate is_binary_op(string name) {
|
||||
name = "__lt__" or
|
||||
name = "__le__" or
|
||||
name = "__eq__" or
|
||||
name = "__ne__" or
|
||||
name = "__gt__" or
|
||||
name = "__ge__" or
|
||||
name = "__cmp__" or
|
||||
name = "__rcmp__" or
|
||||
name = "__getattr___" or
|
||||
name = "__getattribute___" or
|
||||
name = "__delattr__" or
|
||||
name = "__delete__" or
|
||||
name = "__instancecheck__" or
|
||||
name = "__subclasscheck__" or
|
||||
name = "__getitem__" or
|
||||
name = "__delitem__" or
|
||||
name = "__contains__" or
|
||||
name = "__add__" or
|
||||
name = "__sub__" or
|
||||
name = "__mul__" or
|
||||
name = "__floordiv__" or
|
||||
name = "__div__" or
|
||||
name = "__truediv__" or
|
||||
name = "__mod__" or
|
||||
name = "__divmod__" or
|
||||
name = "__lshift__" or
|
||||
name = "__rshift__" or
|
||||
name = "__and__" or
|
||||
name = "__xor__" or
|
||||
name = "__or__" or
|
||||
name = "__radd__" or
|
||||
name = "__rsub__" or
|
||||
name = "__rmul__" or
|
||||
name = "__rfloordiv__" or
|
||||
name = "__rdiv__" or
|
||||
name = "__rtruediv__" or
|
||||
name = "__rmod__" or
|
||||
name = "__rdivmod__" or
|
||||
name = "__rpow__" or
|
||||
name = "__rlshift__" or
|
||||
name = "__rrshift__" or
|
||||
name = "__rand__" or
|
||||
name = "__rxor__" or
|
||||
name = "__ror__" or
|
||||
name = "__iadd__" or
|
||||
name = "__isub__" or
|
||||
name = "__imul__" or
|
||||
name = "__ifloordiv__" or
|
||||
name = "__idiv__" or
|
||||
name = "__itruediv__" or
|
||||
name = "__imod__" or
|
||||
name = "__idivmod__" or
|
||||
name = "__ipow__" or
|
||||
name = "__ilshift__" or
|
||||
name = "__irshift__" or
|
||||
name = "__iand__" or
|
||||
name = "__ixor__" or
|
||||
name = "__ior__" or
|
||||
name = "__coerce__"
|
||||
name = "__lt__" or
|
||||
name = "__le__" or
|
||||
name = "__eq__" or
|
||||
name = "__ne__" or
|
||||
name = "__gt__" or
|
||||
name = "__ge__" or
|
||||
name = "__cmp__" or
|
||||
name = "__rcmp__" or
|
||||
name = "__getattr___" or
|
||||
name = "__getattribute___" or
|
||||
name = "__delattr__" or
|
||||
name = "__delete__" or
|
||||
name = "__instancecheck__" or
|
||||
name = "__subclasscheck__" or
|
||||
name = "__getitem__" or
|
||||
name = "__delitem__" or
|
||||
name = "__contains__" or
|
||||
name = "__add__" or
|
||||
name = "__sub__" or
|
||||
name = "__mul__" or
|
||||
name = "__floordiv__" or
|
||||
name = "__div__" or
|
||||
name = "__truediv__" or
|
||||
name = "__mod__" or
|
||||
name = "__divmod__" or
|
||||
name = "__lshift__" or
|
||||
name = "__rshift__" or
|
||||
name = "__and__" or
|
||||
name = "__xor__" or
|
||||
name = "__or__" or
|
||||
name = "__radd__" or
|
||||
name = "__rsub__" or
|
||||
name = "__rmul__" or
|
||||
name = "__rfloordiv__" or
|
||||
name = "__rdiv__" or
|
||||
name = "__rtruediv__" or
|
||||
name = "__rmod__" or
|
||||
name = "__rdivmod__" or
|
||||
name = "__rpow__" or
|
||||
name = "__rlshift__" or
|
||||
name = "__rrshift__" or
|
||||
name = "__rand__" or
|
||||
name = "__rxor__" or
|
||||
name = "__ror__" or
|
||||
name = "__iadd__" or
|
||||
name = "__isub__" or
|
||||
name = "__imul__" or
|
||||
name = "__ifloordiv__" or
|
||||
name = "__idiv__" or
|
||||
name = "__itruediv__" or
|
||||
name = "__imod__" or
|
||||
name = "__idivmod__" or
|
||||
name = "__ipow__" or
|
||||
name = "__ilshift__" or
|
||||
name = "__irshift__" or
|
||||
name = "__iand__" or
|
||||
name = "__ixor__" or
|
||||
name = "__ior__" or
|
||||
name = "__coerce__"
|
||||
}
|
||||
|
||||
predicate is_ternary_op(string name) {
|
||||
name = "__setattr__" or
|
||||
name = "__set__" or
|
||||
name = "__setitem__" or
|
||||
name = "__getslice__" or
|
||||
name = "__delslice__"
|
||||
name = "__setattr__" or
|
||||
name = "__set__" or
|
||||
name = "__setitem__" or
|
||||
name = "__getslice__" or
|
||||
name = "__delslice__"
|
||||
}
|
||||
|
||||
predicate is_quad_op(string name) {
|
||||
name = "__setslice__" or name = "__exit__"
|
||||
}
|
||||
predicate is_quad_op(string name) { name = "__setslice__" or name = "__exit__" }
|
||||
|
||||
int argument_count(PythonFunctionValue f, string name, ClassValue cls) {
|
||||
cls.declaredAttribute(name) = f and
|
||||
@@ -125,52 +122,62 @@ int argument_count(PythonFunctionValue f, string name, ClassValue cls) {
|
||||
)
|
||||
}
|
||||
|
||||
predicate incorrect_special_method_defn(PythonFunctionValue func, string message, boolean show_counts, string name, ClassValue owner) {
|
||||
exists(int required |
|
||||
required = argument_count(func, name, owner) |
|
||||
/* actual_non_default <= actual */
|
||||
if required > func.maxParameters() then
|
||||
(message = "Too few parameters" and show_counts = true)
|
||||
else if required < func.minParameters() then
|
||||
(message = "Too many parameters" and show_counts = true)
|
||||
else if (func.minParameters() < required and not func.getScope().hasVarArg()) then
|
||||
(message = (required -func.minParameters()) + " default values(s) will never be used" and show_counts = false)
|
||||
else
|
||||
none()
|
||||
)
|
||||
predicate incorrect_special_method_defn(
|
||||
PythonFunctionValue func, string message, boolean show_counts, string name, ClassValue owner
|
||||
) {
|
||||
exists(int required | required = argument_count(func, name, owner) |
|
||||
/* actual_non_default <= actual */
|
||||
if required > func.maxParameters()
|
||||
then message = "Too few parameters" and show_counts = true
|
||||
else
|
||||
if required < func.minParameters()
|
||||
then message = "Too many parameters" and show_counts = true
|
||||
else
|
||||
if func.minParameters() < required and not func.getScope().hasVarArg()
|
||||
then
|
||||
message = (required - func.minParameters()) + " default values(s) will never be used" and
|
||||
show_counts = false
|
||||
else none()
|
||||
)
|
||||
}
|
||||
|
||||
predicate incorrect_pow(FunctionValue func, string message, boolean show_counts, ClassValue owner) {
|
||||
owner.declaredAttribute("__pow__") = func and
|
||||
(
|
||||
func.maxParameters() < 2 and message = "Too few parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() < 2 and message = (2 - func.minParameters()) + " default value(s) will never be used" and show_counts = false
|
||||
or
|
||||
func.minParameters() = 3 and message = "Third parameter to __pow__ should have a default value" and show_counts = false
|
||||
)
|
||||
func.maxParameters() < 2 and message = "Too few parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() < 2 and
|
||||
message = (2 - func.minParameters()) + " default value(s) will never be used" and
|
||||
show_counts = false
|
||||
or
|
||||
func.minParameters() = 3 and
|
||||
message = "Third parameter to __pow__ should have a default value" and
|
||||
show_counts = false
|
||||
)
|
||||
}
|
||||
|
||||
predicate incorrect_get(FunctionValue func, string message, boolean show_counts, ClassValue owner) {
|
||||
owner.declaredAttribute("__get__") = func and
|
||||
(
|
||||
func.maxParameters() < 3 and message = "Too few parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() < 2 and not func.getScope().hasVarArg() and
|
||||
message = (2 - func.minParameters()) + " default value(s) will never be used" and show_counts = false
|
||||
)
|
||||
func.maxParameters() < 3 and message = "Too few parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() < 2 and
|
||||
not func.getScope().hasVarArg() and
|
||||
message = (2 - func.minParameters()) + " default value(s) will never be used" and
|
||||
show_counts = false
|
||||
)
|
||||
}
|
||||
|
||||
string should_have_parameters(PythonFunctionValue f, string name, ClassValue owner) {
|
||||
exists(int i | i = argument_count(f, name, owner) |
|
||||
result = i.toString()
|
||||
)
|
||||
or
|
||||
owner.declaredAttribute(name) = f and (name = "__get__" or name = "__pow__") and result = "2 or 3"
|
||||
exists(int i | i = argument_count(f, name, owner) | result = i.toString())
|
||||
or
|
||||
owner.declaredAttribute(name) = f and
|
||||
(name = "__get__" or name = "__pow__") and
|
||||
result = "2 or 3"
|
||||
}
|
||||
|
||||
string has_parameters(PythonFunctionValue f) {
|
||||
@@ -183,18 +190,23 @@ string has_parameters(PythonFunctionValue f) {
|
||||
)
|
||||
}
|
||||
|
||||
from PythonFunctionValue f, string message, string sizes, boolean show_counts, string name, ClassValue owner
|
||||
where
|
||||
(
|
||||
incorrect_special_method_defn(f, message, show_counts, name, owner)
|
||||
or
|
||||
incorrect_pow(f, message, show_counts, owner) and name = "__pow__"
|
||||
or
|
||||
incorrect_get(f, message, show_counts, owner) and name = "__get__"
|
||||
)
|
||||
and
|
||||
(
|
||||
show_counts = false and sizes = "" or
|
||||
show_counts = true and sizes = ", which has " + has_parameters(f) + ", but should have " + should_have_parameters(f, name, owner)
|
||||
)
|
||||
from
|
||||
PythonFunctionValue f, string message, string sizes, boolean show_counts, string name,
|
||||
ClassValue owner
|
||||
where
|
||||
(
|
||||
incorrect_special_method_defn(f, message, show_counts, name, owner)
|
||||
or
|
||||
incorrect_pow(f, message, show_counts, owner) and name = "__pow__"
|
||||
or
|
||||
incorrect_get(f, message, show_counts, owner) and name = "__get__"
|
||||
) and
|
||||
(
|
||||
show_counts = false and sizes = ""
|
||||
or
|
||||
show_counts = true and
|
||||
sizes =
|
||||
", which has " + has_parameters(f) + ", but should have " +
|
||||
should_have_parameters(f, name, owner)
|
||||
)
|
||||
select f, message + " for special method " + name + sizes + ", in class $@.", owner, owner.getName()
|
||||
|
||||
@@ -13,9 +13,11 @@ import python
|
||||
import Testing.Mox
|
||||
|
||||
predicate is_used(Call c) {
|
||||
exists(Expr outer | outer != c and outer.containsInScope(c) | outer instanceof Call or outer instanceof Attribute or outer instanceof Subscript)
|
||||
exists(Expr outer | outer != c and outer.containsInScope(c) |
|
||||
outer instanceof Call or outer instanceof Attribute or outer instanceof Subscript
|
||||
)
|
||||
or
|
||||
exists(Stmt s |
|
||||
exists(Stmt s |
|
||||
c = s.getASubExpression() and
|
||||
not s instanceof ExprStmt and
|
||||
/* Ignore if a single return, as def f(): return g() is quite common. Covers implicit return in a lambda. */
|
||||
@@ -24,11 +26,13 @@ predicate is_used(Call c) {
|
||||
}
|
||||
|
||||
from Call c, FunctionValue func
|
||||
where
|
||||
/* Call result is used, but callee is a procedure */
|
||||
is_used(c) and c.getFunc().pointsTo(func) and func.getScope().isProcedure() and
|
||||
/* All callees are procedures */
|
||||
forall(FunctionValue callee | c.getFunc().pointsTo(callee) | callee.getScope().isProcedure()) and
|
||||
/* Mox return objects have an `AndReturn` method */
|
||||
not useOfMoxInModule(c.getEnclosingModule())
|
||||
where
|
||||
/* Call result is used, but callee is a procedure */
|
||||
is_used(c) and
|
||||
c.getFunc().pointsTo(func) and
|
||||
func.getScope().isProcedure() and
|
||||
/* All callees are procedures */
|
||||
forall(FunctionValue callee | c.getFunc().pointsTo(callee) | callee.getScope().isProcedure()) and
|
||||
/* Mox return objects have an `AndReturn` method */
|
||||
not useOfMoxInModule(c.getEnclosingModule())
|
||||
select c, "The result of '$@' is used even though it is always None.", func, func.getQualifiedName()
|
||||
|
||||
@@ -60,7 +60,8 @@ predicate deprecated_module(string name, string instead, int major, int minor) {
|
||||
|
||||
string deprecation_message(string mod) {
|
||||
exists(int major, int minor | deprecated_module(mod, _, major, minor) |
|
||||
result = "The " + mod + " module was deprecated in version " + major.toString() + "." +
|
||||
result =
|
||||
"The " + mod + " module was deprecated in version " + major.toString() + "." +
|
||||
minor.toString() + "."
|
||||
)
|
||||
}
|
||||
@@ -77,8 +78,7 @@ from ImportExpr imp, string name, string instead
|
||||
where
|
||||
name = imp.getName() and
|
||||
deprecated_module(name, instead, _, _) and
|
||||
not exists(Try try, ExceptStmt except | except = try.getAHandler()
|
||||
|
|
||||
not exists(Try try, ExceptStmt except | except = try.getAHandler() |
|
||||
except.getType().pointsTo(ClassValue::importError()) and
|
||||
except.containsInScope(imp)
|
||||
)
|
||||
|
||||
@@ -13,4 +13,4 @@
|
||||
import python
|
||||
|
||||
from EncodingError error
|
||||
select error, error.getMessage()
|
||||
select error, error.getMessage()
|
||||
|
||||
@@ -13,10 +13,13 @@
|
||||
import python
|
||||
|
||||
predicate shadowsImport(Variable l) {
|
||||
exists(Import i, Name shadow | shadow = i.getAName().getAsname() and shadow.getId() = l.getId() and i.getScope() = l.getScope().getScope*())
|
||||
exists(Import i, Name shadow |
|
||||
shadow = i.getAName().getAsname() and
|
||||
shadow.getId() = l.getId() and
|
||||
i.getScope() = l.getScope().getScope*()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
from Variable l, Name defn
|
||||
where shadowsImport(l) and defn.defines(l) and exists(For for | defn = for.getTarget())
|
||||
select defn, "Loop variable '" + l.getId() + "' shadows an import"
|
||||
|
||||
@@ -14,4 +14,3 @@ import python
|
||||
|
||||
from ImportStar i
|
||||
select i, "Using 'from ... import *' pollutes the namespace"
|
||||
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
import python
|
||||
|
||||
predicate import_and_import_from(Import i1, Import i2, Module m) {
|
||||
i1.getEnclosingModule() = i2.getEnclosingModule() and
|
||||
exists (ImportExpr e1, ImportExpr e2, ImportMember im |
|
||||
e1 = i1.getAName().getValue() and im = i2.getAName().getValue() and e2 = im.getModule() |
|
||||
i1.getEnclosingModule() = i2.getEnclosingModule() and
|
||||
exists(ImportExpr e1, ImportExpr e2, ImportMember im |
|
||||
e1 = i1.getAName().getValue() and im = i2.getAName().getValue() and e2 = im.getModule()
|
||||
|
|
||||
e1.getName() = m.getName() and e2.getName() = m.getName()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,16 +11,15 @@
|
||||
* @id py/multiple-imports-on-line
|
||||
*/
|
||||
|
||||
/* Look for imports of the form:
|
||||
import modA, modB
|
||||
(Imports should be one per line according PEP 8)
|
||||
*/
|
||||
/*
|
||||
* Look for imports of the form:
|
||||
* import modA, modB
|
||||
* (Imports should be one per line according PEP 8)
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
predicate multiple_import(Import imp) {
|
||||
count(imp.getAName()) > 1 and not imp.isFromImport()
|
||||
}
|
||||
predicate multiple_import(Import imp) { count(imp.getAName()) > 1 and not imp.isFromImport() }
|
||||
|
||||
from Import i
|
||||
where multiple_import(i)
|
||||
|
||||
@@ -14,7 +14,8 @@ import python
|
||||
|
||||
predicate modules_imports_itself(ImportingStmt i, ModuleValue m) {
|
||||
i.getEnclosingModule() = m.getScope() and
|
||||
m = max(string s, ModuleValue m_ |
|
||||
m =
|
||||
max(string s, ModuleValue m_ |
|
||||
s = i.getAnImportedModuleName() and
|
||||
m_.importedAs(s)
|
||||
|
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user