diff --git a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.py b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.py
deleted file mode 100644
index e76c27145dbb..000000000000
--- a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#Incorrect unhashable class
-class MyMutableThing(object):
-
- def __init__(self):
- pass
-
- def __hash__(self):
- raise NotImplementedError("%r is unhashable" % self)
-
-#Make class unhashable in the standard way
-class MyCorrectMutableThing(object):
-
- def __init__(self):
- pass
-
- __hash__ = None
diff --git a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.qhelp b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.qhelp
index f4f0cd6920ab..42d7d421b0a6 100644
--- a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.qhelp
+++ b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.qhelp
@@ -9,7 +9,7 @@ When the expression a + b
is evaluated the Python virtual machine w
is not implemented it will call type(b).__radd__(b, a)
.
Since the virtual machine calls these special methods for common expressions, users of the class will expect these operations to raise standard exceptions.
-For example, users would expect that the expression a.b
might raise an AttributeError
+For example, users would expect that the expression a.b
may raise an AttributeError
if the object a
does not have an attribute b
.
If a KeyError
were raised instead,
then this would be unexpected and may break code that expected an AttributeError
, but not a KeyError
.
@@ -20,18 +20,18 @@ Therefore, if a method is unable to perform the expected operation then its resp
- - Attribute access,
a.b
: Raise AttributeError
- - Arithmetic operations,
a + b
: Do not raise an exception, return NotImplemented
instead.
- - Indexing,
a[b]
: Raise KeyError
.
- - Hashing,
hash(a)
: Use __hash__ = None
to indicate that an object is unhashable.
- - Equality methods,
a != b
: Never raise an exception, always return True
or False
.
- - Ordering comparison methods,
a < b
: Raise a TypeError
if the objects cannot be ordered.
- - Most others: Ideally, do not implement the method at all, otherwise raise
TypeError
to indicate that the operation is unsupported.
+ - Attribute access,
a.b
(__getattr__
): Raise AttributeError
.
+ - Arithmetic operations,
a + b
(__add__
): Do not raise an exception, return NotImplemented
instead.
+ - Indexing,
a[b]
(__getitem__
): Raise KeyError
or IndexError
.
+ - Hashing,
hash(a)
(__hash__
): Should not raise an exception. Use __hash__ = None
to indicate that an object is unhashable rather than raising an exception.
+ - Equality methods,
a == b
(__eq__
): Never raise an exception, always return True
or False
.
+ - Ordering comparison methods,
a < b
(__lt__
): Raise a TypeError
if the objects cannot be ordered.
+ - Most others: If the operation is never supported, the method often does not need to be implemented at all; otherwise a
TypeError
should be raised.
-If the method is meant to be abstract, then declare it so using the @abstractmethod
decorator.
+
If the method is intended to be abstract, and thus always raise an exception, then declare it so using the @abstractmethod
decorator.
Otherwise, either remove the method or ensure that the method raises an exception of the correct type.
@@ -39,31 +39,29 @@ Otherwise, either remove the method or ensure that the method raises an exceptio
-This example shows two unhashable classes. The first class is unhashable in a non-standard way which may cause maintenance problems.
-The second, corrected, class uses the standard idiom for unhashable classes.
+In the following example, the __add__
method of A
raises a TypeError
if other
is of the wrong type.
+However, it should return NotImplemented
instead of rising an exception, to allow other classes to support adding to A
.
+This is demonstrated in the class B
.
-
+
-In this example, the first class is implicitly abstract; the __add__
method is unimplemented,
-presumably with the expectation that it will be implemented by sub-classes.
-The second class makes this explicit with an @abstractmethod
decoration on the unimplemented __add__
method.
+In the following example, the __getitem__
method of C
raises a ValueError
, rather than a KeyError
or IndexError
as expected.
-
+
-In this last example, the first class implements a collection backed by the file store.
-However, should an IOError
be raised in the __getitem__
it will propagate to the caller.
-The second class handles any IOError
by reraising a KeyError
which is the standard exception for
-the __getitem__
method.
+In the following example, the class __hash__
method of D
raises NotImplementedError
.
+This causes D
to be incorrectly identified as hashable by isinstance(obj, collections.abc.Hashable)
; so the correct
+way to make a class unhashable is to set __hash__ = None
.
-
+
Python Language Reference: Special Method Names.
-Python Library Reference: Exceptions.
+Python Library Reference: Exceptions.
diff --git a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.ql b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.ql
index 4bf52af9061f..3cd7e0fe9871 100644
--- a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.ql
+++ b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.ql
@@ -7,114 +7,188 @@
* error-handling
* @problem.severity recommendation
* @sub-severity high
- * @precision very-high
+ * @precision high
* @id py/unexpected-raise-in-special-method
*/
import python
+import semmle.python.ApiGraphs
+import semmle.python.dataflow.new.internal.DataFlowDispatch
-private predicate attribute_method(string name) {
- name = "__getattribute__" or name = "__getattr__" or name = "__setattr__"
+/** Holds if `name` is the name of a special method for attribute access such as `a.b`, that should raise an `AttributeError`. */
+private predicate attributeMethod(string name) {
+ name = ["__getattribute__", "__getattr__", "__delattr__"] // __setattr__ excluded as it makes sense to raise different kinds of errors based on the `value` parameter
}
-private predicate indexing_method(string name) {
- name = "__getitem__" or name = "__setitem__" or name = "__delitem__"
+/** Holds if `name` is the name of a special method for indexing operations such as `a[b]`, that should raise a `LookupError`. */
+private predicate indexingMethod(string name) {
+ name = ["__getitem__", "__delitem__"] // __setitem__ excluded as it makes sense to raise different kinds of errors based on the `value` parameter
}
-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__"
+/** Holds if `name` is the name of a special method for arithmetic operations. */
+private predicate arithmeticMethod(string name) {
+ name =
+ [
+ "__add__", "__sub__", "__and__", "__or__", "__xor__", "__lshift__", "__rshift__", "__pow__",
+ "__mul__", "__div__", "__divmod__", "__truediv__", "__floordiv__", "__matmul__", "__radd__",
+ "__rsub__", "__rand__", "__ror__", "__rxor__", "__rlshift__", "__rrshift__", "__rpow__",
+ "__rmul__", "__rdiv__", "__rdivmod__", "__rtruediv__", "__rfloordiv__", "__rmatmul__",
+ "__iadd__", "__isub__", "__iand__", "__ior__", "__ixor__", "__ilshift__", "__irshift__",
+ "__ipow__", "__imul__", "__idiv__", "__idivmod__", "__itruediv__", "__ifloordiv__",
+ "__imatmul__", "__pos__", "__neg__", "__abs__", "__invert__",
]
}
-private predicate ordering_method(string name) {
- name = "__lt__"
- or
- name = "__le__"
- or
- name = "__gt__"
- or
- name = "__ge__"
- or
- name = "__cmp__" and major_version() = 2
+/** Holds if `name is the name of a special method for ordering operations such as `a < b`. */
+private predicate orderingMethod(string name) {
+ name =
+ [
+ "__lt__",
+ "__le__",
+ "__gt__",
+ "__ge__",
+ ]
}
-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__"
+/** Holds if `name` is the name of a special method for casting an object to a numeric type, such as `int(x)` */
+private predicate castMethod(string name) {
+ name =
+ [
+ "__int__",
+ "__float__",
+ "__index__",
+ "__trunc__",
+ "__complex__"
+ ] // __bool__ excluded as it makes sense to allow it to always raise
}
-predicate correct_raise(string name, ClassObject ex) {
- ex.getAnImproperSuperType() = theTypeErrorType() and
+/** Holds if we allow a special method named `name` to raise `exec` as an exception. */
+predicate correctRaise(string name, Expr exec) {
+ execIsOfType(exec, "TypeError") and
(
- name = "__copy__" or
- name = "__deepcopy__" or
- name = "__call__" or
- indexing_method(name) or
- attribute_method(name)
+ indexingMethod(name) or
+ attributeMethod(name) or
+ // Allow add methods to raise a TypeError, as they can be used for sequence concatenation as well as arithmetic
+ name = ["__add__", "__iadd__", "__radd__"]
)
or
- preferred_raise(name, ex)
- or
- preferred_raise(name, ex.getASuperType())
+ exists(string execName |
+ preferredRaise(name, execName, _) and
+ execIsOfType(exec, execName)
+ )
}
-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()
+/** Holds if it is preferred for `name` to raise exceptions of type `execName`. `message` is the alert message. */
+predicate preferredRaise(string name, string execName, string message) {
+ attributeMethod(name) and
+ execName = "AttributeError" and
+ message = "should raise an AttributeError instead."
+ or
+ indexingMethod(name) and
+ execName = "LookupError" and
+ message = "should raise a LookupError (KeyError or IndexError) instead."
+ or
+ orderingMethod(name) and
+ execName = "TypeError" and
+ message = "should raise a TypeError or return NotImplemented instead."
+ or
+ arithmeticMethod(name) and
+ execName = "ArithmeticError" and
+ message = "should raise an ArithmeticError or return NotImplemented instead."
+ or
+ name = "__bool__" and
+ execName = "TypeError" and
+ message = "should raise a TypeError instead."
}
-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."
+/** Holds if `exec` is an exception object of the type named `execName`. */
+predicate execIsOfType(Expr exec, string execName) {
+ // Might make sense to have execName be an IPA type here. Or part of a more general API modeling builtin/stdlib subclass relations.
+ exists(string subclass |
+ execName = "TypeError" and
+ subclass = "TypeError"
+ or
+ execName = "LookupError" and
+ subclass = ["LookupError", "KeyError", "IndexError"]
+ or
+ execName = "ArithmeticError" and
+ subclass = ["ArithmeticError", "FloatingPointError", "OverflowError", "ZeroDivisionError"]
+ or
+ execName = "AttributeError" and
+ subclass = "AttributeError"
+ |
+ exec = API::builtin(subclass).getACall().asExpr()
+ or
+ exec = API::builtin(subclass).getASubclass().getACall().asExpr()
+ )
+}
+
+/**
+ * Holds if `meth` need not be implemented if it always raises. `message` is the alert message, and `allowNotImplemented` is true
+ * if we still allow the method to always raise `NotImplementedError`.
+ */
+predicate noNeedToAlwaysRaise(Function meth, string message, boolean allowNotImplemented) {
+ meth.getName() = "__hash__" and
+ message = "use __hash__ = None instead." and
+ allowNotImplemented = false
+ or
+ castMethod(meth.getName()) and
+ message = "this method does not need to be implemented." and
+ allowNotImplemented = true and
+ // Allow an always raising cast method if it's overriding other behavior
+ not exists(Function overridden |
+ overridden.getName() = meth.getName() and
+ overridden.getScope() = getADirectSuperclass+(meth.getScope()) and
+ not alwaysRaises(overridden, _)
+ )
+}
+
+/** Holds if `func` has a decorator likely marking it as an abstract method. */
+predicate isAbstract(Function func) { func.getADecorator().(Name).getId().matches("%abstract%") }
+
+/** Holds if `f` always raises the exception `exec`. */
+predicate alwaysRaises(Function f, Expr exec) {
+ directlyRaises(f, exec) and
+ strictcount(Expr e | directlyRaises(f, e)) = 1 and
+ not exists(f.getANormalExit())
}
-predicate is_abstract(FunctionObject func) {
- func.getFunction().getADecorator().(Name).getId().matches("%abstract%")
+/** Holds if `f` directly raises `exec` using a `raise` statement. */
+predicate directlyRaises(Function f, Expr exec) {
+ exists(Raise r |
+ r.getScope() = f and
+ exec = r.getException() and
+ exec instanceof Call
+ )
}
-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()
+/** Holds if `exec` is a `NotImplementedError`. */
+predicate isNotImplementedError(Expr exec) {
+ exec = API::builtin("NotImplementedError").getACall().asExpr()
}
-from FunctionObject f, ClassObject cls, string message
+/** Gets the name of the builtin exception type `exec` constructs, if it can be determined. */
+string getExecName(Expr exec) { result = exec.(Call).getFunc().(Name).getId() }
+
+from Function f, Expr exec, string message
where
- f.getFunction().isSpecialMethod() and
- not is_abstract(f) and
- always_raises(f, cls) and
+ f.isSpecialMethod() and
+ not isAbstract(f) and
+ directlyRaises(f, exec) and
(
- no_need_to_raise(f.getName(), message) and not cls.getName() = "NotImplementedError"
+ exists(boolean allowNotImplemented, string subMessage |
+ alwaysRaises(f, exec) and
+ noNeedToAlwaysRaise(f, subMessage, allowNotImplemented) and
+ (allowNotImplemented = true implies not isNotImplementedError(exec)) and // don't alert if it's a NotImplementedError and that's ok
+ message = "This method always raises $@ - " + subMessage
+ )
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"
+ not isNotImplementedError(exec) and
+ not correctRaise(f.getName(), exec) and
+ exists(string subMessage | preferredRaise(f.getName(), _, subMessage) |
+ if alwaysRaises(f, exec)
+ then message = "This method always raises $@ - " + subMessage
+ else message = "This method raises $@ - " + subMessage
)
)
-select f, "Function always raises $@; " + message, cls, cls.toString()
+select f, message, exec, getExecName(exec)
diff --git a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod2.py b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod2.py
deleted file mode 100644
index 405400bfe614..000000000000
--- a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod2.py
+++ /dev/null
@@ -1,15 +0,0 @@
-
-#Abstract base class, but don't declare it.
-class ImplicitAbstractClass(object):
-
- def __add__(self, other):
- raise NotImplementedError()
-
-#Make abstractness explicit.
-class ExplicitAbstractClass:
- __metaclass__ = ABCMeta
-
- @abstractmethod
- def __add__(self, other):
- raise NotImplementedError()
-
diff --git a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod3.py b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod3.py
deleted file mode 100644
index 048d5043b4dc..000000000000
--- a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod3.py
+++ /dev/null
@@ -1,27 +0,0 @@
-
-#Incorrect file-backed table
-class FileBackedTable(object):
-
- def __getitem__(self, key):
- if key not in self.index:
- raise IOError("Key '%s' not in table" % key)
- else:
- #May raise an IOError
- return self.backing.get_row(key)
-
-#Correct by transforming exception
-class ObjectLikeFileBackedTable(object):
-
- def get_from_key(self, key):
- if key not in self.index:
- raise IOError("Key '%s' not in table" % key)
- else:
- #May raise an IOError
- return self.backing.get_row(key)
-
- def __getitem__(self, key):
- try:
- return self.get_from_key(key)
- except IOError:
- raise KeyError(key)
-
diff --git a/python/ql/src/Functions/examples/IncorrectRaiseInSpecialMethod.py b/python/ql/src/Functions/examples/IncorrectRaiseInSpecialMethod.py
new file mode 100644
index 000000000000..d565a86cab27
--- /dev/null
+++ b/python/ql/src/Functions/examples/IncorrectRaiseInSpecialMethod.py
@@ -0,0 +1,22 @@
+class A:
+ def __init__(self, a):
+ self.a = a
+
+ def __add__(self, other):
+ # BAD: Should return NotImplemented instead of raising
+ if not isinstance(other,A):
+ raise TypeError(f"Cannot add A to {other.__class__}")
+ return A(self.a + other.a)
+
+class B:
+ def __init__(self, a):
+ self.a = a
+
+ def __add__(self, other):
+ # GOOD: Returning NotImplemented allows for the operation to fallback to other implementations to allow other classes to support adding to B.
+ if not isinstance(other,B):
+ return NotImplemented
+ return B(self.a + other.a)
+
+
+
diff --git a/python/ql/src/Functions/examples/IncorrectRaiseInSpecialMethod2.py b/python/ql/src/Functions/examples/IncorrectRaiseInSpecialMethod2.py
new file mode 100644
index 000000000000..ba5f90f46708
--- /dev/null
+++ b/python/ql/src/Functions/examples/IncorrectRaiseInSpecialMethod2.py
@@ -0,0 +1,7 @@
+class C:
+ def __getitem__(self, idx):
+ if self.idx < 0:
+ # BAD: Should raise a KeyError or IndexError instead.
+ raise ValueError("Invalid index")
+ return self.lookup(idx)
+
diff --git a/python/ql/src/Functions/examples/IncorrectRaiseInSpecialMethod3.py b/python/ql/src/Functions/examples/IncorrectRaiseInSpecialMethod3.py
new file mode 100644
index 000000000000..33541adc7e64
--- /dev/null
+++ b/python/ql/src/Functions/examples/IncorrectRaiseInSpecialMethod3.py
@@ -0,0 +1,4 @@
+class D:
+ def __hash__(self):
+ # BAD: Use `__hash__ = None` instead.
+ raise NotImplementedError(f"{self.__class__} is unhashable.")
\ No newline at end of file
diff --git a/python/ql/src/change-notes/2025-07-25-unexpected-raise-special-method.md b/python/ql/src/change-notes/2025-07-25-unexpected-raise-special-method.md
new file mode 100644
index 000000000000..4b79dbc3b81e
--- /dev/null
+++ b/python/ql/src/change-notes/2025-07-25-unexpected-raise-special-method.md
@@ -0,0 +1,5 @@
+---
+category: minorAnalysis
+---
+* The `py/unexpected-raise-in-special-method` query has been modernized. It produces additional results in cases where the exception is
+only raised conditionally. Its precision has been changed from `very-high` to `high`.
\ No newline at end of file
diff --git a/python/ql/test/query-tests/Functions/IncorrectRaiseInSpecialMethod/IncorrectRaiseInSpecialMethod.expected b/python/ql/test/query-tests/Functions/IncorrectRaiseInSpecialMethod/IncorrectRaiseInSpecialMethod.expected
new file mode 100644
index 000000000000..3907a725ee18
--- /dev/null
+++ b/python/ql/test/query-tests/Functions/IncorrectRaiseInSpecialMethod/IncorrectRaiseInSpecialMethod.expected
@@ -0,0 +1,6 @@
+| test.py:6:5:6:33 | Function __getitem__ | This method always raises $@ - should raise a LookupError (KeyError or IndexError) instead. | test.py:7:15:7:33 | ZeroDivisionError() | ZeroDivisionError |
+| test.py:9:5:9:32 | Function __getattr__ | This method always raises $@ - should raise an AttributeError instead. | test.py:10:15:10:33 | ZeroDivisionError() | ZeroDivisionError |
+| test.py:12:5:12:23 | Function __bool__ | This method always raises $@ - should raise a TypeError instead. | test.py:13:15:13:26 | ValueError() | ValueError |
+| test.py:15:5:15:22 | Function __int__ | This method always raises $@ - this method does not need to be implemented. | test.py:16:15:16:26 | ValueError() | ValueError |
+| test.py:24:5:24:23 | Function __hash__ | This method always raises $@ - use __hash__ = None instead. | test.py:25:15:25:35 | NotImplementedError() | NotImplementedError |
+| test.py:28:5:28:29 | Function __sub__ | This method raises $@ - should raise an ArithmeticError or return NotImplemented instead. | test.py:30:19:30:29 | TypeError() | TypeError |
diff --git a/python/ql/test/query-tests/Functions/IncorrectRaiseInSpecialMethod/IncorrectRaiseInSpecialMethod.qlref b/python/ql/test/query-tests/Functions/IncorrectRaiseInSpecialMethod/IncorrectRaiseInSpecialMethod.qlref
new file mode 100644
index 000000000000..a81e499ea66b
--- /dev/null
+++ b/python/ql/test/query-tests/Functions/IncorrectRaiseInSpecialMethod/IncorrectRaiseInSpecialMethod.qlref
@@ -0,0 +1,2 @@
+query: Functions/IncorrectRaiseInSpecialMethod.ql
+postprocess: utils/test/InlineExpectationsTestQuery.ql
\ No newline at end of file
diff --git a/python/ql/test/query-tests/Functions/IncorrectRaiseInSpecialMethod/test.py b/python/ql/test/query-tests/Functions/IncorrectRaiseInSpecialMethod/test.py
new file mode 100644
index 000000000000..d5b1bc585f62
--- /dev/null
+++ b/python/ql/test/query-tests/Functions/IncorrectRaiseInSpecialMethod/test.py
@@ -0,0 +1,66 @@
+class A:
+
+ def __add__(self, other): # No alert - Always allow NotImplementedError
+ raise NotImplementedError()
+
+ def __getitem__(self, index): # $ Alert
+ raise ZeroDivisionError()
+
+ def __getattr__(self, name): # $ Alert
+ raise ZeroDivisionError()
+
+ def __bool__(self): # $ Alert
+ raise ValueError()
+
+ def __int__(self): # $ Alert # Cast method need not be defined to always raise
+ raise ValueError()
+
+ def __float__(self): # No alert - OK to raise conditionally
+ if self.z:
+ return 0
+ else:
+ raise ValueError()
+
+ def __hash__(self): # $ Alert # should use __hash__=None rather than stub implementation to make class unhashable
+ raise NotImplementedError()
+
+class B:
+ def __sub__(self, other): # $ Alert # should return NotImplemented instead
+ if not isinstance(other,B):
+ raise TypeError()
+ return self
+
+ def __add__(self, other): # No alert - allow add to raise a TypeError, as it is sometimes used for sequence concatenation as well as arithmetic
+ if not isinstance(other,B):
+ raise TypeError()
+ return self
+
+ def __setitem__(self, key, val): # No alert - allow setitem to raise arbitrary exceptions as they could be due to the value, rather than a LookupError relating to the key
+ if val < 0:
+ raise ValueError()
+
+ def __getitem__(self, key): # No alert - indexing method allowed to raise TypeError or subclasses of LookupError.
+ if not isinstance(key, int):
+ raise TypeError()
+ if key < 0:
+ raise KeyError()
+ return 3
+
+ def __getattribute__(self, name):
+ if name != "a":
+ raise AttributeError()
+ return 2
+
+ def __div__(self, other):
+ if other == 0:
+ raise ZeroDivisionError()
+ return self
+
+
+class D:
+ def __int__(self):
+ return 2
+
+class E(D):
+ def __int__(self): # No alert - cast method may override to raise exception
+ raise TypeError()
\ No newline at end of file
diff --git a/python/ql/test/query-tests/Functions/general/IncorrectRaiseInSpecialMethod.expected b/python/ql/test/query-tests/Functions/general/IncorrectRaiseInSpecialMethod.expected
deleted file mode 100644
index dd4429de02e9..000000000000
--- a/python/ql/test/query-tests/Functions/general/IncorrectRaiseInSpecialMethod.expected
+++ /dev/null
@@ -1,3 +0,0 @@
-| protocols.py:98:5:98:33 | Function __getitem__ | Function always raises $@; raise LookupError instead | file://:Compiled Code:0:0:0:0 | builtin-class ZeroDivisionError | builtin-class ZeroDivisionError |
-| protocols.py:101:5:101:26 | Function __getattr__ | Function always raises $@; raise AttributeError instead | file://:Compiled Code:0:0:0:0 | builtin-class ZeroDivisionError | builtin-class ZeroDivisionError |
-| protocols.py:104:5:104:23 | Function __bool__ | Function always raises $@; raise TypeError instead | file://:Compiled Code:0:0:0:0 | builtin-class ZeroDivisionError | builtin-class ZeroDivisionError |
diff --git a/python/ql/test/query-tests/Functions/general/IncorrectRaiseInSpecialMethod.qlref b/python/ql/test/query-tests/Functions/general/IncorrectRaiseInSpecialMethod.qlref
deleted file mode 100644
index 07fd22a93767..000000000000
--- a/python/ql/test/query-tests/Functions/general/IncorrectRaiseInSpecialMethod.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Functions/IncorrectRaiseInSpecialMethod.ql
\ No newline at end of file
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy