mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
gh-89373: _Py_Dealloc() checks tp_dealloc exception (#32357)
If Python is built in debug mode, _Py_Dealloc() now ensures that the tp_dealloc function leaves the current exception unchanged.
This commit is contained in:
parent
8a4e519e78
commit
364ed94092
4 changed files with 50 additions and 3 deletions
|
@ -288,6 +288,7 @@ Effects of a debug build:
|
||||||
to detect usage of uninitialized objects.
|
to detect usage of uninitialized objects.
|
||||||
* Ensure that functions which can clear or replace the current exception are
|
* Ensure that functions which can clear or replace the current exception are
|
||||||
not called with an exception raised.
|
not called with an exception raised.
|
||||||
|
* Check that deallocator functions don't change the current exception.
|
||||||
* The garbage collector (:func:`gc.collect` function) runs some basic checks
|
* The garbage collector (:func:`gc.collect` function) runs some basic checks
|
||||||
on objects consistency.
|
on objects consistency.
|
||||||
* The :c:macro:`Py_SAFE_DOWNCAST()` macro checks for integer underflow and
|
* The :c:macro:`Py_SAFE_DOWNCAST()` macro checks for integer underflow and
|
||||||
|
|
|
@ -516,7 +516,12 @@ class CAPITest(unittest.TestCase):
|
||||||
del subclass_instance
|
del subclass_instance
|
||||||
|
|
||||||
# Test that setting __class__ modified the reference counts of the types
|
# Test that setting __class__ modified the reference counts of the types
|
||||||
self.assertEqual(type_refcnt - 1, B.refcnt_in_del)
|
if Py_DEBUG:
|
||||||
|
# gh-89373: In debug mode, _Py_Dealloc() keeps a strong reference
|
||||||
|
# to the type while calling tp_dealloc()
|
||||||
|
self.assertEqual(type_refcnt, B.refcnt_in_del)
|
||||||
|
else:
|
||||||
|
self.assertEqual(type_refcnt - 1, B.refcnt_in_del)
|
||||||
self.assertEqual(new_type_refcnt + 1, A.refcnt_in_del)
|
self.assertEqual(new_type_refcnt + 1, A.refcnt_in_del)
|
||||||
|
|
||||||
# Test that the original type already has decreased its refcnt
|
# Test that the original type already has decreased its refcnt
|
||||||
|
@ -581,7 +586,12 @@ class CAPITest(unittest.TestCase):
|
||||||
del subclass_instance
|
del subclass_instance
|
||||||
|
|
||||||
# Test that setting __class__ modified the reference counts of the types
|
# Test that setting __class__ modified the reference counts of the types
|
||||||
self.assertEqual(type_refcnt - 1, _testcapi.HeapCTypeSubclassWithFinalizer.refcnt_in_del)
|
if Py_DEBUG:
|
||||||
|
# gh-89373: In debug mode, _Py_Dealloc() keeps a strong reference
|
||||||
|
# to the type while calling tp_dealloc()
|
||||||
|
self.assertEqual(type_refcnt, _testcapi.HeapCTypeSubclassWithFinalizer.refcnt_in_del)
|
||||||
|
else:
|
||||||
|
self.assertEqual(type_refcnt - 1, _testcapi.HeapCTypeSubclassWithFinalizer.refcnt_in_del)
|
||||||
self.assertEqual(new_type_refcnt + 1, _testcapi.HeapCTypeSubclass.refcnt_in_del)
|
self.assertEqual(new_type_refcnt + 1, _testcapi.HeapCTypeSubclass.refcnt_in_del)
|
||||||
|
|
||||||
# Test that the original type already has decreased its refcnt
|
# Test that the original type already has decreased its refcnt
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
If Python is built in debug mode, Python now ensures that deallocator
|
||||||
|
functions leave the current exception unchanged. Patch by Victor Stinner.
|
|
@ -2354,11 +2354,45 @@ _PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,
|
||||||
void
|
void
|
||||||
_Py_Dealloc(PyObject *op)
|
_Py_Dealloc(PyObject *op)
|
||||||
{
|
{
|
||||||
destructor dealloc = Py_TYPE(op)->tp_dealloc;
|
PyTypeObject *type = Py_TYPE(op);
|
||||||
|
destructor dealloc = type->tp_dealloc;
|
||||||
|
#ifdef Py_DEBUG
|
||||||
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
|
PyObject *old_exc_type = tstate->curexc_type;
|
||||||
|
// Keep the old exception type alive to prevent undefined behavior
|
||||||
|
// on (tstate->curexc_type != old_exc_type) below
|
||||||
|
Py_XINCREF(old_exc_type);
|
||||||
|
// Make sure that type->tp_name remains valid
|
||||||
|
Py_INCREF(type);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef Py_TRACE_REFS
|
#ifdef Py_TRACE_REFS
|
||||||
_Py_ForgetReference(op);
|
_Py_ForgetReference(op);
|
||||||
#endif
|
#endif
|
||||||
(*dealloc)(op);
|
(*dealloc)(op);
|
||||||
|
|
||||||
|
#ifdef Py_DEBUG
|
||||||
|
// gh-89373: The tp_dealloc function must leave the current exception
|
||||||
|
// unchanged.
|
||||||
|
if (tstate->curexc_type != old_exc_type) {
|
||||||
|
const char *err;
|
||||||
|
if (old_exc_type == NULL) {
|
||||||
|
err = "Deallocator of type '%s' raised an exception";
|
||||||
|
}
|
||||||
|
else if (tstate->curexc_type == NULL) {
|
||||||
|
err = "Deallocator of type '%s' cleared the current exception";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// It can happen if dealloc() normalized the current exception.
|
||||||
|
// A deallocator function must not change the current exception,
|
||||||
|
// not even normalize it.
|
||||||
|
err = "Deallocator of type '%s' overrode the current exception";
|
||||||
|
}
|
||||||
|
_Py_FatalErrorFormat(__func__, err, type->tp_name);
|
||||||
|
}
|
||||||
|
Py_XDECREF(old_exc_type);
|
||||||
|
Py_DECREF(type);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue