mirror of
https://github.com/github/codeql.git
synced 2025-12-24 12:46:34 +01:00
121 lines
3.5 KiB
Plaintext
121 lines
3.5 KiB
Plaintext
/**
|
|
* @name Non-standard exception raised in special method
|
|
* @description Raising a non-standard exception in a special method alters the expected interface of that method.
|
|
* @kind problem
|
|
* @tags reliability
|
|
* maintainability
|
|
* convention
|
|
* @problem.severity recommendation
|
|
* @sub-severity high
|
|
* @precision very-high
|
|
* @id py/unexpected-raise-in-special-method
|
|
*/
|
|
|
|
import python
|
|
|
|
private predicate attribute_method(string name) {
|
|
name = "__getattribute__" or name = "__getattr__" or name = "__setattr__"
|
|
}
|
|
|
|
private predicate indexing_method(string name) {
|
|
name = "__getitem__" or name = "__setitem__" or name = "__delitem__"
|
|
}
|
|
|
|
private predicate arithmetic_method(string name) {
|
|
name in [
|
|
"__add__", "__sub__", "__or__", "__xor__", "__rshift__", "__pow__", "__mul__", "__neg__",
|
|
"__radd__", "__rsub__", "__rdiv__", "__rfloordiv__", "__div__", "__rdiv__", "__rlshift__",
|
|
"__rand__", "__ror__", "__rxor__", "__rrshift__", "__rpow__", "__rmul__", "__truediv__",
|
|
"__rtruediv__", "__pos__", "__iadd__", "__isub__", "__idiv__", "__ifloordiv__", "__idiv__",
|
|
"__ilshift__", "__iand__", "__ior__", "__ixor__", "__irshift__", "__abs__", "__ipow__",
|
|
"__imul__", "__itruediv__", "__floordiv__", "__div__", "__divmod__", "__lshift__", "__and__"
|
|
]
|
|
}
|
|
|
|
private predicate ordering_method(string name) {
|
|
name = "__lt__"
|
|
or
|
|
name = "__le__"
|
|
or
|
|
name = "__gt__"
|
|
or
|
|
name = "__ge__"
|
|
or
|
|
name = "__cmp__" and major_version() = 2
|
|
}
|
|
|
|
private predicate cast_method(string name) {
|
|
name = "__nonzero__" and major_version() = 2
|
|
or
|
|
name = "__int__"
|
|
or
|
|
name = "__float__"
|
|
or
|
|
name = "__long__"
|
|
or
|
|
name = "__trunc__"
|
|
or
|
|
name = "__complex__"
|
|
}
|
|
|
|
predicate correct_raise(string name, ClassObject ex) {
|
|
ex.getAnImproperSuperType() = theTypeErrorType() and
|
|
(
|
|
name = "__copy__" or
|
|
name = "__deepcopy__" or
|
|
name = "__call__" or
|
|
indexing_method(name) or
|
|
attribute_method(name)
|
|
)
|
|
or
|
|
preferred_raise(name, ex)
|
|
or
|
|
preferred_raise(name, ex.getASuperType())
|
|
}
|
|
|
|
predicate preferred_raise(string name, ClassObject ex) {
|
|
attribute_method(name) and ex = theAttributeErrorType()
|
|
or
|
|
indexing_method(name) and ex = Object::builtin("LookupError")
|
|
or
|
|
ordering_method(name) and ex = theTypeErrorType()
|
|
or
|
|
arithmetic_method(name) and ex = Object::builtin("ArithmeticError")
|
|
or
|
|
name = "__bool__" and ex = theTypeErrorType()
|
|
}
|
|
|
|
predicate no_need_to_raise(string name, string message) {
|
|
name = "__hash__" and message = "use __hash__ = None instead"
|
|
or
|
|
cast_method(name) and message = "there is no need to implement the method at all."
|
|
}
|
|
|
|
predicate is_abstract(FunctionObject func) {
|
|
func.getFunction().getADecorator().(Name).getId().matches("%abstract%")
|
|
}
|
|
|
|
predicate always_raises(FunctionObject f, ClassObject ex) {
|
|
ex = f.getARaisedType() and
|
|
strictcount(f.getARaisedType()) = 1 and
|
|
not exists(f.getFunction().getANormalExit()) and
|
|
/* raising StopIteration is equivalent to a return in a generator */
|
|
not ex = theStopIterationType()
|
|
}
|
|
|
|
from FunctionObject f, ClassObject cls, string message
|
|
where
|
|
f.getFunction().isSpecialMethod() and
|
|
not is_abstract(f) and
|
|
always_raises(f, cls) and
|
|
(
|
|
no_need_to_raise(f.getName(), message) and not cls.getName() = "NotImplementedError"
|
|
or
|
|
not correct_raise(f.getName(), cls) and
|
|
not cls.getName() = "NotImplementedError" and
|
|
exists(ClassObject preferred | preferred_raise(f.getName(), preferred) |
|
|
message = "raise " + preferred.getName() + " instead"
|
|
)
|
|
)
|
|
select f, "Function always raises $@; " + message, cls, cls.toString()
|