mirror of https://github.com/python/cpython
gh-102381: don't call watcher callback with dead object (#102382)
Co-authored-by: T. Wouters <thomas@python.org>
This commit is contained in:
parent
a33ca2ad1f
commit
1e703a4733
|
@ -172,6 +172,11 @@ bound into a function.
|
||||||
before the destruction of *co* takes place, so the prior state of *co*
|
before the destruction of *co* takes place, so the prior state of *co*
|
||||||
can be inspected.
|
can be inspected.
|
||||||
|
|
||||||
|
If *event* is ``PY_CODE_EVENT_DESTROY``, taking a reference in the callback
|
||||||
|
to the about-to-be-destroyed code object will resurrect it and prevent it
|
||||||
|
from being freed at this time. When the resurrected object is destroyed
|
||||||
|
later, any watcher callbacks active at that time will be called again.
|
||||||
|
|
||||||
Users of this API should not rely on internal runtime implementation
|
Users of this API should not rely on internal runtime implementation
|
||||||
details. Such details may include, but are not limited to, the exact
|
details. Such details may include, but are not limited to, the exact
|
||||||
order and timing of creation and destruction of code objects. While
|
order and timing of creation and destruction of code objects. While
|
||||||
|
@ -179,9 +184,15 @@ bound into a function.
|
||||||
(including whether a callback is invoked or not), it does not change
|
(including whether a callback is invoked or not), it does not change
|
||||||
the semantics of the Python code being executed.
|
the semantics of the Python code being executed.
|
||||||
|
|
||||||
If the callback returns with an exception set, it must return ``-1``; this
|
If the callback sets an exception, it must return ``-1``; this exception will
|
||||||
exception will be printed as an unraisable exception using
|
be printed as an unraisable exception using :c:func:`PyErr_WriteUnraisable`.
|
||||||
:c:func:`PyErr_WriteUnraisable`. Otherwise it should return ``0``.
|
Otherwise it should return ``0``.
|
||||||
|
|
||||||
|
There may already be a pending exception set on entry to the callback. In
|
||||||
|
this case, the callback should return ``0`` with the same exception still
|
||||||
|
set. This means the callback may not call any other API that can set an
|
||||||
|
exception unless it saves and clears the exception state first, and restores
|
||||||
|
it before returning.
|
||||||
|
|
||||||
.. versionadded:: 3.12
|
.. versionadded:: 3.12
|
||||||
|
|
||||||
|
|
|
@ -298,13 +298,26 @@ Dictionary Objects
|
||||||
dictionary.
|
dictionary.
|
||||||
|
|
||||||
The callback may inspect but must not modify *dict*; doing so could have
|
The callback may inspect but must not modify *dict*; doing so could have
|
||||||
unpredictable effects, including infinite recursion.
|
unpredictable effects, including infinite recursion. Do not trigger Python
|
||||||
|
code execution in the callback, as it could modify the dict as a side effect.
|
||||||
|
|
||||||
|
If *event* is ``PyDict_EVENT_DEALLOCATED``, taking a new reference in the
|
||||||
|
callback to the about-to-be-destroyed dictionary will resurrect it and
|
||||||
|
prevent it from being freed at this time. When the resurrected object is
|
||||||
|
destroyed later, any watcher callbacks active at that time will be called
|
||||||
|
again.
|
||||||
|
|
||||||
Callbacks occur before the notified modification to *dict* takes place, so
|
Callbacks occur before the notified modification to *dict* takes place, so
|
||||||
the prior state of *dict* can be inspected.
|
the prior state of *dict* can be inspected.
|
||||||
|
|
||||||
If the callback returns with an exception set, it must return ``-1``; this
|
If the callback sets an exception, it must return ``-1``; this exception will
|
||||||
exception will be printed as an unraisable exception using
|
be printed as an unraisable exception using :c:func:`PyErr_WriteUnraisable`.
|
||||||
:c:func:`PyErr_WriteUnraisable`. Otherwise it should return ``0``.
|
Otherwise it should return ``0``.
|
||||||
|
|
||||||
|
There may already be a pending exception set on entry to the callback. In
|
||||||
|
this case, the callback should return ``0`` with the same exception still
|
||||||
|
set. This means the callback may not call any other API that can set an
|
||||||
|
exception unless it saves and clears the exception state first, and restores
|
||||||
|
it before returning.
|
||||||
|
|
||||||
.. versionadded:: 3.12
|
.. versionadded:: 3.12
|
||||||
|
|
|
@ -173,8 +173,19 @@ There are a few functions specific to Python functions.
|
||||||
runtime behavior depending on optimization decisions, it does not change
|
runtime behavior depending on optimization decisions, it does not change
|
||||||
the semantics of the Python code being executed.
|
the semantics of the Python code being executed.
|
||||||
|
|
||||||
If the callback returns with an exception set, it must return ``-1``; this
|
If *event* is ``PyFunction_EVENT_DESTROY``, Taking a reference in the
|
||||||
exception will be printed as an unraisable exception using
|
callback to the about-to-be-destroyed function will resurrect it, preventing
|
||||||
:c:func:`PyErr_WriteUnraisable`. Otherwise it should return ``0``.
|
it from being freed at this time. When the resurrected object is destroyed
|
||||||
|
later, any watcher callbacks active at that time will be called again.
|
||||||
|
|
||||||
|
If the callback sets an exception, it must return ``-1``; this exception will
|
||||||
|
be printed as an unraisable exception using :c:func:`PyErr_WriteUnraisable`.
|
||||||
|
Otherwise it should return ``0``.
|
||||||
|
|
||||||
|
There may already be a pending exception set on entry to the callback. In
|
||||||
|
this case, the callback should return ``0`` with the same exception still
|
||||||
|
set. This means the callback may not call any other API that can set an
|
||||||
|
exception unless it saves and clears the exception state first, and restores
|
||||||
|
it before returning.
|
||||||
|
|
||||||
.. versionadded:: 3.12
|
.. versionadded:: 3.12
|
||||||
|
|
|
@ -224,9 +224,14 @@ PyAPI_FUNC(int) PyCode_Addr2Line(PyCodeObject *, int);
|
||||||
|
|
||||||
PyAPI_FUNC(int) PyCode_Addr2Location(PyCodeObject *, int, int *, int *, int *, int *);
|
PyAPI_FUNC(int) PyCode_Addr2Location(PyCodeObject *, int, int *, int *, int *, int *);
|
||||||
|
|
||||||
typedef enum PyCodeEvent {
|
#define PY_FOREACH_CODE_EVENT(V) \
|
||||||
PY_CODE_EVENT_CREATE,
|
V(CREATE) \
|
||||||
PY_CODE_EVENT_DESTROY
|
V(DESTROY)
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
#define PY_DEF_EVENT(op) PY_CODE_EVENT_##op,
|
||||||
|
PY_FOREACH_CODE_EVENT(PY_DEF_EVENT)
|
||||||
|
#undef PY_DEF_EVENT
|
||||||
} PyCodeEvent;
|
} PyCodeEvent;
|
||||||
|
|
||||||
|
|
||||||
|
@ -236,7 +241,7 @@ typedef enum PyCodeEvent {
|
||||||
* The callback is invoked with a borrowed reference to co, after it is
|
* The callback is invoked with a borrowed reference to co, after it is
|
||||||
* created and before it is destroyed.
|
* created and before it is destroyed.
|
||||||
*
|
*
|
||||||
* If the callback returns with an exception set, it must return -1. Otherwise
|
* If the callback sets an exception, it must return -1. Otherwise
|
||||||
* it should return 0.
|
* it should return 0.
|
||||||
*/
|
*/
|
||||||
typedef int (*PyCode_WatchCallback)(
|
typedef int (*PyCode_WatchCallback)(
|
||||||
|
|
|
@ -16,11 +16,11 @@ typedef struct {
|
||||||
|
|
||||||
/* Dictionary version: globally unique, value change each time
|
/* Dictionary version: globally unique, value change each time
|
||||||
the dictionary is modified */
|
the dictionary is modified */
|
||||||
#ifdef Py_BUILD_CORE
|
#ifdef Py_BUILD_CORE
|
||||||
uint64_t ma_version_tag;
|
uint64_t ma_version_tag;
|
||||||
#else
|
#else
|
||||||
Py_DEPRECATED(3.12) uint64_t ma_version_tag;
|
Py_DEPRECATED(3.12) uint64_t ma_version_tag;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
PyDictKeysObject *ma_keys;
|
PyDictKeysObject *ma_keys;
|
||||||
|
|
||||||
|
@ -90,13 +90,18 @@ PyAPI_FUNC(PyObject *) _PyDictView_Intersect(PyObject* self, PyObject *other);
|
||||||
|
|
||||||
/* Dictionary watchers */
|
/* Dictionary watchers */
|
||||||
|
|
||||||
|
#define PY_FOREACH_DICT_EVENT(V) \
|
||||||
|
V(ADDED) \
|
||||||
|
V(MODIFIED) \
|
||||||
|
V(DELETED) \
|
||||||
|
V(CLONED) \
|
||||||
|
V(CLEARED) \
|
||||||
|
V(DEALLOCATED)
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
PyDict_EVENT_ADDED,
|
#define PY_DEF_EVENT(EVENT) PyDict_EVENT_##EVENT,
|
||||||
PyDict_EVENT_MODIFIED,
|
PY_FOREACH_DICT_EVENT(PY_DEF_EVENT)
|
||||||
PyDict_EVENT_DELETED,
|
#undef PY_DEF_EVENT
|
||||||
PyDict_EVENT_CLONED,
|
|
||||||
PyDict_EVENT_CLEARED,
|
|
||||||
PyDict_EVENT_DEALLOCATED,
|
|
||||||
} PyDict_WatchEvent;
|
} PyDict_WatchEvent;
|
||||||
|
|
||||||
// Callback to be invoked when a watched dict is cleared, dealloced, or modified.
|
// Callback to be invoked when a watched dict is cleared, dealloced, or modified.
|
||||||
|
|
|
@ -131,17 +131,17 @@ PyAPI_DATA(PyTypeObject) PyStaticMethod_Type;
|
||||||
PyAPI_FUNC(PyObject *) PyClassMethod_New(PyObject *);
|
PyAPI_FUNC(PyObject *) PyClassMethod_New(PyObject *);
|
||||||
PyAPI_FUNC(PyObject *) PyStaticMethod_New(PyObject *);
|
PyAPI_FUNC(PyObject *) PyStaticMethod_New(PyObject *);
|
||||||
|
|
||||||
#define FOREACH_FUNC_EVENT(V) \
|
#define PY_FOREACH_FUNC_EVENT(V) \
|
||||||
V(CREATE) \
|
V(CREATE) \
|
||||||
V(DESTROY) \
|
V(DESTROY) \
|
||||||
V(MODIFY_CODE) \
|
V(MODIFY_CODE) \
|
||||||
V(MODIFY_DEFAULTS) \
|
V(MODIFY_DEFAULTS) \
|
||||||
V(MODIFY_KWDEFAULTS)
|
V(MODIFY_KWDEFAULTS)
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
#define DEF_EVENT(EVENT) PyFunction_EVENT_##EVENT,
|
#define PY_DEF_EVENT(EVENT) PyFunction_EVENT_##EVENT,
|
||||||
FOREACH_FUNC_EVENT(DEF_EVENT)
|
PY_FOREACH_FUNC_EVENT(PY_DEF_EVENT)
|
||||||
#undef DEF_EVENT
|
#undef PY_DEF_EVENT
|
||||||
} PyFunction_WatchEvent;
|
} PyFunction_WatchEvent;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -164,6 +164,7 @@ _PyDict_NotifyEvent(PyDict_WatchEvent event,
|
||||||
PyObject *key,
|
PyObject *key,
|
||||||
PyObject *value)
|
PyObject *value)
|
||||||
{
|
{
|
||||||
|
assert(Py_REFCNT((PyObject*)mp) > 0);
|
||||||
int watcher_bits = mp->ma_version_tag & DICT_VERSION_MASK;
|
int watcher_bits = mp->ma_version_tag & DICT_VERSION_MASK;
|
||||||
if (watcher_bits) {
|
if (watcher_bits) {
|
||||||
_PyDict_SendEvent(watcher_bits, event, mp, key, value);
|
_PyDict_SendEvent(watcher_bits, event, mp, key, value);
|
||||||
|
|
|
@ -109,10 +109,21 @@ class TestDictWatchers(unittest.TestCase):
|
||||||
self.watch(wid, d)
|
self.watch(wid, d)
|
||||||
with catch_unraisable_exception() as cm:
|
with catch_unraisable_exception() as cm:
|
||||||
d["foo"] = "bar"
|
d["foo"] = "bar"
|
||||||
self.assertIs(cm.unraisable.object, d)
|
self.assertIn(
|
||||||
|
"PyDict_EVENT_ADDED watcher callback for <dict at",
|
||||||
|
cm.unraisable.object
|
||||||
|
)
|
||||||
self.assertEqual(str(cm.unraisable.exc_value), "boom!")
|
self.assertEqual(str(cm.unraisable.exc_value), "boom!")
|
||||||
self.assert_events([])
|
self.assert_events([])
|
||||||
|
|
||||||
|
def test_dealloc_error(self):
|
||||||
|
d = {}
|
||||||
|
with self.watcher(kind=self.ERROR) as wid:
|
||||||
|
self.watch(wid, d)
|
||||||
|
with catch_unraisable_exception() as cm:
|
||||||
|
del d
|
||||||
|
self.assertEqual(str(cm.unraisable.exc_value), "boom!")
|
||||||
|
|
||||||
def test_two_watchers(self):
|
def test_two_watchers(self):
|
||||||
d1 = {}
|
d1 = {}
|
||||||
d2 = {}
|
d2 = {}
|
||||||
|
@ -389,6 +400,25 @@ class TestCodeObjectWatchers(unittest.TestCase):
|
||||||
del co4
|
del co4
|
||||||
self.assert_event_counts(0, 0, 0, 0)
|
self.assert_event_counts(0, 0, 0, 0)
|
||||||
|
|
||||||
|
def test_error(self):
|
||||||
|
with self.code_watcher(2):
|
||||||
|
with catch_unraisable_exception() as cm:
|
||||||
|
co = _testcapi.code_newempty("test_watchers", "dummy0", 0)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
cm.unraisable.object,
|
||||||
|
f"PY_CODE_EVENT_CREATE watcher callback for {co!r}"
|
||||||
|
)
|
||||||
|
self.assertEqual(str(cm.unraisable.exc_value), "boom!")
|
||||||
|
|
||||||
|
def test_dealloc_error(self):
|
||||||
|
co = _testcapi.code_newempty("test_watchers", "dummy0", 0)
|
||||||
|
with self.code_watcher(2):
|
||||||
|
with catch_unraisable_exception() as cm:
|
||||||
|
del co
|
||||||
|
|
||||||
|
self.assertEqual(str(cm.unraisable.exc_value), "boom!")
|
||||||
|
|
||||||
def test_clear_out_of_range_watcher_id(self):
|
def test_clear_out_of_range_watcher_id(self):
|
||||||
with self.assertRaisesRegex(ValueError, r"Invalid code watcher ID -1"):
|
with self.assertRaisesRegex(ValueError, r"Invalid code watcher ID -1"):
|
||||||
_testcapi.clear_code_watcher(-1)
|
_testcapi.clear_code_watcher(-1)
|
||||||
|
@ -479,7 +509,25 @@ class TestFuncWatchers(unittest.TestCase):
|
||||||
def myfunc():
|
def myfunc():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.assertIs(cm.unraisable.object, myfunc)
|
self.assertEqual(
|
||||||
|
cm.unraisable.object,
|
||||||
|
f"PyFunction_EVENT_CREATE watcher callback for {myfunc!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_dealloc_watcher_raises_error(self):
|
||||||
|
class MyError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def watcher(*args):
|
||||||
|
raise MyError("testing 123")
|
||||||
|
|
||||||
|
def myfunc():
|
||||||
|
pass
|
||||||
|
|
||||||
|
with self.add_watcher(watcher):
|
||||||
|
with catch_unraisable_exception() as cm:
|
||||||
|
del myfunc
|
||||||
|
|
||||||
self.assertIsInstance(cm.unraisable.exc_value, MyError)
|
self.assertIsInstance(cm.unraisable.exc_value, MyError)
|
||||||
|
|
||||||
def test_clear_out_of_range_watcher_id(self):
|
def test_clear_out_of_range_watcher_id(self):
|
||||||
|
|
|
@ -317,6 +317,13 @@ noop_code_event_handler(PyCodeEvent event, PyCodeObject *co)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
error_code_event_handler(PyCodeEvent event, PyCodeObject *co)
|
||||||
|
{
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "boom!");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
add_code_watcher(PyObject *self, PyObject *which_watcher)
|
add_code_watcher(PyObject *self, PyObject *which_watcher)
|
||||||
{
|
{
|
||||||
|
@ -333,7 +340,11 @@ add_code_watcher(PyObject *self, PyObject *which_watcher)
|
||||||
num_code_object_created_events[1] = 0;
|
num_code_object_created_events[1] = 0;
|
||||||
num_code_object_destroyed_events[1] = 0;
|
num_code_object_destroyed_events[1] = 0;
|
||||||
}
|
}
|
||||||
|
else if (which_l == 2) {
|
||||||
|
watcher_id = PyCode_AddWatcher(error_code_event_handler);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
|
PyErr_Format(PyExc_ValueError, "invalid watcher %d", which_l);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (watcher_id < 0) {
|
if (watcher_id < 0) {
|
||||||
|
@ -672,7 +683,7 @@ _PyTestCapi_Init_Watchers(PyObject *mod)
|
||||||
PyFunction_EVENT_##event)) { \
|
PyFunction_EVENT_##event)) { \
|
||||||
return -1; \
|
return -1; \
|
||||||
}
|
}
|
||||||
FOREACH_FUNC_EVENT(ADD_EVENT);
|
PY_FOREACH_FUNC_EVENT(ADD_EVENT);
|
||||||
#undef ADD_EVENT
|
#undef ADD_EVENT
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -11,9 +11,24 @@
|
||||||
#include "pycore_tuple.h" // _PyTuple_ITEMS()
|
#include "pycore_tuple.h" // _PyTuple_ITEMS()
|
||||||
#include "clinic/codeobject.c.h"
|
#include "clinic/codeobject.c.h"
|
||||||
|
|
||||||
|
static PyObject* code_repr(PyCodeObject *co);
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
code_event_name(PyCodeEvent event) {
|
||||||
|
switch (event) {
|
||||||
|
#define CASE(op) \
|
||||||
|
case PY_CODE_EVENT_##op: \
|
||||||
|
return "PY_CODE_EVENT_" #op;
|
||||||
|
PY_FOREACH_CODE_EVENT(CASE)
|
||||||
|
#undef CASE
|
||||||
|
}
|
||||||
|
Py_UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
notify_code_watchers(PyCodeEvent event, PyCodeObject *co)
|
notify_code_watchers(PyCodeEvent event, PyCodeObject *co)
|
||||||
{
|
{
|
||||||
|
assert(Py_REFCNT(co) > 0);
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
assert(interp->_initialized);
|
assert(interp->_initialized);
|
||||||
uint8_t bits = interp->active_code_watchers;
|
uint8_t bits = interp->active_code_watchers;
|
||||||
|
@ -25,7 +40,21 @@ notify_code_watchers(PyCodeEvent event, PyCodeObject *co)
|
||||||
// callback must be non-null if the watcher bit is set
|
// callback must be non-null if the watcher bit is set
|
||||||
assert(cb != NULL);
|
assert(cb != NULL);
|
||||||
if (cb(event, co) < 0) {
|
if (cb(event, co) < 0) {
|
||||||
PyErr_WriteUnraisable((PyObject *) co);
|
// Don't risk resurrecting the object if an unraisablehook keeps
|
||||||
|
// a reference; pass a string as context.
|
||||||
|
PyObject *context = NULL;
|
||||||
|
PyObject *repr = code_repr(co);
|
||||||
|
if (repr) {
|
||||||
|
context = PyUnicode_FromFormat(
|
||||||
|
"%s watcher callback for %U",
|
||||||
|
code_event_name(event), repr);
|
||||||
|
Py_DECREF(repr);
|
||||||
|
}
|
||||||
|
if (context == NULL) {
|
||||||
|
context = Py_NewRef(Py_None);
|
||||||
|
}
|
||||||
|
PyErr_WriteUnraisable(context);
|
||||||
|
Py_DECREF(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
|
@ -1667,7 +1696,14 @@ code_new_impl(PyTypeObject *type, int argcount, int posonlyargcount,
|
||||||
static void
|
static void
|
||||||
code_dealloc(PyCodeObject *co)
|
code_dealloc(PyCodeObject *co)
|
||||||
{
|
{
|
||||||
|
assert(Py_REFCNT(co) == 0);
|
||||||
|
Py_SET_REFCNT(co, 1);
|
||||||
notify_code_watchers(PY_CODE_EVENT_DESTROY, co);
|
notify_code_watchers(PY_CODE_EVENT_DESTROY, co);
|
||||||
|
if (Py_REFCNT(co) > 1) {
|
||||||
|
Py_SET_REFCNT(co, Py_REFCNT(co) - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Py_SET_REFCNT(co, 0);
|
||||||
|
|
||||||
if (co->co_extra != NULL) {
|
if (co->co_extra != NULL) {
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
|
|
@ -2308,7 +2308,14 @@ Fail:
|
||||||
static void
|
static void
|
||||||
dict_dealloc(PyDictObject *mp)
|
dict_dealloc(PyDictObject *mp)
|
||||||
{
|
{
|
||||||
|
assert(Py_REFCNT(mp) == 0);
|
||||||
|
Py_SET_REFCNT(mp, 1);
|
||||||
_PyDict_NotifyEvent(PyDict_EVENT_DEALLOCATED, mp, NULL, NULL);
|
_PyDict_NotifyEvent(PyDict_EVENT_DEALLOCATED, mp, NULL, NULL);
|
||||||
|
if (Py_REFCNT(mp) > 1) {
|
||||||
|
Py_SET_REFCNT(mp, Py_REFCNT(mp) - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Py_SET_REFCNT(mp, 0);
|
||||||
PyDictValues *values = mp->ma_values;
|
PyDictValues *values = mp->ma_values;
|
||||||
PyDictKeysObject *keys = mp->ma_keys;
|
PyDictKeysObject *keys = mp->ma_keys;
|
||||||
Py_ssize_t i, n;
|
Py_ssize_t i, n;
|
||||||
|
@ -5732,6 +5739,18 @@ PyDict_ClearWatcher(int watcher_id)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
dict_event_name(PyDict_WatchEvent event) {
|
||||||
|
switch (event) {
|
||||||
|
#define CASE(op) \
|
||||||
|
case PyDict_EVENT_##op: \
|
||||||
|
return "PyDict_EVENT_" #op;
|
||||||
|
PY_FOREACH_DICT_EVENT(CASE)
|
||||||
|
#undef CASE
|
||||||
|
}
|
||||||
|
Py_UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
_PyDict_SendEvent(int watcher_bits,
|
_PyDict_SendEvent(int watcher_bits,
|
||||||
PyDict_WatchEvent event,
|
PyDict_WatchEvent event,
|
||||||
|
@ -5744,9 +5763,18 @@ _PyDict_SendEvent(int watcher_bits,
|
||||||
if (watcher_bits & 1) {
|
if (watcher_bits & 1) {
|
||||||
PyDict_WatchCallback cb = interp->dict_state.watchers[i];
|
PyDict_WatchCallback cb = interp->dict_state.watchers[i];
|
||||||
if (cb && (cb(event, (PyObject*)mp, key, value) < 0)) {
|
if (cb && (cb(event, (PyObject*)mp, key, value) < 0)) {
|
||||||
// some dict modification paths (e.g. PyDict_Clear) can't raise, so we
|
// We don't want to resurrect the dict by potentially having an
|
||||||
// can't propagate exceptions from dict watchers.
|
// unraisablehook keep a reference to it, so we don't pass the
|
||||||
PyErr_WriteUnraisable((PyObject *)mp);
|
// dict as context, just an informative string message. Dict
|
||||||
|
// repr can call arbitrary code, so we invent a simpler version.
|
||||||
|
PyObject *context = PyUnicode_FromFormat(
|
||||||
|
"%s watcher callback for <dict at %p>",
|
||||||
|
dict_event_name(event), mp);
|
||||||
|
if (context == NULL) {
|
||||||
|
context = Py_NewRef(Py_None);
|
||||||
|
}
|
||||||
|
PyErr_WriteUnraisable(context);
|
||||||
|
Py_DECREF(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
watcher_bits >>= 1;
|
watcher_bits >>= 1;
|
||||||
|
|
|
@ -8,6 +8,20 @@
|
||||||
#include "pycore_pyerrors.h" // _PyErr_Occurred()
|
#include "pycore_pyerrors.h" // _PyErr_Occurred()
|
||||||
#include "structmember.h" // PyMemberDef
|
#include "structmember.h" // PyMemberDef
|
||||||
|
|
||||||
|
static PyObject* func_repr(PyFunctionObject *op);
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
func_event_name(PyFunction_WatchEvent event) {
|
||||||
|
switch (event) {
|
||||||
|
#define CASE(op) \
|
||||||
|
case PyFunction_EVENT_##op: \
|
||||||
|
return "PyFunction_EVENT_" #op;
|
||||||
|
PY_FOREACH_FUNC_EVENT(CASE)
|
||||||
|
#undef CASE
|
||||||
|
}
|
||||||
|
Py_UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
notify_func_watchers(PyInterpreterState *interp, PyFunction_WatchEvent event,
|
notify_func_watchers(PyInterpreterState *interp, PyFunction_WatchEvent event,
|
||||||
PyFunctionObject *func, PyObject *new_value)
|
PyFunctionObject *func, PyObject *new_value)
|
||||||
|
@ -21,7 +35,21 @@ notify_func_watchers(PyInterpreterState *interp, PyFunction_WatchEvent event,
|
||||||
// callback must be non-null if the watcher bit is set
|
// callback must be non-null if the watcher bit is set
|
||||||
assert(cb != NULL);
|
assert(cb != NULL);
|
||||||
if (cb(event, func, new_value) < 0) {
|
if (cb(event, func, new_value) < 0) {
|
||||||
PyErr_WriteUnraisable((PyObject *) func);
|
// Don't risk resurrecting the func if an unraisablehook keeps a
|
||||||
|
// reference; pass a string as context.
|
||||||
|
PyObject *context = NULL;
|
||||||
|
PyObject *repr = func_repr(func);
|
||||||
|
if (repr != NULL) {
|
||||||
|
context = PyUnicode_FromFormat(
|
||||||
|
"%s watcher callback for %U",
|
||||||
|
func_event_name(event), repr);
|
||||||
|
Py_DECREF(repr);
|
||||||
|
}
|
||||||
|
if (context == NULL) {
|
||||||
|
context = Py_NewRef(Py_None);
|
||||||
|
}
|
||||||
|
PyErr_WriteUnraisable(context);
|
||||||
|
Py_DECREF(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
|
@ -33,6 +61,7 @@ static inline void
|
||||||
handle_func_event(PyFunction_WatchEvent event, PyFunctionObject *func,
|
handle_func_event(PyFunction_WatchEvent event, PyFunctionObject *func,
|
||||||
PyObject *new_value)
|
PyObject *new_value)
|
||||||
{
|
{
|
||||||
|
assert(Py_REFCNT(func) > 0);
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
assert(interp->_initialized);
|
assert(interp->_initialized);
|
||||||
if (interp->active_func_watchers) {
|
if (interp->active_func_watchers) {
|
||||||
|
@ -766,7 +795,14 @@ func_clear(PyFunctionObject *op)
|
||||||
static void
|
static void
|
||||||
func_dealloc(PyFunctionObject *op)
|
func_dealloc(PyFunctionObject *op)
|
||||||
{
|
{
|
||||||
|
assert(Py_REFCNT(op) == 0);
|
||||||
|
Py_SET_REFCNT(op, 1);
|
||||||
handle_func_event(PyFunction_EVENT_DESTROY, op, NULL);
|
handle_func_event(PyFunction_EVENT_DESTROY, op, NULL);
|
||||||
|
if (Py_REFCNT(op) > 1) {
|
||||||
|
Py_SET_REFCNT(op, Py_REFCNT(op) - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Py_SET_REFCNT(op, 0);
|
||||||
_PyObject_GC_UNTRACK(op);
|
_PyObject_GC_UNTRACK(op);
|
||||||
if (op->func_weakreflist != NULL) {
|
if (op->func_weakreflist != NULL) {
|
||||||
PyObject_ClearWeakRefs((PyObject *) op);
|
PyObject_ClearWeakRefs((PyObject *) op);
|
||||||
|
|
Loading…
Reference in New Issue