mirror of https://github.com/python/cpython
gh-97591: In `Exception.__setstate__()` acquire strong references before calling `tp_hash` slot (#97700)
This commit is contained in:
parent
8baef8ae36
commit
d639438609
|
@ -114,6 +114,31 @@ class ExceptionClassTests(unittest.TestCase):
|
|||
[repr(exc), exc.__class__.__name__ + '()'])
|
||||
self.interface_test_driver(results)
|
||||
|
||||
def test_setstate_refcount_no_crash(self):
|
||||
# gh-97591: Acquire strong reference before calling tp_hash slot
|
||||
# in PyObject_SetAttr.
|
||||
import gc
|
||||
d = {}
|
||||
class HashThisKeyWillClearTheDict(str):
|
||||
def __hash__(self) -> int:
|
||||
d.clear()
|
||||
return super().__hash__()
|
||||
class Value(str):
|
||||
pass
|
||||
exc = Exception()
|
||||
|
||||
d[HashThisKeyWillClearTheDict()] = Value() # refcount of Value() is 1 now
|
||||
|
||||
# Exception.__setstate__ should aquire a strong reference of key and
|
||||
# value in the dict. Otherwise, Value()'s refcount would go below
|
||||
# zero in the tp_hash call in PyObject_SetAttr(), and it would cause
|
||||
# crash in GC.
|
||||
exc.__setstate__(d) # __hash__() is called again here, clearing the dict.
|
||||
|
||||
# This GC would crash if the refcount of Value() goes below zero.
|
||||
gc.collect()
|
||||
|
||||
|
||||
class UsageTests(unittest.TestCase):
|
||||
|
||||
"""Test usage of exceptions"""
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Fixed a missing incref/decref pair in `Exception.__setstate__()`.
|
||||
Patch by Ofey Chan.
|
|
@ -167,8 +167,14 @@ BaseException_setstate(PyObject *self, PyObject *state)
|
|||
return NULL;
|
||||
}
|
||||
while (PyDict_Next(state, &i, &d_key, &d_value)) {
|
||||
if (PyObject_SetAttr(self, d_key, d_value) < 0)
|
||||
Py_INCREF(d_key);
|
||||
Py_INCREF(d_value);
|
||||
int res = PyObject_SetAttr(self, d_key, d_value);
|
||||
Py_DECREF(d_value);
|
||||
Py_DECREF(d_key);
|
||||
if (res < 0) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
|
|
Loading…
Reference in New Issue