mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Initial commit of Python queries and QL libraries.
This commit is contained in:
committed by
Mark Shannon
parent
90c75cd362
commit
5f58824d1b
18
python/ql/src/Classes/ConflictingAttributesInBaseClasses.py
Normal file
18
python/ql/src/Classes/ConflictingAttributesInBaseClasses.py
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
class TCPServer(object):
|
||||
|
||||
def process_request(self, request, client_address):
|
||||
self.do_work(request, client_address)
|
||||
self.shutdown_request(request)
|
||||
|
||||
|
||||
class ThreadingMixIn:
|
||||
"""Mix-in class to handle each request in a new thread."""
|
||||
|
||||
def process_request(self, request, client_address):
|
||||
"""Start a new thread to process the request."""
|
||||
t = threading.Thread(target = self.do_work, args = (request, client_address))
|
||||
t.daemon = self.daemon_threads
|
||||
t.start()
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
|
||||
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
When a class subclasses multiple base classes, attribute lookup is performed from left to right amongst the base classes.
|
||||
This form of attribute lookup is called "method resolution order" and is a solution to the
|
||||
<a href="http://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem">diamond inheritance problem</a> where several base classes
|
||||
override a method in a shared superclass.
|
||||
</p>
|
||||
<p>
|
||||
Unfortunately, this means that if more than one base class defines the same attribute, the leftmost base class will effectively override
|
||||
the attribute of the rightmost base class, even though the leftmost base class is not a subclass of the rightmost base class.
|
||||
Unless the methods in question are designed for inheritance, using <code>super</code>, then this implicit overriding may not be the desired behavior.
|
||||
Even if it is the desired behavior it makes the code hard to understand and maintain.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>There are a number of ways that might be used to address this issue:
|
||||
</p><ul>
|
||||
<li>Override the attribute in the subclass to implement the correct behavior.</li>
|
||||
<li>Modify the class hierarchy and move equivalent or redundant methods to a common super class.</li>
|
||||
<li>Modify the method hierarchy, breaking up complex methods into constituent parts.</li>
|
||||
<li>Use delegation rather than inheritance.</li>
|
||||
</ul>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
In this example the class <code>ThreadingTCPServer</code> inherits from <code>ThreadingMixIn</code> and from <code>TCPServer</code>.
|
||||
However, both these classes implement <code>process_request</code> which means that <code>ThreadingTCPServer</code> will inherit
|
||||
<code>process_request</code> from <code>ThreadingMixIn</code>. Consequently, the implementation of <code>process_request</code> in <code>TCPServer</code>
|
||||
will be ignored, which may not be the correct behavior.
|
||||
</p>
|
||||
<sample src="ConflictingAttributesInBaseClasses.py" />
|
||||
|
||||
<p>
|
||||
This can be fixed either by overriding the method, as shown in class <code>ThreadingTCPServerOverriding</code>
|
||||
or by ensuring that the
|
||||
functionality provided by the two base classes does not overlap, as shown in class <code>ThreadingTCPServerChangedHierarchy</code>.
|
||||
</p>
|
||||
<sample src="ConflictingAttributesInBaseClasses_Fixed.py" />
|
||||
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Language Reference: <a href="https://docs.python.org/2/reference/datamodel.html">Data model</a>.</li>
|
||||
<li>Python releases: <a href="https://www.python.org/download/releases/2.3/mro/">The Python 2.3 Method Resolution Order</a>.</li>
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/C3_linearization">C3 linearization</a>.</li>
|
||||
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
57
python/ql/src/Classes/ConflictingAttributesInBaseClasses.ql
Normal file
57
python/ql/src/Classes/ConflictingAttributesInBaseClasses.ql
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @name Conflicting attributes in base classes
|
||||
* @description When a class subclasses multiple base classes and more than one base class defines the same attribute, attribute overriding may result in unexpected behavior by instances of this class.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* maintainability
|
||||
* modularity
|
||||
* @problem.severity warning
|
||||
* @sub-severity low
|
||||
* @precision high
|
||||
* @id py/conflicting-attributes
|
||||
*/
|
||||
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
||||
/* 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 |
|
||||
meth.getScope() = f.getFunction() and
|
||||
meth.getFunc() = attr and
|
||||
attr.getObject() = sup and
|
||||
attr.getName() = f.getName() and
|
||||
sup.getFunc() = v.getAnAccess() and
|
||||
v.getId() = "super"
|
||||
)
|
||||
}
|
||||
|
||||
/** 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 */
|
||||
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()
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
#Fixed by overriding. This does not change behavior, but makes it explicit and comprehensible.
|
||||
class ThreadingTCPServerOverriding(ThreadingMixIn, TCPServer):
|
||||
|
||||
def process_request(self, request, client_address):
|
||||
#process_request forwards to do_work, so it is OK to call ThreadingMixIn.process_request directly
|
||||
ThreadingMixIn.process_request(self, request, client_address)
|
||||
|
||||
|
||||
#Fixed by separating threading functionality from request handling.
|
||||
class ThreadingMixIn:
|
||||
"""Mix-in class to help with threads."""
|
||||
|
||||
def do_job_in_thread(self, job, args):
|
||||
"""Start a new thread to do the job"""
|
||||
t = threading.Thread(target = job, args = args)
|
||||
t.start()
|
||||
|
||||
class ThreadingTCPServerChangedHierarchy(ThreadingMixIn, TCPServer):
|
||||
|
||||
def process_request(self, request, client_address):
|
||||
"""Start a new thread to process the request."""
|
||||
self.do_job_in_thread(self.do_work, (request, client_address))
|
||||
|
||||
40
python/ql/src/Classes/DefineEqualsWhenAddingAttributes.py
Normal file
40
python/ql/src/Classes/DefineEqualsWhenAddingAttributes.py
Normal file
@@ -0,0 +1,40 @@
|
||||
class Point(object):
|
||||
|
||||
def __init__(self, x, y):
|
||||
self._x = x
|
||||
self._y = y
|
||||
|
||||
def __repr__(self):
|
||||
return 'Point(%r, %r)' % (self._x, self._y)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Point):
|
||||
return False
|
||||
return self._x == other._x and self._y == other._y
|
||||
|
||||
class ColorPoint(Point):
|
||||
|
||||
def __init__(self, x, y, color):
|
||||
Point.__init__(self, x, y)
|
||||
self._color = color
|
||||
|
||||
def __repr__(self):
|
||||
return 'ColorPoint(%r, %r)' % (self._x, self._y, self._color)
|
||||
|
||||
#ColorPoint(0, 0, Red) == ColorPoint(0, 0, Green) should be False, but is True.
|
||||
|
||||
#Fixed version
|
||||
class ColorPoint(Point):
|
||||
|
||||
def __init__(self, x, y, color):
|
||||
Point.__init__(self, x, y)
|
||||
self._color = color
|
||||
|
||||
def __repr__(self):
|
||||
return 'ColorPoint(%r, %r)' % (self._x, self._y, self._color)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, ColorPoint):
|
||||
return False
|
||||
return Point.__eq__(self, other) and self._color = other._color
|
||||
|
||||
37
python/ql/src/Classes/DefineEqualsWhenAddingAttributes.qhelp
Normal file
37
python/ql/src/Classes/DefineEqualsWhenAddingAttributes.qhelp
Normal file
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>A class that defines attributes that are not present in its superclasses
|
||||
may need to override the <code>__eq__()</code> method (<code>__ne__()</code>
|
||||
should also be defined).</p>
|
||||
|
||||
<p>Adding additional attributes without overriding <code>__eq__()</code> means
|
||||
that the additional attributes will not be accounted for in equality tests.</p>
|
||||
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Override the <code>__eq__</code> method.</p>
|
||||
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the following example the <code>ColorPoint</code>
|
||||
class subclasses the <code>Point</code> class and adds a new attribute,
|
||||
but does not override the <code>__eq__</code> method.
|
||||
</p>
|
||||
|
||||
<sample src="DefineEqualsWhenAddingAttributes.py" />
|
||||
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Peter Grogono, Philip Santas: <a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.48.5109&rep=rep1&type=pdf">Equality in Object Oriented Languages</a></li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
52
python/ql/src/Classes/DefineEqualsWhenAddingAttributes.ql
Normal file
52
python/ql/src/Classes/DefineEqualsWhenAddingAttributes.ql
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @name __eq__ not overridden when adding attributes
|
||||
* @description When adding new attributes to instances of a class, equality for that class needs to be defined.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* @problem.severity warning
|
||||
* @sub-severity high
|
||||
* @precision high
|
||||
* @id py/missing-equals
|
||||
*/
|
||||
|
||||
import python
|
||||
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
|
||||
/* Exclude classes used as metaclasses */
|
||||
not cls.getASuperType() = theTypeType()
|
||||
}
|
||||
|
||||
predicate should_override_eq(ClassObject cls, Object base_eq) {
|
||||
not cls.declaresAttribute("__eq__") and
|
||||
exists(ClassObject sup | sup = cls.getABaseType() and sup.declaredAttribute("__eq__") = base_eq |
|
||||
not exists(GenericEqMethod eq | eq.getScope() = sup.getPyClass()) and
|
||||
not exists(IdentityEqMethod eq | eq.getScope() = sup.getPyClass()) and
|
||||
not base_eq.(FunctionObject).getFunction() instanceof IdentityEqMethod and
|
||||
not base_eq = theObjectType().declaredAttribute("__eq__")
|
||||
)
|
||||
}
|
||||
|
||||
/** 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 |
|
||||
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()
|
||||
71
python/ql/src/Classes/Equality.qll
Normal file
71
python/ql/src/Classes/Equality.qll
Normal file
@@ -0,0 +1,71 @@
|
||||
import python
|
||||
|
||||
|
||||
private Attribute dictAccess(LocalVariable var) {
|
||||
result.getName() = "__dict__" and
|
||||
result.getObject() = var.getAnAccess()
|
||||
}
|
||||
|
||||
private Call getattr(LocalVariable obj, LocalVariable attr) {
|
||||
result.getFunc().(Name).getId() = "getattr" and
|
||||
result.getArg(0) = obj.getAnAccess() and
|
||||
result.getArg(1) = attr.getAnAccess()
|
||||
}
|
||||
|
||||
/** A generic equality method that compares all attributes in its dict,
|
||||
* or compares attributes using `getattr`. */
|
||||
class GenericEqMethod extends Function {
|
||||
|
||||
GenericEqMethod() {
|
||||
this.getName() = "__eq__" and
|
||||
exists(LocalVariable self, LocalVariable other |
|
||||
self.getAnAccess() = this.getArg(0) and self.getId() = "self" and
|
||||
other.getAnAccess() = this.getArg(1) and
|
||||
exists(Compare eq |
|
||||
eq.getOp(0) instanceof Eq or
|
||||
eq.getOp(0) instanceof NotEq |
|
||||
// `self.__dict__ == other.__dict__`
|
||||
eq.getAChildNode() = dictAccess(self) and
|
||||
eq.getAChildNode() = dictAccess(other)
|
||||
or
|
||||
// `getattr(self, var) == getattr(other, var)`
|
||||
exists(Variable var |
|
||||
eq.getAChildNode() = getattr(self, var) and
|
||||
eq.getAChildNode() = getattr(other, var)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An __eq__ method that just does self is other */
|
||||
class IdentityEqMethod extends Function {
|
||||
|
||||
IdentityEqMethod() {
|
||||
this.getName() = "__eq__" and
|
||||
exists(LocalVariable self, LocalVariable other |
|
||||
self.getAnAccess() = this.getArg(0) and self.getId() = "self" and
|
||||
other.getAnAccess() = this.getArg(1) and
|
||||
exists(Compare eq | eq.getOp(0) instanceof Is |
|
||||
eq.getAChildNode() = self.getAnAccess() and
|
||||
eq.getAChildNode() = other.getAnAccess()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** An (in)equality method that delegates to its complement */
|
||||
class DelegatingEqualityMethod extends Function {
|
||||
|
||||
DelegatingEqualityMethod() {
|
||||
exists(Return ret, UnaryExpr not_, Compare comp, Cmpop op, Parameter p0, Parameter p1 |
|
||||
ret.getScope() = this and
|
||||
ret.getValue() = not_ and
|
||||
not_.getOp() instanceof Not and not_.getOperand() = comp and
|
||||
comp.compares(p0.getVariable().getAnAccess(), op, p1.getVariable().getAnAccess()) |
|
||||
this.getName() = "__eq__" and op instanceof NotEq or
|
||||
this.getName() = "__ne__" and op instanceof Eq
|
||||
)
|
||||
}
|
||||
}
|
||||
52
python/ql/src/Classes/EqualsOrHash.py
Normal file
52
python/ql/src/Classes/EqualsOrHash.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# Incorrect: equality method defined but class contains no hash method
|
||||
class Point(object):
|
||||
|
||||
def __init__(self, x, y):
|
||||
self._x = x
|
||||
self._y = y
|
||||
|
||||
def __repr__(self):
|
||||
return 'Point(%r, %r)' % (self._x, self._y)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Point):
|
||||
return False
|
||||
return self._x == other._x and self._y == other._y
|
||||
|
||||
|
||||
# Improved: equality and hash method defined (inequality method still missing)
|
||||
class PointUpdated(object):
|
||||
|
||||
def __init__(self, x, y):
|
||||
self._x = x
|
||||
self._y = y
|
||||
|
||||
def __repr__(self):
|
||||
return 'Point(%r, %r)' % (self._x, self._y)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Point):
|
||||
return False
|
||||
return self._x == other._x and self._y == other._y
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._x) ^ hash(self._y)
|
||||
|
||||
# Improved: equality method defined and class instances made unhashable
|
||||
class UnhashablePoint(object):
|
||||
|
||||
def __init__(self, x, y):
|
||||
self._x = x
|
||||
self._y = y
|
||||
|
||||
def __repr__(self):
|
||||
return 'Point(%r, %r)' % (self._x, self._y)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Point):
|
||||
return False
|
||||
return self._x == other._x and self._y == other._y
|
||||
|
||||
#Tell the interpreter that instances of this class cannot be hashed
|
||||
__hash__ = None
|
||||
|
||||
46
python/ql/src/Classes/EqualsOrHash.qhelp
Normal file
46
python/ql/src/Classes/EqualsOrHash.qhelp
Normal file
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>In order to conform to the object model, classes that define their own equality method should also
|
||||
define their own hash method, or be unhashable. If the hash method is not defined then the <code>hash</code> of the
|
||||
super class is used. This is unlikely to result in the expected behavior.</p>
|
||||
|
||||
<p>A class can be made unhashable by setting its <code>__hash__</code> attribute to <code>None</code>.</p>
|
||||
|
||||
<p>In Python 3, if you define a class-level equality method and omit a <code>__hash__</code> method
|
||||
then the class is automatically marked as unhashable.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>When you define an <code>__eq__</code> method for a class, remember to implement a <code>__hash__</code> method or set
|
||||
<code>__hash__ = None</code>.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the following example the <code>Point</code> class defines an equality method but
|
||||
no hash method. If hash is called on this class then the hash method defined for <code>object</code>
|
||||
is used. This is unlikely to give the required behavior. The <code>PointUpdated</code> class
|
||||
is better as it defines both an equality and a hash method.
|
||||
If <code>Point</code> was not to be used in <code>dict</code>s or <code>set</code>s, then it could be defined as
|
||||
<code>UnhashablePoint</code> below.
|
||||
</p>
|
||||
<p>
|
||||
To comply fully with the object model this class should also define an inequality method (identified
|
||||
by a separate rule).</p>
|
||||
|
||||
<sample src="EqualsOrHash.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/reference/datamodel.html#object.__hash__">object.__hash__</a>.</li>
|
||||
<li>Python Glossary: <a href="http://docs.python.org/2/glossary.html#term-hashable">hashable</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
46
python/ql/src/Classes/EqualsOrHash.ql
Normal file
46
python/ql/src/Classes/EqualsOrHash.ql
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @name Inconsistent equality and hashing
|
||||
* @description Defining equality for a class without also defining hashability (or vice-versa) violates the object model.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* external/cwe/cwe-581
|
||||
* @problem.severity warning
|
||||
* @sub-severity high
|
||||
* @precision very-high
|
||||
* @id py/equals-hash-mismatch
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
FunctionObject defines_equality(ClassObject c, string name) {
|
||||
(name = "__eq__" or major_version() = 2 and name = "__cmp__")
|
||||
and
|
||||
result = c.declaredAttribute(name)
|
||||
}
|
||||
|
||||
FunctionObject implemented_method(ClassObject c, string name) {
|
||||
result = defines_equality(c, name)
|
||||
or
|
||||
result = c.declaredAttribute("__hash__") and name = "__hash__"
|
||||
}
|
||||
|
||||
string unimplemented_method(ClassObject c) {
|
||||
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
|
||||
}
|
||||
|
||||
predicate violates_hash_contract(ClassObject c, string present, string missing, Object method) {
|
||||
not c.unhashable() and
|
||||
missing = unimplemented_method(c) and
|
||||
method = implemented_method(c, present) and
|
||||
not c.unknowableAttributes()
|
||||
}
|
||||
|
||||
from ClassObject c, string present, string missing, FunctionObject method
|
||||
where violates_hash_contract(c, present, missing, method) and
|
||||
exists(c.getPyClass()) // Suppress results that aren't from source
|
||||
select method, "Class $@ implements " + present + " but does not define " + missing + ".", c, c.getName()
|
||||
32
python/ql/src/Classes/EqualsOrNotEquals.py
Normal file
32
python/ql/src/Classes/EqualsOrNotEquals.py
Normal file
@@ -0,0 +1,32 @@
|
||||
class PointOriginal(object):
|
||||
|
||||
def __init__(self, x, y):
|
||||
self._x, x
|
||||
self._y = y
|
||||
|
||||
def __repr__(self):
|
||||
return 'Point(%r, %r)' % (self._x, self._y)
|
||||
|
||||
def __eq__(self, other): # Incorrect: equality is defined but inequality is not
|
||||
if not isinstance(other, Point):
|
||||
return False
|
||||
return self._x == other._x and self._y == other._y
|
||||
|
||||
|
||||
class PointUpdated(object):
|
||||
|
||||
def __init__(self, x, y):
|
||||
self._x, x
|
||||
self._y = y
|
||||
|
||||
def __repr__(self):
|
||||
return 'Point(%r, %r)' % (self._x, self._y)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Point):
|
||||
return False
|
||||
return self._x == other._x and self._y == other._y
|
||||
|
||||
def __ne__(self, other): # Improved: equality and inequality method defined (hash method still missing)
|
||||
return not self == other
|
||||
|
||||
37
python/ql/src/Classes/EqualsOrNotEquals.qhelp
Normal file
37
python/ql/src/Classes/EqualsOrNotEquals.qhelp
Normal file
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>In order to conform to the object model, classes should define either no equality methods, or both
|
||||
an equality and an inequality method. If only one of <code>__eq__</code> or <code>__ne__</code> is
|
||||
defined then the method from the super class is used. This is unlikely to result in the expected
|
||||
behavior.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>When you define an equality or an inequality method for a class, remember to implement both an
|
||||
<code>__eq__</code> method and an <code>__ne__</code> method.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the following example the <code>PointOriginal</code> class defines an equality method but
|
||||
no inequality method. If this class is tested for inequality then a type error will be raised. The
|
||||
<code>PointUpdated</code> class is better as it defines both an equality and an inequality method. To
|
||||
comply fully with the object model this class should also define a hash method (identified by
|
||||
a separate rule).</p>
|
||||
|
||||
<sample src="EqualsOrNotEquals.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/2/reference/datamodel.html#object.__ne__">object.__ne__</a>,
|
||||
<a href="http://docs.python.org/2/reference/expressions.html#comparisons">Comparisons</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
50
python/ql/src/Classes/EqualsOrNotEquals.ql
Normal file
50
python/ql/src/Classes/EqualsOrNotEquals.ql
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @name Inconsistent equality and inequality
|
||||
* @description Defining only an equality method or an inequality method for a class violates the object model.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* @problem.severity warning
|
||||
* @sub-severity high
|
||||
* @precision very-high
|
||||
* @id py/inconsistent-equality
|
||||
*/
|
||||
|
||||
import python
|
||||
import Equality
|
||||
|
||||
string equals_or_ne() {
|
||||
result = "__eq__" or result = "__ne__"
|
||||
}
|
||||
|
||||
predicate total_ordering(Class cls) {
|
||||
exists(Attribute a | a = cls.getADecorator() |
|
||||
a.getName() = "total_ordering")
|
||||
or
|
||||
exists(Name n | n = cls.getADecorator() |
|
||||
n.getId() = "total_ordering")
|
||||
}
|
||||
|
||||
FunctionObject implemented_method(ClassObject c, string name) {
|
||||
result = c.declaredAttribute(name) and name = equals_or_ne()
|
||||
}
|
||||
|
||||
string unimplemented_method(ClassObject c) {
|
||||
not c.declaresAttribute(result) and result = equals_or_ne()
|
||||
}
|
||||
|
||||
predicate violates_equality_contract(ClassObject c, string present, string missing, FunctionObject method) {
|
||||
missing = unimplemented_method(c) and
|
||||
method = implemented_method(c, present) and
|
||||
not c.unknowableAttributes() and
|
||||
not total_ordering(c.getPyClass()) 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.getFunction() instanceof DelegatingEqualityMethod and
|
||||
not c.lookupAttribute(missing).(FunctionObject).getFunction() instanceof DelegatingEqualityMethod
|
||||
}
|
||||
|
||||
from ClassObject c, string present, string missing, FunctionObject method
|
||||
where violates_equality_contract(c, present, missing, method)
|
||||
|
||||
select method, "Class $@ implements " + present + " but does not implement " + missing + ".", c, c.getName()
|
||||
6
python/ql/src/Classes/IncompleteOrdering.py
Normal file
6
python/ql/src/Classes/IncompleteOrdering.py
Normal file
@@ -0,0 +1,6 @@
|
||||
class IncompleteOrdering(object):
|
||||
def __init__(self, i):
|
||||
self.i = i
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.i < other.i
|
||||
35
python/ql/src/Classes/IncompleteOrdering.qhelp
Normal file
35
python/ql/src/Classes/IncompleteOrdering.qhelp
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p> A class that implements an ordering operator
|
||||
(<code>__lt__</code>, <code>__gt__</code>, <code>__le__</code> or <code>__ge__</code>) should implement
|
||||
all four in order that ordering between two objects is consistent and obeys the usual mathematical rules.
|
||||
If the ordering is inconsistent with default equality, then <code>__eq__</code> and <code>__ne__</code>
|
||||
should also be implemented.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Ensure that all four ordering comparisons are implemented as well as <code>__eq__</code> and <code>
|
||||
__ne__</code> if required.</p>
|
||||
|
||||
<p>It is not necessary to manually implement all four comparisons,
|
||||
the <code>functools.total_ordering</code> class decorator can be used.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In this example only the <code>__lt__</code> operator has been implemented which could lead to
|
||||
inconsistent behavior. <code>__gt__</code>, <code>__le__</code>, <code>__ge__</code>, and in this case,
|
||||
<code>__eq__</code> and <code>__ne__</code> should be implemented.</p>
|
||||
<sample src="IncompleteOrdering.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/2/reference/datamodel.html#object.__lt__">Rich comparisons in Python</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
75
python/ql/src/Classes/IncompleteOrdering.ql
Normal file
75
python/ql/src/Classes/IncompleteOrdering.ql
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @name Incomplete ordering
|
||||
* @description Class defines one or more ordering method but does not define all 4 ordering comparison methods
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* @problem.severity warning
|
||||
* @sub-severity low
|
||||
* @precision very-high
|
||||
* @id py/incomplete-ordering
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
predicate total_ordering(Class cls) {
|
||||
exists(Attribute a | a = cls.getADecorator() |
|
||||
a.getName() = "total_ordering")
|
||||
or
|
||||
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 = "__ge__" and n = 4
|
||||
}
|
||||
|
||||
predicate overrides_ordering_method(ClassObject c, string name) {
|
||||
name = ordering_name(_) and
|
||||
(
|
||||
c.declaresAttribute(name)
|
||||
or
|
||||
exists(ClassObject sup |
|
||||
sup = c.getASuperType() and not sup = theObjectType() |
|
||||
sup.declaresAttribute(name)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
string unimplemented_ordering(ClassObject c, int n) {
|
||||
not c = theObjectType() and
|
||||
not overrides_ordering_method(c, result) and
|
||||
result = ordering_name(n)
|
||||
}
|
||||
|
||||
string unimplemented_ordering_methods(ClassObject 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) |
|
||||
prefix = "" and result = unimplemented_ordering(c, n)
|
||||
or
|
||||
result = prefix and not exists(unimplemented_ordering(c, n)) and n < 5
|
||||
or
|
||||
prefix != "" and result = prefix + " or " + unimplemented_ordering(c, n)
|
||||
)
|
||||
}
|
||||
|
||||
Object ordering_method(ClassObject 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)
|
||||
}
|
||||
|
||||
from ClassObject c, Object ordering, string name
|
||||
where not c.unknowableAttributes() and
|
||||
not total_ordering(c.getPyClass())
|
||||
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
|
||||
|
||||
|
||||
6
python/ql/src/Classes/InconsistentMRO.py
Normal file
6
python/ql/src/Classes/InconsistentMRO.py
Normal file
@@ -0,0 +1,6 @@
|
||||
class X(object):
|
||||
def __init__(self):
|
||||
print("X")
|
||||
class Y(object,X):
|
||||
def __init__(self):
|
||||
print("Y")
|
||||
30
python/ql/src/Classes/InconsistentMRO.qhelp
Normal file
30
python/ql/src/Classes/InconsistentMRO.qhelp
Normal file
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Python 2.3 introduced new-style classes (classes inheriting from object). New-style classes use
|
||||
the C3 linearization method to determine a method resolution ordering (MRO) for each class. The C3
|
||||
linearization method ensures that for a class C, if a class C1 precedes a class C2 in the MRO of C
|
||||
then C1 should also precede C2 in the MRO of all subclasses of C. It is possible to create a
|
||||
situation where it is impossible to achieve this consistency and this will guarantee that a <code>
|
||||
TypeError</code> will be raised at runtime.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Use a class hierarchy that is not ambiguous.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>The MRO of class <code>X</code> is just <code>X, object</code>. The program will fail when the MRO
|
||||
of class <code>Y</code> needs to be calculated because <code>object</code> precedes <code>X</code> in
|
||||
the definition of <code>Y</code> but the opposite is true in the MRO of <code>X</code>.</p>
|
||||
<sample src="InconsistentMRO.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python: <a href="http://www.python.org/download/releases/2.3/mro/">The Python 2.3 Method Resolution Order</a>.</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
27
python/ql/src/Classes/InconsistentMRO.ql
Normal file
27
python/ql/src/Classes/InconsistentMRO.ql
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @name Inconsistent method resolution order
|
||||
* @description Class definition will raise a type error at runtime due to inconsistent method resolution order(MRO)
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* @problem.severity error
|
||||
* @sub-severity high
|
||||
* @precision very-high
|
||||
* @id py/inconsistent-mro
|
||||
*/
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
predicate invalid_mro(ClassObject t, ClassObject left, ClassObject right) {
|
||||
t.isNewStyle() and
|
||||
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()
|
||||
48
python/ql/src/Classes/InitCallsSubclassMethod.py
Normal file
48
python/ql/src/Classes/InitCallsSubclassMethod.py
Normal file
@@ -0,0 +1,48 @@
|
||||
#Superclass __init__ calls subclass method
|
||||
|
||||
class Super(object):
|
||||
|
||||
def __init__(self, arg):
|
||||
self._state = "Not OK"
|
||||
self.set_up(arg)
|
||||
self._state = "OK"
|
||||
|
||||
def set_up(self, arg):
|
||||
"Do some set up"
|
||||
|
||||
class Sub(Super):
|
||||
|
||||
def __init__(self, arg):
|
||||
Super.__init__(self, arg)
|
||||
self.important_state = "OK"
|
||||
|
||||
def set_up(self, arg):
|
||||
Super.set_up(self, arg)
|
||||
"Do some more set up" # Dangerous as self._state is "Not OK"
|
||||
|
||||
|
||||
#Improved version with inheritance:
|
||||
|
||||
class Super(object):
|
||||
|
||||
def __init__(self, arg):
|
||||
self._state = "Not OK"
|
||||
self.super_set_up(arg)
|
||||
self._state = "OK"
|
||||
|
||||
def super_set_up(self, arg):
|
||||
"Do some set up"
|
||||
|
||||
|
||||
class Sub(Super):
|
||||
|
||||
def __init__(self, arg):
|
||||
Super.__init__(self, arg)
|
||||
self.sub_set_up(self, arg)
|
||||
self.important_state = "OK"
|
||||
|
||||
|
||||
def sub_set_up(self, arg):
|
||||
"Do some more set up"
|
||||
|
||||
|
||||
42
python/ql/src/Classes/InitCallsSubclassMethod.qhelp
Normal file
42
python/ql/src/Classes/InitCallsSubclassMethod.qhelp
Normal file
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
When an instance of a class is initialized, the super-class state should be
|
||||
fully initialized before it becomes visible to the subclass.
|
||||
Calling methods of the subclass in the superclass' <code>__init__</code>
|
||||
method violates this important invariant.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Do not use methods that are subclassed in the construction of an object.
|
||||
For simpler cases move the initialization into the superclass' <code>__init__</code> method,
|
||||
preventing it being overridden. Additional initialization of subclass should
|
||||
be done in the <code>__init__</code> method of the subclass.
|
||||
For more complex cases, it is advisable to use a static method or function to manage
|
||||
object creation.
|
||||
</p>
|
||||
|
||||
<p>Alternatively, avoid inheritance altogether using composition instead.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<sample src="InitCallsSubclassMethod.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>CERT Secure Coding: <a href="https://www.securecoding.cert.org/confluence/display/java/MET05-J.+Ensure+that+constructors+do+not+call+overridable+methods">
|
||||
Rule MET05-J</a>. Although this is a Java rule it applies to most object-oriented languages.</li>
|
||||
<li>Python Standard Library: <a href="http://docs.python.org/library/functions.html#staticmethod">Static methods</a>.</li>
|
||||
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
|
||||
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
35
python/ql/src/Classes/InitCallsSubclassMethod.ql
Normal file
35
python/ql/src/Classes/InitCallsSubclassMethod.ql
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @name __init__ method calls overridden method
|
||||
* @description Calling a method from __init__ that is overridden by a subclass may result in a partially
|
||||
* initialized instance being observed.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* @problem.severity warning
|
||||
* @sub-severity low
|
||||
* @precision high
|
||||
* @id py/init-calls-subclass
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
||||
|
||||
25
python/ql/src/Classes/MaybeUndefinedClassAttribute.py
Normal file
25
python/ql/src/Classes/MaybeUndefinedClassAttribute.py
Normal file
@@ -0,0 +1,25 @@
|
||||
class Spam:
|
||||
|
||||
def __init__(self):
|
||||
self.spam = 'spam, spam, spam'
|
||||
|
||||
def set_eggs(eggs):
|
||||
self.eggs = eggs
|
||||
|
||||
def __str__(self):
|
||||
return '%s and %s' % (self.spam, self.eggs) # Maybe uninitialized attribute 'eggs'
|
||||
|
||||
#Fixed version
|
||||
|
||||
class Spam:
|
||||
|
||||
def __init__(self):
|
||||
self.spam = 'spam, spam, spam'
|
||||
self.eggs = None
|
||||
|
||||
def set_eggs(eggs):
|
||||
self.eggs = eggs
|
||||
|
||||
def __str__(self):
|
||||
return '%s and %s' % (self.spam, self.eggs) # OK
|
||||
|
||||
32
python/ql/src/Classes/MaybeUndefinedClassAttribute.qhelp
Normal file
32
python/ql/src/Classes/MaybeUndefinedClassAttribute.qhelp
Normal file
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
|
||||
<overview>
|
||||
<p>A possibly non-existent attribute of <code>self</code> is accessed in a method.
|
||||
The attribute is set in another method of the class, but may be uninitialized if the
|
||||
method that uses the attribute is called before the one that sets it.
|
||||
This may result in an <code>AttributeError</code> at run time.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Ensure that all attributes are initialized in the <code>__init__</code> method.</p>
|
||||
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<sample src="MaybeUndefinedClassAttribute.py" />
|
||||
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Standard Library: <a href="http://docs.python.org/library/exceptions.html#exceptions.AttributeError">exception AttributeError</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
40
python/ql/src/Classes/MaybeUndefinedClassAttribute.ql
Normal file
40
python/ql/src/Classes/MaybeUndefinedClassAttribute.ql
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @name Maybe undefined class attribute
|
||||
* @description Accessing an attribute of 'self' that is not initialized in the __init__ method may cause an AttributeError at runtime
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision low
|
||||
* @id py/maybe-undefined-attribute
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.SelfAttribute
|
||||
|
||||
predicate guarded_by_other_attribute(SelfAttributeRead a, CheckClass c) {
|
||||
c.sometimesDefines(a.getName()) and
|
||||
exists(SelfAttributeRead guard, If i |
|
||||
i.contains(a) and
|
||||
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
|
||||
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"
|
||||
|
||||
67
python/ql/src/Classes/MethodCallOrder.qll
Normal file
67
python/ql/src/Classes/MethodCallOrder.qll
Normal file
@@ -0,0 +1,67 @@
|
||||
import python
|
||||
|
||||
// Helper predicates for multiple call to __init__/__del__ queries.
|
||||
|
||||
pragma [noinline]
|
||||
private predicate multiple_invocation_paths(FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject multi) {
|
||||
i1 != i2 and
|
||||
i1 = top.getACallee+() and
|
||||
i2 = top.getACallee+() and
|
||||
i1.getFunction() = multi and
|
||||
i2.getFunction() = multi
|
||||
}
|
||||
|
||||
/** Holds if `self.name` calls `multi` by multiple paths, and thus calls it more than once. */
|
||||
predicate multiple_calls_to_superclass_method(ClassObject self, FunctionObject multi, string name) {
|
||||
exists(FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2 |
|
||||
multiple_invocation_paths(top, i1, i2, multi) and
|
||||
top.runtime(self.declaredAttribute(name)) and
|
||||
self.getASuperType().declaredAttribute(name) = multi |
|
||||
/* Only called twice if called from different functions,
|
||||
* or if one call-site can reach the other */
|
||||
i1.getCall().getScope() != i2.getCall().getScope()
|
||||
or
|
||||
i1.getCall().strictlyReaches(i2.getCall())
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if all attributes called `name` can be inferred to be methods. */
|
||||
private predicate named_attributes_not_method(ClassObject cls, string name) {
|
||||
cls.declaresAttribute(name) and not cls.declaredAttribute(name) instanceof FunctionObject
|
||||
}
|
||||
|
||||
/** Holds if `f` actually does something. */
|
||||
private predicate does_something(FunctionObject f) {
|
||||
f.isBuiltin() and not f = theObjectType().lookupAttribute("__init__")
|
||||
or
|
||||
exists(Stmt s | s = f.getFunction().getAStmt() and not s instanceof Pass)
|
||||
}
|
||||
|
||||
/** Holds if `meth` looks like it should have a call to `name`, but does not */
|
||||
private predicate missing_call(FunctionObject meth, string name) {
|
||||
exists(CallNode call, AttrNode attr |
|
||||
call.getScope() = meth.getFunction() and
|
||||
call.getFunction() = attr and
|
||||
attr.getName() = name and
|
||||
not exists(FunctionObject f | f.getACall() = call)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `self.name` does not call `missing`, even though it is expected to. */
|
||||
predicate missing_call_to_superclass_method(ClassObject self, FunctionObject top, FunctionObject missing, string name) {
|
||||
missing = self.getASuperType().declaredAttribute(name) and
|
||||
top = self.lookupAttribute(name) and
|
||||
/* There is no call to missing originating from top */
|
||||
not top.getACallee*() = missing and
|
||||
/* Make sure that all named 'methods' are objects that we can understand. */
|
||||
not exists(ClassObject sup |
|
||||
sup = self.getAnImproperSuperType() and
|
||||
named_attributes_not_method(sup, name)
|
||||
) and
|
||||
not self.isAbstract()
|
||||
and
|
||||
does_something(missing)
|
||||
and
|
||||
not missing_call(top, name)
|
||||
}
|
||||
|
||||
26
python/ql/src/Classes/MissingCallToDel.py
Normal file
26
python/ql/src/Classes/MissingCallToDel.py
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
class Vehicle(object):
|
||||
|
||||
def __del__(self):
|
||||
recycle(self.base_parts)
|
||||
|
||||
class Car(Vehicle):
|
||||
|
||||
def __del__(self):
|
||||
recycle(self.car_parts)
|
||||
Vehicle.__del__(self)
|
||||
|
||||
#Car.__del__ is missed out.
|
||||
class SportsCar(Car, Vehicle):
|
||||
|
||||
def __del__(self):
|
||||
recycle(self.sports_car_parts)
|
||||
Vehicle.__del__(self)
|
||||
|
||||
#Fix SportsCar by calling Car.__del__
|
||||
class FixedSportsCar(Car, Vehicle):
|
||||
|
||||
def __del__(self):
|
||||
recycle(self.sports_car_parts)
|
||||
Car.__del__(self)
|
||||
|
||||
50
python/ql/src/Classes/MissingCallToDel.qhelp
Normal file
50
python/ql/src/Classes/MissingCallToDel.qhelp
Normal file
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object destruction.
|
||||
However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies.
|
||||
Therefore the developer has responsibility for ensuring that objects are properly cleaned up when
|
||||
there are multiple <code>__del__</code> methods that need to be called.
|
||||
</p>
|
||||
<p>
|
||||
If the <code>__del__</code> method of a superclass is not called during object destruction it is likely that
|
||||
that resources may be leaked.
|
||||
</p>
|
||||
|
||||
<p>A call to the <code>__del__</code> method of a superclass during object destruction may be omitted:
|
||||
</p>
|
||||
<ul>
|
||||
<li>When a subclass calls the <code>__del__</code> method of the wrong class.</li>
|
||||
<li>When a call to the <code>__del__</code> method of one its base classes is omitted.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Either be careful to explicitly call the <code>__del__</code> of the correct base class, or
|
||||
use <code>super()</code> throughout the inheritance hierarchy.</p>
|
||||
|
||||
<p>Alternatively refactor one or more of the classes to use composition rather than inheritance.</p>
|
||||
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In this example, explicit calls to <code>__del__</code> are used, but <code>SportsCar</code> erroneously calls
|
||||
<code>Vehicle.__del__</code>. This is fixed in <code>FixedSportsCar</code> by calling <code>Car.__del__</code>.
|
||||
</p>
|
||||
|
||||
<sample src="MissingCallToDel.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Tutorial: <a href="https://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
|
||||
<li>Python Standard Library: <a href="https://docs.python.org/2/library/functions.html#super">super</a>.</li>
|
||||
<li>Artima Developer: <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=236275">Things to Know About Python Super</a>.</li>
|
||||
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
26
python/ql/src/Classes/MissingCallToDel.ql
Normal file
26
python/ql/src/Classes/MissingCallToDel.ql
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @name Missing call to __del__ during object destruction
|
||||
* @description An omitted call to a super-class __del__ method may lead to class instances not being cleaned up properly.
|
||||
* @kind problem
|
||||
* @tags efficiency
|
||||
* correctness
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision high
|
||||
* @id py/missing-call-to-delete
|
||||
*/
|
||||
|
||||
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()
|
||||
|
||||
26
python/ql/src/Classes/MissingCallToInit.py
Normal file
26
python/ql/src/Classes/MissingCallToInit.py
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
class Vehicle(object):
|
||||
|
||||
def __init__(self):
|
||||
self.mobile = True
|
||||
|
||||
class Car(Vehicle):
|
||||
|
||||
def __init__(self):
|
||||
Vehicle.__init__(self)
|
||||
self.car_init()
|
||||
|
||||
#Car.__init__ is missed out.
|
||||
class SportsCar(Car, Vehicle):
|
||||
|
||||
def __init__(self):
|
||||
Vehicle.__init__(self)
|
||||
self.sports_car_init()
|
||||
|
||||
#Fix SportsCar by calling Car.__init__
|
||||
class FixedSportsCar(Car, Vehicle):
|
||||
|
||||
def __init__(self):
|
||||
Car.__init__(self)
|
||||
self.sports_car_init()
|
||||
|
||||
52
python/ql/src/Classes/MissingCallToInit.qhelp
Normal file
52
python/ql/src/Classes/MissingCallToInit.qhelp
Normal file
@@ -0,0 +1,52 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object initialization.
|
||||
However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies.
|
||||
Therefore the developer has responsibility for ensuring that objects are properly initialized when
|
||||
there are multiple <code>__init__</code> methods that need to be called.
|
||||
</p>
|
||||
<p>
|
||||
If the <code>__init__</code> method of a superclass is not called during object initialization it is likely that
|
||||
that object will end up in an incorrect state.
|
||||
</p>
|
||||
|
||||
<p>A call to the <code>__init__</code> method of a superclass during object initialization may be omitted:
|
||||
</p>
|
||||
<ul>
|
||||
<li>When a subclass calls the <code>__init__</code> method of the wrong class.</li>
|
||||
<li>When a call to the <code>__init__</code> method of one its base classes is omitted.</li>
|
||||
<li>When multiple inheritance is used and a class inherits from several base classes,
|
||||
and at least one of those does not use <code>super()</code> in its own <code>__init__</code> method.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Either be careful to explicitly call the <code>__init__</code> of the correct base class, or
|
||||
use <code>super()</code> throughout the inheritance hierarchy.</p>
|
||||
|
||||
<p>Alternatively refactor one or more of the classes to use composition rather than inheritance.</p>
|
||||
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In this example, explicit calls to <code>__init__</code> are used, but <code>SportsCar</code> erroneously calls
|
||||
<code>Vehicle.__init__</code>. This is fixed in <code>FixedSportsCar</code> by calling <code>Car.__init__</code>.
|
||||
</p>
|
||||
|
||||
<sample src="MissingCallToInit.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Tutorial: <a href="https://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
|
||||
<li>Python Standard Library: <a href="https://docs.python.org/2/library/functions.html#super">super</a>.</li>
|
||||
<li>Artima Developer: <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=236275">Things to Know About Python Super</a>.</li>
|
||||
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
28
python/ql/src/Classes/MissingCallToInit.ql
Normal file
28
python/ql/src/Classes/MissingCallToInit.ql
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @name Missing call to __init__ during object initialization
|
||||
* @description An omitted call to a super-class __init__ method may lead to objects of this class not being fully initialized.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision high
|
||||
* @id py/missing-call-to-init
|
||||
*/
|
||||
|
||||
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
|
||||
// If a superclass is incorrect, don't flag this class as well.
|
||||
not missing_call_to_superclass_method(self.getASuperType(), _, missing, "__init__") and
|
||||
not missing.neverReturns() and
|
||||
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"
|
||||
40
python/ql/src/Classes/MutatingDescriptor.py
Normal file
40
python/ql/src/Classes/MutatingDescriptor.py
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
#This is prone to strange side effects and race conditions.
|
||||
class MutatingDescriptor(object):
|
||||
|
||||
def __init__(self, func):
|
||||
self.my_func = func
|
||||
|
||||
def __get__(self, obj, obj_type):
|
||||
#Modified state is visible to all instances of C that might call "show".
|
||||
self.my_obj = obj
|
||||
return self
|
||||
|
||||
def __call__(self, *args):
|
||||
return self.my_func(self.my_obj, *args)
|
||||
|
||||
def show(obj):
|
||||
print (obj)
|
||||
|
||||
class C(object):
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return ("C: " + str(self.value))
|
||||
|
||||
show = MutatingDescriptor(show)
|
||||
|
||||
c1 = C(1)
|
||||
c1.show()
|
||||
c2 = C(2)
|
||||
c2.show()
|
||||
c1_show = c1.show
|
||||
c2.show
|
||||
c1_show()
|
||||
|
||||
#Outputs:
|
||||
#C: 1
|
||||
#C: 2
|
||||
#C: 2
|
||||
42
python/ql/src/Classes/MutatingDescriptor.qhelp
Normal file
42
python/ql/src/Classes/MutatingDescriptor.qhelp
Normal file
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>The descriptor protocol allows user programmable attribute access.
|
||||
The descriptor protocol is what enables class methods, static methods, properties and <code>super()</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Descriptor objects are class attributes which control the behavior of instance attributes. Consequently, a single descriptor
|
||||
is common to all instances of a class and should not be mutated when instance attributes are accessed.
|
||||
</p>
|
||||
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Do not mutate the descriptor object, rather create a new object that contains the necessary state.</p>
|
||||
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In this example the descriptor class <code>MutatingDescriptor</code> stores a reference to <code>obj</code> in an attribute.
|
||||
</p><sample src="MutatingDescriptor.py" />
|
||||
|
||||
<p>In the following example, the descriptor class <code>NonMutatingDescriptor</code> returns a new object every time <code>__get__</code>
|
||||
is called.
|
||||
|
||||
</p><sample src="MutatingDescriptorFixed.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Language Reference: <a href="https://docs.python.org/reference/datamodel.html#descriptors">Implementing Descriptors.</a></li>
|
||||
<li>Mark Lutz. <em>Learning Python</em>, Section 30.6: Methods Are Objects: Bound or Unbound. O'Reilly 2013.</li>
|
||||
<li>A real world example: <a href="https://github.com/numpy/numpy/issues/5247">NumPy Issue 5247.</a></li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
28
python/ql/src/Classes/MutatingDescriptor.ql
Normal file
28
python/ql/src/Classes/MutatingDescriptor.ql
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @name Mutation of descriptor in __get__ or __set__ method.
|
||||
* @description Descriptor objects can be shared across many instances. Mutating them can cause strange side effects or race conditions.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision very-high
|
||||
* @id py/mutable-descriptor
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
predicate mutates_descriptor(ClassObject cls, SelfAttributeStore s) {
|
||||
cls.isDescriptorType() and
|
||||
exists(PyFunctionObject f |
|
||||
cls.lookupAttribute(_) = f and
|
||||
not f.getName() = "__init__" and
|
||||
s.getScope() = f.getFunction()
|
||||
)
|
||||
}
|
||||
|
||||
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()
|
||||
37
python/ql/src/Classes/MutatingDescriptorFixed.py
Normal file
37
python/ql/src/Classes/MutatingDescriptorFixed.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import types
|
||||
|
||||
#Immutable version, which is safe to share.
|
||||
class NonMutatingDescriptor(object):
|
||||
|
||||
def __init__(self, func):
|
||||
self.my_func = func
|
||||
|
||||
def __get__(self, obj, obj_type):
|
||||
#Return a new object to each access.
|
||||
return types.MethodType(self.my_func, obj)
|
||||
|
||||
def show(obj):
|
||||
print (obj)
|
||||
|
||||
class C(object):
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return ("C: " + str(self.value))
|
||||
|
||||
show = NonMutatingDescriptor(show)
|
||||
|
||||
c1 = C(1)
|
||||
c1.show()
|
||||
c2 = C(2)
|
||||
c2.show()
|
||||
c1_show = c1.show
|
||||
c2.show
|
||||
c1_show()
|
||||
|
||||
#Outputs:
|
||||
#C: 1
|
||||
#C: 2
|
||||
#C: 1
|
||||
35
python/ql/src/Classes/OverwritingAttributeInSuperClass.py
Normal file
35
python/ql/src/Classes/OverwritingAttributeInSuperClass.py
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
#Attribute set in both superclass and subclass
|
||||
class C(object):
|
||||
|
||||
def __init__(self):
|
||||
self.var = 0
|
||||
|
||||
class D(C):
|
||||
|
||||
def __init__(self):
|
||||
self.var = 1 # self.var will be overwritten
|
||||
C.__init__(self)
|
||||
|
||||
class E(object):
|
||||
|
||||
def __init__(self):
|
||||
self.var = 0 # self.var will be overwritten
|
||||
|
||||
class F(E):
|
||||
|
||||
def __init__(self):
|
||||
E.__init__(self)
|
||||
self.var = 1
|
||||
|
||||
#Fixed version -- Pass explicitly as a parameter
|
||||
class G(object):
|
||||
|
||||
def __init__(self, var = 0):
|
||||
self.var = var
|
||||
|
||||
class H(G):
|
||||
|
||||
def __init__(self):
|
||||
G.__init__(self, 1)
|
||||
|
||||
29
python/ql/src/Classes/OverwritingAttributeInSuperClass.qhelp
Normal file
29
python/ql/src/Classes/OverwritingAttributeInSuperClass.qhelp
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Subclasses should not set attributes that are set in the superclass.
|
||||
Doing so may violate invariants in the superclass. </p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
If you did not intend to override the attribute value set in the superclass,
|
||||
then rename the subclass attribute.
|
||||
If you do want to be able to set a new value for the attribute of the superclass,
|
||||
then convert the superclass attribute to a property.
|
||||
Otherwise the value should be passed as a parameter to the superclass
|
||||
<code>__init__</code> method.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<sample src="OverwritingAttributeInSuperClass.py" />
|
||||
|
||||
|
||||
|
||||
</example>
|
||||
</qhelp>
|
||||
71
python/ql/src/Classes/OverwritingAttributeInSuperClass.ql
Normal file
71
python/ql/src/Classes/OverwritingAttributeInSuperClass.ql
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @name Overwriting attribute in super-class or sub-class
|
||||
* @description Assignment to self attribute overwrites attribute previously defined in subclass or superclass __init__ method.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* maintainability
|
||||
* modularity
|
||||
* @problem.severity warning
|
||||
* @sub-severity low
|
||||
* @precision medium
|
||||
* @id py/overwritten-inherited-attribute
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
|
||||
class InitCallStmt extends ExprStmt {
|
||||
|
||||
InitCallStmt() {
|
||||
exists(Call call, Attribute attr | call = this.getValue() and attr = call.getFunc() |
|
||||
attr.getName() = "__init__")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
predicate overwrites_which(Function subinit, AssignStmt write_attr, string which) {
|
||||
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")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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
|
||||
42
python/ql/src/Classes/PropertyInOldStyleClass.py
Normal file
42
python/ql/src/Classes/PropertyInOldStyleClass.py
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
class OldStyle:
|
||||
|
||||
def __init__(self, x):
|
||||
self._x = x
|
||||
|
||||
# Incorrect: 'OldStyle' is not a new-style class and '@property' is not supported
|
||||
@property
|
||||
def x(self):
|
||||
return self._x
|
||||
|
||||
|
||||
class InheritOldStyle(OldStyle):
|
||||
|
||||
def __init__(self, x):
|
||||
self._x = x
|
||||
|
||||
# Incorrect: 'InheritOldStyle' is not a new-style class and '@property' is not supported
|
||||
@property
|
||||
def x(self):
|
||||
return self._x
|
||||
|
||||
|
||||
class NewStyle(object):
|
||||
|
||||
def __init__(self, x):
|
||||
self._x = x
|
||||
|
||||
# Correct: 'NewStyle' is a new-style class and '@property' is supported
|
||||
@property
|
||||
def x(self):
|
||||
return self._x
|
||||
|
||||
class InheritNewStyle(NewStyle):
|
||||
|
||||
def __init__(self, x):
|
||||
self._x = x
|
||||
|
||||
# Correct: 'InheritNewStyle' inherits from a new-style class and '@property' is supported
|
||||
@property
|
||||
def x(self):
|
||||
return self._x
|
||||
43
python/ql/src/Classes/PropertyInOldStyleClass.qhelp
Normal file
43
python/ql/src/Classes/PropertyInOldStyleClass.qhelp
Normal file
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
|
||||
<overview>
|
||||
<p>Property descriptors are only supported for the new-style classes that were introduced in Python
|
||||
2.1. Property descriptors should only be used in new-style classes.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>If you want to define properties in a class, then ensure that the class is a new-style class. You can
|
||||
convert an old-style class to a new-style class by inheriting from <code>object</code>.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the following example all the classes attempt to set a property for <code>x</code>. However, only
|
||||
the third and fourth classes are new-style classes. Consequently, the <code>x</code>
|
||||
property is only available for the <code>NewStyle</code> and <code>InheritNewStyle</code> classes.</p>
|
||||
|
||||
<p>If you define the <code>OldStyle</code> class as inheriting from a new-style class, then the <code>x
|
||||
</code> property would be available for both the <code>OldStyle</code> and <code>InheritOldStyle</code> classes.</p>
|
||||
|
||||
<sample src="PropertyInOldStyleClass.py" />
|
||||
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Glossary: <a href="http://docs.python.org/glossary.html#term-new-style-class">New-style class</a>.</li>
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/2/reference/datamodel.html#newstyle">New-style and classic
|
||||
classes</a>,
|
||||
<a href="http://docs.python.org/2/reference/datamodel.html#implementing-descriptors">
|
||||
Descriptors</a>.</li>
|
||||
<li>Python Standard Library: <a href="http://docs.python.org/library/functions.html#property">Property</a>.</li>
|
||||
<li>The History of Python: <a href="http://python-history.blogspot.co.uk/2010/06/inside-story-on-new-style-classes.html">
|
||||
Inside story on new-style classes</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
17
python/ql/src/Classes/PropertyInOldStyleClass.ql
Normal file
17
python/ql/src/Classes/PropertyInOldStyleClass.ql
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @name Property in old-style class
|
||||
* @description Using property descriptors in old-style classes does not work from Python 2.1 onward.
|
||||
* @kind problem
|
||||
* @tags portability
|
||||
* correctness
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision very-high
|
||||
* @id py/property-in-old-style-class
|
||||
*/
|
||||
|
||||
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."
|
||||
33
python/ql/src/Classes/ShouldBeContextManager.py
Normal file
33
python/ql/src/Classes/ShouldBeContextManager.py
Normal file
@@ -0,0 +1,33 @@
|
||||
class remotelock(object): # Resources can be released using __del__
|
||||
|
||||
def __init__(self, repo):
|
||||
self.repo = repo
|
||||
|
||||
def release(self):
|
||||
self.repo.unlock()
|
||||
self.repo = None
|
||||
|
||||
def __del__(self):
|
||||
if self.repo:
|
||||
self.release()
|
||||
|
||||
|
||||
class remotelock2(object): # Resources can be released using a with statement
|
||||
|
||||
def __init__(self, repo):
|
||||
self.repo = repo
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def release(self):
|
||||
self.repo.unlock()
|
||||
self.repo = None
|
||||
|
||||
def __del__(self):
|
||||
if self.repo:
|
||||
self.release()
|
||||
|
||||
def __exit__(self, exct_type, exce_value, traceback):
|
||||
if self.repo:
|
||||
self.release()
|
||||
42
python/ql/src/Classes/ShouldBeContextManager.qhelp
Normal file
42
python/ql/src/Classes/ShouldBeContextManager.qhelp
Normal file
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>If a class has a <code>close()</code> or similar method to release resources, then it
|
||||
should be made a context manager. Using a context manager allows instances of the class to be used
|
||||
in the <code>with</code> statement, improving code size and readability. This is a simpler and more
|
||||
reliable method than implementing just a <code>__del__</code> method.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>The context manager requires an <code>__enter__</code> and an <code>__exit__</code> method:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>__enter__</code> method acquires the resource or does nothing if the resource
|
||||
is acquired in the <code>__init__</code> method</li>
|
||||
<li><code>__exit__</code> method releases the resource, this can just be a simple wrapper around the
|
||||
<code>close</code> method.</li>
|
||||
</ul>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>The following example shows how a class definition that implements <code>__del__</code> can be
|
||||
updated to use a context manager.</p>
|
||||
|
||||
<sample src="ShouldBeContextManager.py" />
|
||||
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Effbot: <a href="http://effbot.org/zone/python-with-statement.htm">Python with statement</a>.</li>
|
||||
<li>Python Standard Library: <a href="http://docs.python.org/library/stdtypes.html#context-manager-types">Context manager
|
||||
</a>.</li>
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/2.7/reference/datamodel.html#with-statement-context-managers">
|
||||
With Statement Context Managers</a>.</li>
|
||||
<li>Python PEP 343: <a href="http://www.python.org/dev/peps/pep-0343">The "with" Statement</a>.</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
19
python/ql/src/Classes/ShouldBeContextManager.ql
Normal file
19
python/ql/src/Classes/ShouldBeContextManager.ql
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @name Class should be a context manager
|
||||
* @description Making a class a context manager allows instances to be used in a 'with' statement.
|
||||
* This improves resource handling and code readability.
|
||||
* @kind problem
|
||||
* @tags maintainability
|
||||
* readability
|
||||
* convention
|
||||
* @problem.severity recommendation
|
||||
* @sub-severity high
|
||||
* @precision medium
|
||||
* @id py/should-be-context-manager
|
||||
*/
|
||||
|
||||
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."
|
||||
20
python/ql/src/Classes/SlotsInOldStyleClass.py
Normal file
20
python/ql/src/Classes/SlotsInOldStyleClass.py
Normal file
@@ -0,0 +1,20 @@
|
||||
class Point:
|
||||
|
||||
__slots__ = [ '_x', '_y' ] # Incorrect: 'Point' is an old-style class.
|
||||
# No slots are created.
|
||||
# Instances of Point have an attribute dictionary.
|
||||
|
||||
def __init__(self, x, y):
|
||||
self._x = x
|
||||
self._y = y
|
||||
|
||||
|
||||
class Point2(object):
|
||||
|
||||
__slots__ = [ '_x', '_y' ] # Correct: 'Point2' is an new-style class
|
||||
# Two slots '_x' and '_y' are created.
|
||||
# Instances of Point2 have no attribute dictionary.
|
||||
|
||||
def __init__(self, x, y):
|
||||
self._x = x
|
||||
self._y = y
|
||||
37
python/ql/src/Classes/SlotsInOldStyleClass.qhelp
Normal file
37
python/ql/src/Classes/SlotsInOldStyleClass.qhelp
Normal file
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>The ability to override the class dictionary using a <code>__slots__</code> declaration
|
||||
is supported only by new-style classes. When you add a <code>__slots__</code> declaration to an
|
||||
old-style class it just creates a class attribute called '__slots__'.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>If you want to override the dictionary for a class, then ensure that the class is a new-style class.
|
||||
You can convert an old-style class to a new-style class by inheriting from <code>object</code>.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the following example the <code>KeyedRef</code> class is an old-style class (no inheritance). The
|
||||
<code>__slots__</code> declaration in this class creates a class attribute called '__slots__', the class
|
||||
dictionary is unaffected. The <code>KeyedRef2</code> class is a new-style class so the
|
||||
<code>__slots__</code> declaration causes special compact attributes to be created for each name in
|
||||
the slots list and saves space by not creating attribute dictionaries.</p>
|
||||
|
||||
<sample src="SlotsInOldStyleClass.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Glossary: <a href="http://docs.python.org/glossary.html#term-new-style-class">New-style class</a>.</li>
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/2/reference/datamodel.html#newstyle">New-style and classic
|
||||
classes</a>,
|
||||
<a href="http://docs.python.org/reference/datamodel.html#__slots__">__slots__</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
18
python/ql/src/Classes/SlotsInOldStyleClass.ql
Normal file
18
python/ql/src/Classes/SlotsInOldStyleClass.ql
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @name '__slots__' in old-style class
|
||||
* @description Overriding the class dictionary by declaring '__slots__' is not supported by old-style
|
||||
* classes.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @tags portability
|
||||
* correctness
|
||||
* @sub-severity low
|
||||
* @precision very-high
|
||||
* @id py/slots-in-old-style-class
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
from ClassObject c
|
||||
where not c.isNewStyle() and c.declaresAttribute("__slots__") and not c.failedInference()
|
||||
select c, "Using __slots__ in an old style class just creates a class attribute called '__slots__'"
|
||||
17
python/ql/src/Classes/SubclassShadowing.py
Normal file
17
python/ql/src/Classes/SubclassShadowing.py
Normal file
@@ -0,0 +1,17 @@
|
||||
class Mammal(object):
|
||||
|
||||
def __init__(self, milk = 0):
|
||||
self.milk = milk
|
||||
|
||||
|
||||
class Cow(Mammal):
|
||||
|
||||
def __init__(self):
|
||||
Mammal.__init__(self)
|
||||
|
||||
def milk(self):
|
||||
return "Milk"
|
||||
|
||||
#Cow().milk() will raise an error as Cow().milk is the 'milk' attribute
|
||||
#set in Mammal.__init__, not the 'milk' method defined on Cow.
|
||||
|
||||
27
python/ql/src/Classes/SubclassShadowing.qhelp
Normal file
27
python/ql/src/Classes/SubclassShadowing.qhelp
Normal file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p> Subclass shadowing occurs when an instance attribute of a superclass has the
|
||||
the same name as a method of a subclass, or vice-versa.
|
||||
The semantics of Python attribute look-up mean that the instance attribute of
|
||||
the superclass hides the method in the subclass.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Rename the method in the subclass or rename the attribute in the superclass.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>The following code includes an example of subclass shadowing. When you call <code>Cow().milk()</code>
|
||||
an error is raised because <code>Cow().milk</code> is interpreted as the 'milk' attribute set in
|
||||
<code>Mammal.__init__</code>, not the 'milk' method defined within <code>Cow</code>. This can be fixed
|
||||
by changing the name of either the 'milk' attribute or the 'milk' method.</p>
|
||||
|
||||
<sample src="SubclassShadowing.py" />
|
||||
|
||||
</example>
|
||||
</qhelp>
|
||||
40
python/ql/src/Classes/SubclassShadowing.ql
Normal file
40
python/ql/src/Classes/SubclassShadowing.ql
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @name Superclass attribute shadows subclass method
|
||||
* @description Defining an attribute in a superclass method with a name that matches a subclass
|
||||
* method, hides the method in the subclass.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @tags maintainability
|
||||
* correctness
|
||||
* @sub-severity low
|
||||
* @precision high
|
||||
* @id py/attribute-shadows-method
|
||||
*/
|
||||
|
||||
/* 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
|
||||
exists(FunctionObject init, Attribute attr |
|
||||
supercls.declaredAttribute("__init__") = init and
|
||||
attr = assign.getATarget() and
|
||||
((Name)attr.getObject()).getId() = "self" and
|
||||
attr.getName() = f.getName() and
|
||||
assign.getScope() = ((FunctionExpr)init.getOrigin()).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. */
|
||||
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"
|
||||
|
||||
18
python/ql/src/Classes/SuperInOldStyleClass.py
Normal file
18
python/ql/src/Classes/SuperInOldStyleClass.py
Normal file
@@ -0,0 +1,18 @@
|
||||
class PythonModule(_ModuleIteratorHelper): # '_ModuleIteratorHelper' and 'PythonModule' are old-style classes
|
||||
|
||||
# class definitions ....
|
||||
|
||||
def walkModules(self, importPackages=False):
|
||||
if importPackages and self.isPackage():
|
||||
self.load()
|
||||
return super(PythonModule, self).walkModules(importPackages=importPackages) # super() will fail
|
||||
|
||||
|
||||
class PythonModule2(_ModuleIteratorHelper): # call to super replaced with direct call to class
|
||||
|
||||
# class definitions ....
|
||||
|
||||
def walkModules(self, importPackages=False):
|
||||
if importPackages and self.isPackage():
|
||||
self.load()
|
||||
return _ModuleIteratorHelper.__init__(PythonModule, self).walkModules(importPackages=importPackages)
|
||||
38
python/ql/src/Classes/SuperInOldStyleClass.qhelp
Normal file
38
python/ql/src/Classes/SuperInOldStyleClass.qhelp
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>The ability to access inherited methods that have been overridden in a class using <code>super()</code>
|
||||
is supported only by new-style classes. When you use the <code>super()</code> function in an old-style
|
||||
class it fails.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>If you want to access inherited methods using the <code>super()</code> built-in, then ensure that
|
||||
the class is a new-style class. You can convert an old-style class to a new-style class by inheriting
|
||||
from <code>object</code>. Alternatively, you can call the <code>__init__</code> method of the superclass
|
||||
directly from an old-style class using: <code>BaseClass.__init__(...)</code>.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the following example, <code>PythonModule</code> is an old-style class as it inherits from another
|
||||
old-style class. If the <code>_ModuleIteratorHelper</code> class cannot be converted into a new-style
|
||||
class, then the call to <code>super()</code> must be replaced. The <code>PythonModule2</code> class
|
||||
demonstrates the correct way to call a superclass from an old-style class.</p>
|
||||
|
||||
<sample src="SuperInOldStyleClass.py" />
|
||||
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Glossary: <a href="http://docs.python.org/glossary.html#term-new-style-class">New-style class</a>.</li>
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/2/reference/datamodel.html#newstyle">New-style and classic
|
||||
classes</a>.</li>
|
||||
<li>Python Standard Library: <a href="http://docs.python.org/library/functions.html#super">super</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
22
python/ql/src/Classes/SuperInOldStyleClass.ql
Normal file
22
python/ql/src/Classes/SuperInOldStyleClass.ql
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @name 'super' in old style class
|
||||
* @description Using super() to access inherited methods is not supported by old-style classes.
|
||||
* @kind problem
|
||||
* @tags portability
|
||||
* correctness
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision very-high
|
||||
* @id py/super-in-old-style
|
||||
*/
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
from Call c
|
||||
where uses_of_super_in_old_style_class(c)
|
||||
select c, "super() will not work in old-style classes"
|
||||
29
python/ql/src/Classes/SuperclassDelCalledMultipleTimes.py
Normal file
29
python/ql/src/Classes/SuperclassDelCalledMultipleTimes.py
Normal file
@@ -0,0 +1,29 @@
|
||||
#Calling a method multiple times by using explicit calls when a base inherits from other base
|
||||
class Vehicle(object):
|
||||
|
||||
def __del__(self):
|
||||
recycle(self.base_parts)
|
||||
|
||||
|
||||
class Car(Vehicle):
|
||||
|
||||
def __del__(self):
|
||||
recycle(self.car_parts)
|
||||
Vehicle.__del__(self)
|
||||
|
||||
|
||||
class SportsCar(Car, Vehicle):
|
||||
|
||||
# Vehicle.__del__ will get called twice
|
||||
def __del__(self):
|
||||
recycle(self.sports_car_parts)
|
||||
Car.__del__(self)
|
||||
Vehicle.__del__(self)
|
||||
|
||||
|
||||
#Fix SportsCar by only calling Car.__del__
|
||||
class FixedSportsCar(Car, Vehicle):
|
||||
|
||||
def __del__(self):
|
||||
recycle(self.sports_car_parts)
|
||||
Car.__del__(self)
|
||||
58
python/ql/src/Classes/SuperclassDelCalledMultipleTimes.qhelp
Normal file
58
python/ql/src/Classes/SuperclassDelCalledMultipleTimes.qhelp
Normal file
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object destruction.
|
||||
However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies.
|
||||
Therefore the developer has responsibility for ensuring that objects are properly cleaned up when
|
||||
there are multiple <code>__del__</code> methods that need to be called.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Calling a <code>__del__</code> method more than once during object destruction risks resources being released multiple
|
||||
times. The relevant <code>__del__</code> method may not be designed to be called more than once.
|
||||
</p>
|
||||
|
||||
<p>There are a number of ways that a <code>__del__</code> method may be be called more than once.</p>
|
||||
<ul>
|
||||
<li>There may be more than one explicit call to the method in the hierarchy of <code>__del__</code> methods.</li>
|
||||
<li>A class using multiple inheritance directly calls the <code>__del__</code> methods of its base types.
|
||||
One or more of those base types uses <code>super()</code> to pass down the inheritance chain.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Either be careful not to explicitly call a <code>__del__</code> method more than once, or
|
||||
use <code>super()</code> throughout the inheritance hierarchy.</p>
|
||||
|
||||
<p>Alternatively refactor one or more of the classes to use composition rather than inheritance.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the first example, explicit calls to <code>__del__</code> are used, but <code>SportsCar</code> erroneously calls
|
||||
both <code>Vehicle.__del__</code> and <code>Car.__del__</code>.
|
||||
This can be fixed by removing the call to <code>Vehicle.__del__</code>, as shown in <code>FixedSportsCar</code>.
|
||||
</p>
|
||||
|
||||
<sample src="SuperclassDelCalledMultipleTimes.py" />
|
||||
|
||||
<p>In the second example, there is a mixture of explicit calls to <code>__del__</code> and calls using <code>super()</code>.
|
||||
To fix this example, <code>super()</code> should be used throughout.
|
||||
</p>
|
||||
|
||||
<sample src="SuperclassInitCalledMultipleTimes2.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Tutorial: <a href="https://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
|
||||
<li>Python Standard Library: <a href="https://docs.python.org/2/library/functions.html#super">super</a>.</li>
|
||||
<li>Artima Developer: <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=236275">Things to Know About Python Super</a>.</li>
|
||||
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
27
python/ql/src/Classes/SuperclassDelCalledMultipleTimes.ql
Normal file
27
python/ql/src/Classes/SuperclassDelCalledMultipleTimes.ql
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @name Multiple calls to __del__ during object destruction
|
||||
* @description A duplicated call to a super-class __del__ method may lead to class instances not be cleaned up properly.
|
||||
* @kind problem
|
||||
* @tags efficiency
|
||||
* correctness
|
||||
* @problem.severity warning
|
||||
* @sub-severity high
|
||||
* @precision very-high
|
||||
* @id py/multiple-calls-to-delete
|
||||
*/
|
||||
|
||||
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()
|
||||
32
python/ql/src/Classes/SuperclassDelCalledMultipleTimes2.py
Normal file
32
python/ql/src/Classes/SuperclassDelCalledMultipleTimes2.py
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
#Calling a method multiple times by using explicit calls when a base uses super()
|
||||
class Vehicle(object):
|
||||
|
||||
def __del__(self):
|
||||
recycle(self.base_parts)
|
||||
super(Vehicle, self).__del__()
|
||||
|
||||
class Car(Vehicle):
|
||||
|
||||
def __del__(self):
|
||||
recycle(self.car_parts)
|
||||
super(Car, self).__del__()
|
||||
|
||||
|
||||
class SportsCar(Car, Vehicle):
|
||||
|
||||
# Vehicle.__del__ will get called twice
|
||||
def __del__(self):
|
||||
recycle(self.sports_car_parts)
|
||||
Car.__del__(self)
|
||||
Vehicle.__del__(self)
|
||||
|
||||
|
||||
#Fix SportsCar by using super()
|
||||
class FixedSportsCar(Car, Vehicle):
|
||||
|
||||
def __del__(self):
|
||||
recycle(self.sports_car_parts)
|
||||
super(SportsCar, self).__del__()
|
||||
|
||||
|
||||
36
python/ql/src/Classes/SuperclassInitCalledMultipleTimes.py
Normal file
36
python/ql/src/Classes/SuperclassInitCalledMultipleTimes.py
Normal file
@@ -0,0 +1,36 @@
|
||||
#Calling a method multiple times by using explicit calls when a base inherits from other base
|
||||
class Vehicle(object):
|
||||
|
||||
def __init__(self):
|
||||
self.mobile = True
|
||||
|
||||
class Car(Vehicle):
|
||||
|
||||
def __init__(self):
|
||||
Vehicle.__init__(self)
|
||||
self.car_init()
|
||||
|
||||
def car_init(self):
|
||||
pass
|
||||
|
||||
class SportsCar(Car, Vehicle):
|
||||
|
||||
# Vehicle.__init__ will get called twice
|
||||
def __init__(self):
|
||||
Vehicle.__init__(self)
|
||||
Car.__init__(self)
|
||||
self.sports_car_init()
|
||||
|
||||
def sports_car_init(self):
|
||||
pass
|
||||
|
||||
#Fix SportsCar by only calling Car.__init__
|
||||
class FixedSportsCar(Car, Vehicle):
|
||||
|
||||
def __init__(self):
|
||||
Car.__init__(self)
|
||||
self.sports_car_init()
|
||||
|
||||
def sports_car_init(self):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object initialization.
|
||||
However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies.
|
||||
Therefore the developer has responsibility for ensuring that objects are properly initialized when
|
||||
there are multiple <code>__init__</code> methods that need to be called.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Calling an <code>__init__</code> method more than once during object initialization risks the object being incorrectly initialized.
|
||||
It is unlikely that the relevant <code>__init__</code> method is designed to be called more than once.
|
||||
</p>
|
||||
|
||||
<p>There are a number of ways that an <code>__init__</code> method may be be called more than once.</p>
|
||||
<ul>
|
||||
<li>There may be more than one explicit call to the method in the hierarchy of <code>__init__</code> methods.</li>
|
||||
<li>A class using multiple inheritance directly calls the <code>__init__</code> methods of its base types.
|
||||
One or more of those base types uses <code>super()</code> to pass down the inheritance chain.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Either be careful not to explicitly call an <code>__init__</code> method more than once, or
|
||||
use <code>super()</code> throughout the inheritance hierarchy.</p>
|
||||
|
||||
<p>Alternatively refactor one or more of the classes to use composition rather than inheritance.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the first example, explicit calls to <code>__init__</code> are used, but <code>SportsCar</code> erroneously calls
|
||||
both <code>Vehicle.__init__</code> and <code>Car.__init__</code>.
|
||||
This can be fixed by removing the call to <code>Vehicle.__init__</code>, as shown in <code>FixedSportsCar</code>.
|
||||
</p>
|
||||
|
||||
<sample src="SuperclassInitCalledMultipleTimes.py" />
|
||||
|
||||
<p>In the second example, there is a mixture of explicit calls to <code>__init__</code> and calls using <code>super()</code>.
|
||||
To fix this example, <code>super()</code> should be used throughout.
|
||||
</p>
|
||||
|
||||
<sample src="SuperclassInitCalledMultipleTimes2.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Tutorial: <a href="https://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
|
||||
<li>Python Standard Library: <a href="https://docs.python.org/2/library/functions.html#super">super</a>.</li>
|
||||
<li>Artima Developer: <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=236275">Things to Know About Python Super</a>.</li>
|
||||
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
26
python/ql/src/Classes/SuperclassInitCalledMultipleTimes.ql
Normal file
26
python/ql/src/Classes/SuperclassInitCalledMultipleTimes.ql
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @name Multiple calls to __init__ during object initialization
|
||||
* @description A duplicated call to a super-class __init__ method may lead to objects of this class not being properly initialized.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* @problem.severity warning
|
||||
* @sub-severity high
|
||||
* @precision very-high
|
||||
* @id py/multiple-calls-to-init
|
||||
*/
|
||||
|
||||
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()
|
||||
38
python/ql/src/Classes/SuperclassInitCalledMultipleTimes2.py
Normal file
38
python/ql/src/Classes/SuperclassInitCalledMultipleTimes2.py
Normal file
@@ -0,0 +1,38 @@
|
||||
|
||||
#Calling a method multiple times by using explicit calls when a base uses super()
|
||||
class Vehicle(object):
|
||||
|
||||
def __init__(self):
|
||||
super(Vehicle, self).__init__()
|
||||
self.mobile = True
|
||||
|
||||
class Car(Vehicle):
|
||||
|
||||
def __init__(self):
|
||||
super(Car, self).__init__()
|
||||
self.car_init()
|
||||
|
||||
def car_init(self):
|
||||
pass
|
||||
|
||||
class SportsCar(Car, Vehicle):
|
||||
|
||||
# Vehicle.__init__ will get called twice
|
||||
def __init__(self):
|
||||
Vehicle.__init__(self)
|
||||
Car.__init__(self)
|
||||
self.sports_car_init()
|
||||
|
||||
def sports_car_init(self):
|
||||
pass
|
||||
|
||||
#Fix SportsCar by using super()
|
||||
class FixedSportsCar(Car, Vehicle):
|
||||
|
||||
def __init__(self):
|
||||
super(SportsCar, self).__init__()
|
||||
self.sports_car_init()
|
||||
|
||||
def sports_car_init(self):
|
||||
pass
|
||||
|
||||
19
python/ql/src/Classes/UndefinedClassAttribute.py
Normal file
19
python/ql/src/Classes/UndefinedClassAttribute.py
Normal file
@@ -0,0 +1,19 @@
|
||||
class Spam:
|
||||
|
||||
def __init__(self):
|
||||
self.spam = 'spam, spam, spam'
|
||||
|
||||
def __str__(self):
|
||||
return '%s and %s' % (self.spam, self.eggs) # Uninitialized attribute 'eggs'
|
||||
|
||||
#Fixed version
|
||||
|
||||
class Spam:
|
||||
|
||||
def __init__(self):
|
||||
self.spam = 'spam, spam, spam'
|
||||
self.eggs = None
|
||||
|
||||
def __str__(self):
|
||||
return '%s and %s' % (self.spam, self.eggs) # OK
|
||||
|
||||
33
python/ql/src/Classes/UndefinedClassAttribute.qhelp
Normal file
33
python/ql/src/Classes/UndefinedClassAttribute.qhelp
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
|
||||
<overview>
|
||||
<p>A non-existent attribute of <code>self</code> is accessed in a method.
|
||||
An attribute is treated as non-existent if it is not a class attribute
|
||||
and it is not set in any method of the class.
|
||||
This may result in an <code>AttributeError</code> at run time.
|
||||
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Ensure that all attributes are initialized in the <code>__init__</code> method.</p>
|
||||
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<sample src="UndefinedClassAttribute.py" />
|
||||
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Standard Library: <a href="http://docs.python.org/library/exceptions.html#exceptions.AttributeError">exception AttributeError</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
34
python/ql/src/Classes/UndefinedClassAttribute.ql
Normal file
34
python/ql/src/Classes/UndefinedClassAttribute.ql
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @name Undefined class attribute
|
||||
* @description Accessing an attribute of 'self' that is not initialized anywhere in the class in the __init__ method may cause an AttributeError at runtime
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision low
|
||||
* @id py/undefined-attribute
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.SelfAttribute
|
||||
|
||||
predicate undefined_class_attribute(SelfAttributeRead a, CheckClass c, int line, string name) {
|
||||
name = a.getName() and
|
||||
not c.sometimesDefines(name) and
|
||||
c.interestingUndefined(a) and
|
||||
line = a.getLocation().getStartLine() and
|
||||
not attribute_assigned_in_method(c.getAMethodCalledFromInit(), name)
|
||||
}
|
||||
|
||||
predicate report_undefined_class_attribute(Attribute a, ClassObject c, string name) {
|
||||
exists(int line |
|
||||
undefined_class_attribute(a, c, line, name) and
|
||||
line = min(int x | undefined_class_attribute(_, c, x, name))
|
||||
)
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
16
python/ql/src/Classes/UselessClass.py
Normal file
16
python/ql/src/Classes/UselessClass.py
Normal file
@@ -0,0 +1,16 @@
|
||||
class GCDFinder(object):
|
||||
def __init__(self, a, b):
|
||||
self.a = a
|
||||
self.b = b
|
||||
|
||||
def calculate(self):
|
||||
a = self.a
|
||||
b = self.b
|
||||
while a != 0 and b != 0:
|
||||
if a > b:
|
||||
a = a % b
|
||||
else:
|
||||
b = b % a
|
||||
if a == 0:
|
||||
return b
|
||||
return a
|
||||
35
python/ql/src/Classes/UselessClass.qhelp
Normal file
35
python/ql/src/Classes/UselessClass.qhelp
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
|
||||
<overview>
|
||||
<p> If a class has only one public method (other than its <code>__init__</code>)
|
||||
it should be replaced with a function.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p> Convert the single public method into a function.
|
||||
If there is an <code>__init__</code> and it sets attributes on the <code>self</code>
|
||||
then rename the <code>__init__</code> method and remove the <code>self</code> parameter
|
||||
Make the public method an inner function and return that.</p>
|
||||
|
||||
<p>Delete the class.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In this example the class only has a single method. This method does not need to be in its own
|
||||
class. It should be a method on its own that takes <code>a</code> and <code>b</code> as parameters.
|
||||
</p>
|
||||
<sample src="UselessClass.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python: <a href="http://docs.python.org/tutorial/classes.html">Classes</a>.</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
83
python/ql/src/Classes/UselessClass.ql
Normal file
83
python/ql/src/Classes/UselessClass.ql
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @name Useless class
|
||||
* @description Class only defines one public method (apart from __init__ or __new__) and should be replaced by a function
|
||||
* @kind problem
|
||||
* @tags maintainability
|
||||
* useless-code
|
||||
* @problem.severity recommendation
|
||||
* @sub-severity low
|
||||
* @precision medium
|
||||
* @id py/useless-class
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
predicate fewer_than_two_public_methods(Class cls, int methods) {
|
||||
(methods = 0 or methods = 1) and
|
||||
methods = count(Function f | f = cls.getAMethod() and not f = cls.getInitMethod())
|
||||
}
|
||||
|
||||
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() |
|
||||
other.getABaseType() = cls or
|
||||
cls.getABaseType() = other
|
||||
)
|
||||
and
|
||||
not exists(Expr base | base = c.getABase() |
|
||||
not base instanceof Name or ((Name)base).getId() != "object"
|
||||
)
|
||||
}
|
||||
|
||||
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(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"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
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."
|
||||
)
|
||||
select c, msg
|
||||
@@ -0,0 +1,6 @@
|
||||
class Point(object):
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
p = Point(x=1, yy=2) # TypeError: 'yy' is not a valid keyword argument
|
||||
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Using a named argument whose name does not correspond to a parameter of the <code>__init__</code> method of the class being instantiated, will result in a
|
||||
<code>TypeError</code> at runtime.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Check for typos in the name of the arguments and fix those.
|
||||
If the name is clearly different, then this suggests a logical error.
|
||||
The change required to correct the error will depend on whether the wrong argument has been
|
||||
specified or whether the wrong class has been specified.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<sample src="WrongNameForArgumentInClassInstantiation.py"/>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
|
||||
<li>Python Glossary: <a href="https://docs.python.org/2/glossary.html#term-argument">Arguments</a>.</li>
|
||||
<li>Python Glossary: <a href="https://docs.python.org/glossary.html#term-parameter">Parameters</a>.</li>
|
||||
<li>Python Programming FAQ: <a href="https://docs.python.org/2/faq/programming.html#faq-argument-vs-parameter">
|
||||
What is the difference between arguments and parameters?</a>.</li>
|
||||
<li>The Python Language Reference: <a href="https://docs.python.org/3/reference/datamodel.html#object.__init__">Data model: object.__init__</a></li>
|
||||
<li>The Python Tutorial: <a href="https://docs.python.org/3/tutorial/classes.html">Classes</a></li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @name Wrong name for an argument in a class instantiation
|
||||
* @description Using a named argument whose name does not correspond to a
|
||||
* parameter of the __init__ method of the class being
|
||||
* instantiated, will result in a TypeError at runtime.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* external/cwe/cwe-628
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision very-high
|
||||
* @id py/call/wrong-named-class-argument
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
import Expressions.CallArgs
|
||||
|
||||
|
||||
from Call call, ClassObject cls, string name, FunctionObject init
|
||||
where
|
||||
illegally_named_parameter(call, cls, name)
|
||||
and init = get_function_or_initializer(cls)
|
||||
select
|
||||
call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init, init.getQualifiedName()
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
class Point(object):
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
p = Point(1) # TypeError: too few arguments
|
||||
p = Point(1,2,3) # TypeError: too many arguments
|
||||
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
A call to the <code>__init__</code> method of a class must supply an argument
|
||||
for each parameter that does not have a default value defined, so:
|
||||
</p>
|
||||
<ul>
|
||||
<li>The minimum number of arguments is the number of parameters without default values.</li>
|
||||
<li>The maximum number of arguments is the total number of parameters, unless
|
||||
the class <code>__init__</code> method takes a varargs (starred) parameter in
|
||||
which case there is no limit.</li>
|
||||
</ul>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>If there are too few arguments then check to see which arguments have been omitted and supply values for those.</p>
|
||||
|
||||
<p>If there are too many arguments then check to see if any have been added by mistake and remove those.</p>
|
||||
|
||||
<p>
|
||||
Also check where a comma has been inserted instead of an operator or a dot.
|
||||
For example, the code is <code>obj,attr</code> when it should be <code>obj.attr</code>.
|
||||
</p>
|
||||
|
||||
<p> If it is not clear which are the missing or surplus arguments, then this suggests a logical error.
|
||||
The fix will then depend on the nature of the error.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<sample src="WrongNumberArgumentsInClassInstantiation.py"/>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
|
||||
<li>Python Glossary: <a href="https://docs.python.org/2/glossary.html#term-argument">Arguments</a>.</li>
|
||||
<li>Python Glossary: <a href="https://docs.python.org/glossary.html#term-parameter">Parameters</a>.</li>
|
||||
<li>Python Programming FAQ: <a href="https://docs.python.org/2/faq/programming.html#faq-argument-vs-parameter">
|
||||
What is the difference between arguments and parameters?</a>.</li>
|
||||
<li>The Python Language Reference: <a href="https://docs.python.org/3/reference/datamodel.html#object.__init__">Data model: object.__init__</a></li>
|
||||
<li>The Python Tutorial: <a href="https://docs.python.org/3/tutorial/classes.html">Classes</a></li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @name Wrong number of arguments in a class instantiation
|
||||
* @description Using too many or too few arguments in a call to the __init__
|
||||
* method of a class will result in a TypeError at runtime.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* external/cwe/cwe-685
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision very-high
|
||||
* @id py/call/wrong-number-class-arguments
|
||||
*/
|
||||
|
||||
import python
|
||||
import Expressions.CallArgs
|
||||
|
||||
from Call call, ClassObject cls, string too, string should, int limit, FunctionObject init
|
||||
where
|
||||
(
|
||||
too_many_args(call, cls, limit) and too = "too many arguments" and should = "no more than "
|
||||
or
|
||||
too_few_args(call, cls, limit) and too = "too few arguments" and should = "no fewer than "
|
||||
) and init = get_function_or_initializer(cls)
|
||||
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init, init.getQualifiedName()
|
||||
27
python/ql/src/Exceptions/CatchingBaseException.py
Normal file
27
python/ql/src/Exceptions/CatchingBaseException.py
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
def call_main_program_implicit_handle_base_exception():
|
||||
try:
|
||||
#application.main calls sys.exit() when done.
|
||||
application.main()
|
||||
except Exception as ex:
|
||||
log(ex)
|
||||
except:
|
||||
pass
|
||||
|
||||
def call_main_program_explicit_handle_base_exception():
|
||||
try:
|
||||
#application.main calls sys.exit() when done.
|
||||
application.main()
|
||||
except Exception as ex:
|
||||
log(ex)
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
def call_main_program_fixed():
|
||||
try:
|
||||
#application.main calls sys.exit() when done.
|
||||
application.main()
|
||||
except Exception as ex:
|
||||
log(ex)
|
||||
except SystemExit:
|
||||
pass
|
||||
55
python/ql/src/Exceptions/CatchingBaseException.qhelp
Normal file
55
python/ql/src/Exceptions/CatchingBaseException.qhelp
Normal file
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
All exception classes in Python derive from <code>BaseException</code>. <code>BaseException</code> has three important subclasses,
|
||||
<code>Exception</code> from which all errors and normal exceptions derive, <code>KeyboardInterrupt</code> which is raised when the
|
||||
user interrupts the program from the keyboard and <code>SystemExit</code> which is raised by the <code>sys.exit()</code> function to
|
||||
terminate the program.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Since <code>KeyboardInterrupt</code> and <code>SystemExit</code> are special they should not be grouped together with other
|
||||
<code>Exception</code> classes.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Catching <code>BaseException</code>, rather than its subclasses may prevent proper handling of
|
||||
<code>KeyboardInterrupt</code> or <code>SystemExit</code>. It is easy to catch <code>BaseException</code>
|
||||
accidentally as it is caught implicitly by an empty <code>except:</code> statement.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Handle <code>Exception</code>, <code>KeyboardInterrupt</code> and <code>SystemExit</code> separately. Do not use the plain <code>except:</code> form.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
In these examples, a function <code>application.main()</code> is called that might raise <code>SystemExit</code>.
|
||||
In the first two functions, <code>BaseException</code> is caught, but this will discard <code>KeyboardInterrupt</code>.
|
||||
In the third function, <code>call_main_program_fixed</code> only <code>SystemExit</code> is caught,
|
||||
leaving <code>KeyboardInterrupt</code> to propagate.
|
||||
</p>
|
||||
|
||||
|
||||
<p>In these examples <code>KeyboardInterrupt</code> is accidentally ignored.</p>
|
||||
<sample src="CatchingBaseException.py" />
|
||||
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/2.7/reference/compound_stmts.html#try">The try statement</a>,
|
||||
<a href="http://docs.python.org/2.7/reference/executionmodel.html#exceptions">Exceptions</a>.</li>
|
||||
<li>M. Lutz, Learning Python, Section 35.3: Exception Design Tips and Gotchas, O'Reilly Media, 2013.</li>
|
||||
<li>Python Tutorial: <a href="https://docs.python.org/2/tutorial/errors.html">Errors and Exceptions</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
30
python/ql/src/Exceptions/CatchingBaseException.ql
Normal file
30
python/ql/src/Exceptions/CatchingBaseException.ql
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @name Except block handles 'BaseException'
|
||||
* @description Handling 'BaseException' means that system exits and keyboard interrupts may be mis-handled.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* readability
|
||||
* convention
|
||||
* external/cwe/cwe-396
|
||||
* @problem.severity recommendation
|
||||
* @sub-severity high
|
||||
* @precision very-high
|
||||
* @id py/catch-base-exception
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
predicate doesnt_reraise(ExceptStmt ex) {
|
||||
ex.getAFlowNode().getBasicBlock().reachesExit()
|
||||
}
|
||||
|
||||
predicate catches_base_exception(ExceptStmt ex) {
|
||||
ex.getType().refersTo(theBaseExceptionType())
|
||||
or
|
||||
not exists(ex.getType())
|
||||
}
|
||||
|
||||
from ExceptStmt ex
|
||||
where catches_base_exception(ex) and
|
||||
doesnt_reraise(ex)
|
||||
select ex, "Except block directly handles BaseException."
|
||||
6
python/ql/src/Exceptions/EmptyExcept.py
Normal file
6
python/ql/src/Exceptions/EmptyExcept.py
Normal file
@@ -0,0 +1,6 @@
|
||||
# ...
|
||||
try:
|
||||
security_manager.drop_privileges()
|
||||
except SecurityError:
|
||||
pass
|
||||
# ...
|
||||
27
python/ql/src/Exceptions/EmptyExcept.qhelp
Normal file
27
python/ql/src/Exceptions/EmptyExcept.qhelp
Normal file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Ignoring exceptions that should be dealt with in some way is almost always a bad idea.
|
||||
The loss of information can lead to hard to debug errors and incomplete log files.
|
||||
It is even possible that ignoring an exception can cause a security vulnerability.
|
||||
An empty <code>except</code> block may be an indication that the programmer intended to
|
||||
handle the exception but never wrote the code to do so.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Ensure all exceptions are handled correctly.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In this example the program keeps running with the same privileges if it fails to drop to lower
|
||||
privileges.</p>
|
||||
<sample src="EmptyExcept.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
106
python/ql/src/Exceptions/EmptyExcept.ql
Executable file
106
python/ql/src/Exceptions/EmptyExcept.ql
Executable file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* @name Empty except
|
||||
* @description Except doesn't do anything and has no comment
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* maintainability
|
||||
* external/cwe/cwe-390
|
||||
* @problem.severity recommendation
|
||||
* @sub-severity high
|
||||
* @precision high
|
||||
* @id py/empty-except
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
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_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()
|
||||
)
|
||||
}
|
||||
|
||||
predicate non_local_control_flow(ExceptStmt ex) {
|
||||
ex.getType().refersTo(theStopIterationType())
|
||||
}
|
||||
|
||||
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 |
|
||||
/* 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 */
|
||||
pred.getNode().getParentNode*() = try.getAStmt() and
|
||||
not succ.getNode().getParentNode*() = try.getAStmt()
|
||||
)
|
||||
}
|
||||
|
||||
predicate attribute_access(Stmt s) {
|
||||
s.(ExprStmt).getValue() instanceof Attribute
|
||||
or
|
||||
exists(string name |
|
||||
s.(ExprStmt).getValue().(Call).getFunc().(Name).getId() = name |
|
||||
name = "getattr" or name = "setattr" or name = "delattr"
|
||||
)
|
||||
or
|
||||
s.(Delete).getATarget() instanceof Attribute
|
||||
}
|
||||
|
||||
predicate subscript(Stmt s) {
|
||||
s.(ExprStmt).getValue() instanceof Subscript
|
||||
or
|
||||
s.(Delete).getATarget() instanceof Subscript
|
||||
}
|
||||
|
||||
predicate encode_decode(Expr ex, ClassObject type) {
|
||||
exists(string name |
|
||||
ex.(Call).getFunc().(Attribute).getName() = name |
|
||||
name = "encode" and type = builtin_object("UnicodeEncodeError")
|
||||
or
|
||||
name = "decode" and type = builtin_object("UnicodeDecodeError")
|
||||
)
|
||||
}
|
||||
|
||||
predicate small_handler(ExceptStmt ex, Stmt s, ClassObject type) {
|
||||
not exists(ex.getTry().getStmt(1)) and
|
||||
s = ex.getTry().getStmt(0) and
|
||||
ex.getType().refersTo(type)
|
||||
}
|
||||
|
||||
/** Holds if this exception handler is sufficiently small in scope to not need a comment
|
||||
* as to what it is doing.
|
||||
*/
|
||||
predicate focussed_handler(ExceptStmt ex) {
|
||||
exists(Stmt s, ClassObject type |
|
||||
small_handler(ex, s, type) |
|
||||
subscript(s) and type.getAnImproperSuperType() = theLookupErrorType()
|
||||
or
|
||||
attribute_access(s) and type = theAttributeErrorType()
|
||||
or
|
||||
s.(ExprStmt).getValue() instanceof Name and type = theNameErrorType()
|
||||
or
|
||||
encode_decode(s.(ExprStmt).getValue(), type)
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
select ex, "'except' clause does nothing but pass and there is no explanatory comment."
|
||||
7
python/ql/src/Exceptions/IllegalExceptionHandlerType.py
Normal file
7
python/ql/src/Exceptions/IllegalExceptionHandlerType.py
Normal file
@@ -0,0 +1,7 @@
|
||||
def handle_int():
|
||||
try:
|
||||
raise_int()
|
||||
#This will not cause an exception, but it will be ignored
|
||||
except int:
|
||||
print("This will never be printed")
|
||||
|
||||
40
python/ql/src/Exceptions/IllegalExceptionHandlerType.qhelp
Normal file
40
python/ql/src/Exceptions/IllegalExceptionHandlerType.qhelp
Normal file
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>If the class specified in an <code>except</code> handler (within a <code>try</code> statement) is
|
||||
not a legal exception class, then it will never match a raised exception and never be executed
|
||||
</p>
|
||||
|
||||
<p>Legal exception classes are:</p>
|
||||
<ul>
|
||||
<li>Any old-style classes (Python 2 only)</li>
|
||||
<li>Any subclass of the builtin class <code>BaseException</code></li>
|
||||
</ul>
|
||||
<p>
|
||||
However, it recommended that you only use subclasses of the builtin class
|
||||
<code>Exception</code> (which is itself a subclass of <code>BaseException</code>).
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Ensure that the specified class is the one intended. If it is not then replace it with
|
||||
the correct one. Otherwise the entire <code>except</code> block can be deleted.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<sample src="IllegalExceptionHandlerType.py" />
|
||||
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Language Reference: <a href="https://docs.python.org/reference/executionmodel.html#exceptions">Exceptions</a>.</li>
|
||||
<li>Python Tutorial: <a href="https://docs.python.org/tutorial/errors.html#handling-exceptions">Handling Exceptions</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
30
python/ql/src/Exceptions/IllegalExceptionHandlerType.ql
Normal file
30
python/ql/src/Exceptions/IllegalExceptionHandlerType.ql
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @name Non-exception in 'except' clause
|
||||
* @description An exception handler specifying a non-exception type will never handle any exception.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* types
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision very-high
|
||||
* @id py/useless-except
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
from ExceptFlowNode ex, Object t, ClassObject c, ControlFlowNode origin, string what
|
||||
where ex.handledException(t, c, origin) and
|
||||
(
|
||||
exists(ClassObject x | x = t |
|
||||
not x.isLegalExceptionType() and
|
||||
not x.failedInference() and
|
||||
what = "class '" + x.getName() + "'"
|
||||
)
|
||||
or
|
||||
not t instanceof ClassObject and
|
||||
what = "instance of '" + c.getName() + "'"
|
||||
)
|
||||
|
||||
select ex.getNode(), "Non-exception $@ in exception handler which will never match raised exception.", origin, what
|
||||
|
||||
5
python/ql/src/Exceptions/IllegalRaise.py
Normal file
5
python/ql/src/Exceptions/IllegalRaise.py
Normal file
@@ -0,0 +1,5 @@
|
||||
#Cannot raise an int, even if we want to
|
||||
def raise_int():
|
||||
#Will raise a TypeError
|
||||
raise 4
|
||||
|
||||
38
python/ql/src/Exceptions/IllegalRaise.qhelp
Normal file
38
python/ql/src/Exceptions/IllegalRaise.qhelp
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>If the object raised is not a legal Exception class or an instance of one, then
|
||||
a <code>TypeError</code> will be raised instead.</p>
|
||||
|
||||
<p>Legal exception classes are:</p>
|
||||
<ul>
|
||||
<li>Any old-style classes (Python 2 only)</li>
|
||||
<li>Any subclass of the builtin class <code>BaseException</code></li>
|
||||
</ul>
|
||||
<p>
|
||||
However, it recommended that you only use subclasses of the builtin class
|
||||
<code>Exception</code> (which is itself a subclass of <code>BaseException</code>).
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Change the expression in the <code>raise</code> statement to be a legal exception.</p>
|
||||
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<sample src="IllegalRaise.py" />
|
||||
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Language Reference: <a href="https://docs.python.org/reference/executionmodel.html#exceptions">Exceptions</a>.</li>
|
||||
<li>Python Tutorial: <a href="https://docs.python.org/tutorial/errors.html#handling-exceptions">Handling Exceptions</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
21
python/ql/src/Exceptions/IllegalRaise.ql
Normal file
21
python/ql/src/Exceptions/IllegalRaise.ql
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @name Illegal raise
|
||||
* @description Raising a non-exception object or type will result in a TypeError being raised instead.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* types
|
||||
* @problem.severity error
|
||||
* @sub-severity high
|
||||
* @precision very-high
|
||||
* @id py/illegal-raise
|
||||
*/
|
||||
|
||||
import python
|
||||
import Raising
|
||||
import Exceptions.NotImplemented
|
||||
|
||||
from Raise r, ClassObject 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."
|
||||
|
||||
10
python/ql/src/Exceptions/IncorrectExceptOrder.py
Normal file
10
python/ql/src/Exceptions/IncorrectExceptOrder.py
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
|
||||
def incorrect_except_order(val):
|
||||
try:
|
||||
val.attr
|
||||
except Exception:
|
||||
print ("Exception")
|
||||
except AttributeError:
|
||||
print ("AttributeError")
|
||||
|
||||
45
python/ql/src/Exceptions/IncorrectExceptOrder.qhelp
Normal file
45
python/ql/src/Exceptions/IncorrectExceptOrder.qhelp
Normal file
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>When handling an exception, Python searches the except blocks in source code order
|
||||
until it finds a matching <code>except</code> block for the exception.
|
||||
An except block, <code>except E:</code>, specifies a class <code>E</code> and will match any
|
||||
exception that is an instance of <code>E</code>.
|
||||
</p>
|
||||
<p>
|
||||
If a more general except block precedes a more specific except block,
|
||||
then the more general block is always executed and the more specific block is never executed.
|
||||
An except block, <code>except A:</code>, is more general than another except block, <code>except B:</code>,
|
||||
if <code>A</code> is a super class of <code>B</code>.
|
||||
</p>
|
||||
<p>
|
||||
For example:
|
||||
<code>except Exception:</code> is more general than <code>except Error:</code> as <code>Exception</code>
|
||||
is a super class of <code>Error</code>.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Reorganize the <code>except</code> blocks so that the more specific <code>except</code>
|
||||
is defined first. Alternatively, if the more specific <code>except</code> block is
|
||||
no longer required then it should be deleted.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In this example the <code>except Exception:</code> will handle <code>AttributeError</code> preventing the
|
||||
subsequent handler from ever executing.</p>
|
||||
<sample src="IncorrectExceptOrder.py" />
|
||||
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/2.7/reference/compound_stmts.html#try">The try statement</a>,
|
||||
<a href="http://docs.python.org/2.7/reference/executionmodel.html#exceptions">Exceptions</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
34
python/ql/src/Exceptions/IncorrectExceptOrder.ql
Normal file
34
python/ql/src/Exceptions/IncorrectExceptOrder.ql
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @name Unreachable 'except' block
|
||||
* @description Handling general exceptions before specific exceptions means that the specific
|
||||
* handlers are never executed.
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* maintainability
|
||||
* external/cwe/cwe-561
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision very-high
|
||||
* @id py/unreachable-except
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
predicate incorrect_except_order(ExceptStmt ex1, ClassObject cls1, ExceptStmt ex2, ClassObject cls2) {
|
||||
exists(int i, int j, Try t |
|
||||
ex1 = t.getHandler(i) and
|
||||
ex2 = t.getHandler(j) and i < j and
|
||||
cls1 = except_class(ex1) and
|
||||
cls2 = except_class(ex2) and
|
||||
cls1 = cls2.getASuperType()
|
||||
)
|
||||
}
|
||||
|
||||
ClassObject except_class(ExceptStmt ex) {
|
||||
ex.getType().refersTo(result)
|
||||
}
|
||||
|
||||
from ExceptStmt ex1, ClassObject cls1, ExceptStmt ex2, ClassObject cls2
|
||||
where incorrect_except_order(ex1, cls1, ex2, cls2)
|
||||
select ex2, "Except block for $@ is unreachable; the more general $@ for $@ will always be executed in preference.",
|
||||
cls2, cls2.getName(), ex1, "except block", cls1, cls1.getName()
|
||||
9
python/ql/src/Exceptions/NotImplemented.py
Normal file
9
python/ql/src/Exceptions/NotImplemented.py
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
class Abstract(object):
|
||||
|
||||
def wrong(self):
|
||||
# Will raise a TypeError
|
||||
raise NotImplemented()
|
||||
|
||||
def right(self):
|
||||
raise NotImplementedError()
|
||||
11
python/ql/src/Exceptions/NotImplemented.qll
Normal file
11
python/ql/src/Exceptions/NotImplemented.qll
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
import python
|
||||
|
||||
/** Holds if `notimpl` refers to `NotImplemented` or `NotImplemented()` in the `raise` statement */
|
||||
predicate use_of_not_implemented_in_raise(Raise raise, Expr notimpl) {
|
||||
notimpl.refersTo(theNotImplementedObject()) and
|
||||
(
|
||||
notimpl = raise.getException() or
|
||||
notimpl = raise.getException().(Call).getFunc()
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
|
||||
<p><code>NotImplemented</code> is not an Exception, but is often mistakenly used in place of <code>NotImplementedError</code>.
|
||||
Executing <code>raise NotImplemented</code> or <code>raise NotImplemented()</code> will raise a <code>TypeError</code>.
|
||||
When <code>raise NotImplemented</code> is used to mark code that is genuinely never called, this mistake is benign.
|
||||
|
||||
However, should it be called, then a <code>TypeError</code> will be raised rather than the expected <code>NotImplemented</code>,
|
||||
which might make debugging the issue difficult.
|
||||
</p>
|
||||
|
||||
<p>The correct use of <code>NotImplemented</code> is to implement binary operators.
|
||||
Code that is not intended to be called should raise <code>NotImplementedError</code>.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Replace uses of <code>NotImplemented</code> with <code>NotImplementedError</code>.</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
In the example below, the method <code>wrong</code> will incorrectly raise a <code>TypeError</code> when called.
|
||||
The method <code>right</code> will raise a <code>NotImplementedError</code>.
|
||||
</p>
|
||||
|
||||
<sample src="NotImplemented.py" />
|
||||
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
|
||||
<li>Python Language Reference: <a href="https://docs.python.org/library/exceptions.html#NotImplementedError">The NotImplementedError exception</a>.</li>
|
||||
<li>Python Language Reference: <a href="https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types">Emulating numeric types</a>.</li>
|
||||
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
19
python/ql/src/Exceptions/NotImplementedIsNotAnException.ql
Normal file
19
python/ql/src/Exceptions/NotImplementedIsNotAnException.ql
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @name NotImplemented is not an Exception
|
||||
* @description Using 'NotImplemented' as an exception will result in a type error.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @sub-severity high
|
||||
* @precision very-high
|
||||
* @id py/raise-not-implemented
|
||||
* @tags reliability
|
||||
* maintainability
|
||||
*/
|
||||
|
||||
import python
|
||||
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?"
|
||||
14
python/ql/src/Exceptions/Raising.qll
Normal file
14
python/ql/src/Exceptions/Raising.qll
Normal file
@@ -0,0 +1,14 @@
|
||||
import python
|
||||
|
||||
/** Whether the raise statement 'r' raises 'type' from origin 'orig' */
|
||||
predicate type_or_typeof(Raise r, ClassObject type, AstNode orig) {
|
||||
exists(Expr exception |
|
||||
exception = r.getRaised() |
|
||||
exception.refersTo(type, _, orig)
|
||||
or
|
||||
not exists(ClassObject exc_type | exception.refersTo(exc_type)) and
|
||||
not type = theTypeType() and // First value is an unknown exception type
|
||||
exception.refersTo(_, type, orig)
|
||||
)
|
||||
|
||||
}
|
||||
5
python/ql/src/Exceptions/RaisingTuple.py
Normal file
5
python/ql/src/Exceptions/RaisingTuple.py
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
def raise_tuple():
|
||||
ex = Exception, "Important diagnostic information"
|
||||
raise ex
|
||||
47
python/ql/src/Exceptions/RaisingTuple.qhelp
Normal file
47
python/ql/src/Exceptions/RaisingTuple.qhelp
Normal file
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>In Python 2, if a tuple is raised then all elements but the first are ignored and only the first part is raised.
|
||||
If the first element is itself a tuple, then the first element of that is used and so on.
|
||||
This unlikely to be the intended effect and will most likely indicate some sort of error.</p>
|
||||
|
||||
<p>It is important to note that the exception in <code>raise Exception, message</code> is <em>not</em> a tuple, whereas the exception
|
||||
in <code>ex = Exception, message; raise ex</code> <em>is</em> a tuple.</p>
|
||||
|
||||
<p>
|
||||
In Python 3, raising a tuple is an error.
|
||||
</p>
|
||||
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Given that all but the first element of the tuple is ignored,
|
||||
the tuple should be replaced with its first element in order to
|
||||
improve the clarity of the code. If the subsequent parts of the tuple
|
||||
were intended to form the message, then they should be passed as an argument
|
||||
when creating the exception.
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>In the following example the intended error message is mistakenly used to form a tuple.</p>
|
||||
<sample src="RaisingTuple.py" />
|
||||
<p>This can be fixed, either by using the message to create the exception or using the message in the raise
|
||||
statement, as shown below.</p>
|
||||
<sample src="RaisingTuple2.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python Language Reference: <a href="https://docs.python.org/reference/executionmodel.html#exceptions">Exceptions</a>.</li>
|
||||
<li>Python Tutorial: <a href="https://docs.python.org/tutorial/errors.html#handling-exceptions">Handling Exceptions</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
18
python/ql/src/Exceptions/RaisingTuple.ql
Normal file
18
python/ql/src/Exceptions/RaisingTuple.ql
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @name Raising a tuple
|
||||
* @description Raising a tuple will result in all but the first element being discarded
|
||||
* @kind problem
|
||||
* @tags maintainability
|
||||
* @problem.severity warning
|
||||
* @sub-severity high
|
||||
* @precision very-high
|
||||
* @id py/raises-tuple
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
from Raise r, AstNode origin
|
||||
where r.getException().refersTo(_, theTupleType(), origin) 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"
|
||||
9
python/ql/src/Exceptions/RaisingTuple2.py
Normal file
9
python/ql/src/Exceptions/RaisingTuple2.py
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
def fixed_raise_tuple1():
|
||||
ex = Exception("Important diagnostic information")
|
||||
raise ex
|
||||
|
||||
|
||||
def fixed_raise_tuple2():
|
||||
raise Exception, "Important diagnostic information"
|
||||
51
python/ql/src/Exceptions/UnguardedNextInGenerator.qhelp
Normal file
51
python/ql/src/Exceptions/UnguardedNextInGenerator.qhelp
Normal file
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
The function <code>next()</code> will raise a <code>StopIteration</code> exception
|
||||
if the underlying iterator is exhausted.
|
||||
Normally this is fine, but in a generator may cause problems.
|
||||
Since the <code>StopIteration</code> is an exception it will be propagated out of the generator
|
||||
causing termination of the generator. This is unlikely to be the expected behavior and may mask
|
||||
errors.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This problem is considered sufficiently serious that <a href="https://www.python.org/dev/peps/pep-0479">PEP 479</a>
|
||||
has been accepted to modify the handling of <code>StopIteration</code> in generators. Consequently, code that does not handle
|
||||
<code>StopIteration</code> properly is likely to fail in future versions of Python.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
Each call to <code>next()</code> should be wrapped in a <code>try-except</code> to explicitly
|
||||
handle <code>StopIteration</code> exceptions.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
In the following example, an empty file part way through iteration will silently truncate the output as
|
||||
the <code>StopIteration</code> exception propagates to the top level.
|
||||
</p>
|
||||
|
||||
<sample src="UnguardedNextInGeneratorBad.py"/>
|
||||
|
||||
<p>
|
||||
In the following example <code>StopIteration</code> exception is explicitly handled,
|
||||
allowing all the files to be processed.
|
||||
</p>
|
||||
|
||||
<sample src="UnguardedNextInGeneratorGood.py"/>
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Python PEP index: <a href="https://www.python.org/dev/peps/pep-0479">PEP 479</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user