Skip to content

Commit c6fcbb4

Browse files
gh-97591: In Exception.__setstate__() acquire strong references before calling tp_hash slot (GH-97700)
(cherry picked from commit d639438) Co-authored-by: Ofey Chan <ofey206@gmail.com>
1 parent a4fbb94 commit c6fcbb4

File tree

3 files changed

+34
-1
lines changed

3 files changed

+34
-1
lines changed

Lib/test/test_baseexception.py

Lines changed: 25 additions & 0 deletions
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"""
Lines changed: 2 additions & 0 deletions
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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,14 @@ BaseException_setstate(PyObject *self, PyObject *state)
156156
return NULL;
157157
}
158158
while (PyDict_Next(state, &i, &d_key, &d_value)) {
159-
if (PyObject_SetAttr(self, d_key, d_value) < 0)
159+
Py_INCREF(d_key);
160+
Py_INCREF(d_value);
161+
int res = PyObject_SetAttr(self, d_key, d_value);
162+
Py_DECREF(d_value);
163+
Py_DECREF(d_key);
164+
if (res < 0) {
160165
return NULL;
166+
}
161167
}
162168
}
163169
Py_RETURN_NONE;

0 commit comments

Comments
 (0)
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