Skip to content

Commit

Permalink
gh-97591: In Exception.__setstate__() acquire strong references bef…
Browse files Browse the repository at this point in the history
…ore calling `tp_hash` slot (GH-97700)

(cherry picked from commit d639438)

Co-authored-by: Ofey Chan <[email protected]>
  • Loading branch information
miss-islington and ofey404 authored Oct 2, 2022
1 parent a4fbb94 commit c6fcbb4
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 1 deletion.
25 changes: 25 additions & 0 deletions Lib/test/test_baseexception.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,31 @@ def test_interface_no_arg(self):
[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"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fixed a missing incref/decref pair in `Exception.__setstate__()`.
Patch by Ofey Chan.
8 changes: 7 additions & 1 deletion Objects/exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,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;
Expand Down

0 comments on commit c6fcbb4

Please sign in to comment.