richcompare suppresses all errors raised from descriptors of comparison special methods
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Python
- Star
- 77.2k
- Fork
- 35.9k
- Chỉ số merge pull request
- Chỉ số pull request đang chờ
Mô tả
Bug report
Bug description:
This is reporting possibly incorrect suppression of all errors generated by descriptors for the comparison special methods __eq__, __lt__, etc. (and possibly some other special methods). This only affects errors during the __get__ stage, while errors from the actual call propagate as expected.
I say possibly incorrect because I can see this being one of those sensitive underbellies that could affect existing behavior for some libraries. However, this seems inconsistent relative to some other special methods like __contains__, __bool__, etc., and since it suppresses all errors like SystemExit, MemoryError, KeyboardInterrupt, etc. it can hide underlying problems or behaviors that are hard to diagnose.
Minimal example
Minimal demonstration of the issue below is a descriptor which always raises an exception during __get__. The result of comparison through == returns False instead of raising the error. The type of error raised from __get__ is not important, it could have been from exit() or anything else down the stack. However, other special methods like __bool__ do raise the exception, as does directly accessing __eq__ as a method.
class BadDescriptor:
def __get__(self, obj, cls=None):
assert False
class BadBase:
__eq__ = BadDescriptor()
__bool__ = BadDescriptor()
b = BadBase()
print(f"{b == 1=}")
print(f"{b.__eq__(1)=}")
b == 1=False
Traceback (most recent call last):
File "bad_descriptor.py", line 24, in <module>
print(f"{b.__eq__(1)=}")
^^^^^^^^
File "bad_descriptor.py", line 14, in __get__
assert False
^^^^^
AssertionError
Suspected cause and proposed resolution
The source of this behavior appears to be in Objects/typeobject.c function slot_tp_richcompare. Currently if an error occurs while getting the comparison method from a descriptor the error is cleared and NotImplemented is returned. Looking at some of the other special methods it seems like there is some inconsistency, some check for PyErr_Occurred and propagate the exception (EG slot_nb_bool), while others clear the error and default to some other action (E.G. slot_tp_repr).
if (func == NULL) {
PyErr_Clear();
Py_RETURN_NOTIMPLEMENTED;
}
A couple experimental resolutions were attempted to look at the consequence. The first was to treat errors similar to slot_nb_bool, propagating them instead of suppressing.
if (func == NULL) {
if(PyErr_Occurred()){
return NULL;
}
Py_RETURN_NOTIMPLEMENTED;
}
However, this caused a failed test which seems to be checking that raising AttributeError should lead to suppression and default comparison. It is not clear to me if that actually should be the expected behavior, and the comment in the test seems to indicate that it may actually be testing for some internal regression:
======================================================================
ERROR: testForExceptionsRaisedInInstanceGetattr2 (test.test_class.ClassTests.testForExceptionsRaisedInInstanceGetattr2)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/cdodd/proj/contrib/cpython/Lib/test/test_class.py", line 592, in testForExceptionsRaisedInInstanceGetattr2
E() == E() # In debug mode, caused a C-level assert() to fail
^^^^^^^^^^
File "/home/cdodd/proj/contrib/cpython/Lib/test/test_class.py", line 580, in booh
raise AttributeError("booh")
AttributeError: booh
But, as a compromise the following alternative checking specifically for AttributeError passes all tests enabled for my current platform. Depending on feedback here I am willing to contribute a pull request once I have a better idea as to what is expected/desired in these cases.
if (func == NULL) {
PyObject* exc = PyErr_Occurred();
if (exc != NULL){
if (PyErr_GivenExceptionMatches(exc, PyExc_AttributeError)){
// NOTE: suppresses only "attribute" errors
PyErr_Clear();
}else{
return NULL;
}
}
Py_RETURN_NOTIMPLEMENTED;
}
CPython versions tested on:
3.14, CPython main branch
Operating systems tested on:
Linux
Linked PRs
- gh-131187
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu với Objects/typeobject.c và slot_tp_richcompare, sau đó đọc test/test_class.py, đặc biệt là testForExceptionsRaisedInInstanceGetattr2 và helper booh của nó. Xem lại PR được liên kết gh-131187 và xác định cách dự kiến xử lý lỗi descriptor trong quá trình tra cứu phép so sánh; hoàn thành nghĩa là hành vi đã được quyết định và bộ test liên quan chạy đạt.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- c, python
- Lĩnh vực
- compilers
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Đình trệ
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 30/100