Skip to content

Commit e401b65

Browse files
ofey404serhiy-storchaka
authored andcommitted
pythongh-97591: In Exception.__setstate__() acquire strong references before calling tp_hash slot (python#97700)
1 parent 5ffc011 commit e401b65

File tree

3 files changed

+34
-1
lines changed

3 files changed

+34
-1
lines changed

Lib/test/test_baseexception.py

+25
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,31 @@ def test_interface_no_arg(self):
114114
[repr(exc), exc.__class__.__name__ + '()'])
115115
self.interface_test_driver(results)
116116

117+
def test_setstate_refcount_no_crash(self):
118+
# gh-97591: Acquire strong reference before calling tp_hash slot
119+
# in PyObject_SetAttr.
120+
import gc
121+
d = {}
122+
class HashThisKeyWillClearTheDict(str):
123+
def __hash__(self) -> int:
124+
d.clear()
125+
return super().__hash__()
126+
class Value(str):
127+
pass
128+
exc = Exception()
129+
130+
d[HashThisKeyWillClearTheDict()] = Value() # refcount of Value() is 1 now
131+
132+
# Exception.__setstate__ should aquire a strong reference of key and
133+
# value in the dict. Otherwise, Value()'s refcount would go below
134+
# zero in the tp_hash call in PyObject_SetAttr(), and it would cause
135+
# crash in GC.
136+
exc.__setstate__(d) # __hash__() is called again here, clearing the dict.
137+
138+
# This GC would crash if the refcount of Value() goes below zero.
139+
gc.collect()
140+
141+
117142
class UsageTests(unittest.TestCase):
118143

119144
"""Test usage of exceptions"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed a missing incref/decref pair in `Exception.__setstate__()`.
2+
Patch by Ofey Chan.

Objects/exceptions.c

+7-1
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,14 @@ BaseException_setstate(PyObject *self, PyObject *state)
167167
return NULL;
168168
}
169169
while (PyDict_Next(state, &i, &d_key, &d_value)) {
170-
if (PyObject_SetAttr(self, d_key, d_value) < 0)
170+
Py_INCREF(d_key);
171+
Py_INCREF(d_value);
172+
int res = PyObject_SetAttr(self, d_key, d_value);
173+
Py_DECREF(d_value);
174+
Py_DECREF(d_key);
175+
if (res < 0) {
171176
return NULL;
177+
}
172178
}
173179
}
174180
Py_RETURN_NONE;

0 commit comments

Comments
 (0)