gh-91053: Add an optional callback that is invoked whenever a function is modified (#98175)

This commit is contained in:
mpage 2022-11-22 04:06:44 -08:00 committed by GitHub
parent 20d9749a0f
commit 3db0a21f73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 524 additions and 0 deletions

View file

@ -3,10 +3,68 @@
#include "Python.h"
#include "pycore_ceval.h" // _PyEval_BuiltinsFromGlobals()
#include "pycore_function.h" // FUNC_MAX_WATCHERS
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
#include "pycore_pyerrors.h" // _PyErr_Occurred()
#include "structmember.h" // PyMemberDef
static void
notify_func_watchers(PyInterpreterState *interp, PyFunction_WatchEvent event,
PyFunctionObject *func, PyObject *new_value)
{
for (int i = 0; i < FUNC_MAX_WATCHERS; i++) {
PyFunction_WatchCallback cb = interp->func_watchers[i];
if ((cb != NULL) && (cb(event, func, new_value) < 0)) {
PyErr_WriteUnraisable((PyObject *) func);
}
}
}
static inline void
handle_func_event(PyFunction_WatchEvent event, PyFunctionObject *func,
PyObject *new_value)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
if (interp->active_func_watchers) {
notify_func_watchers(interp, event, func, new_value);
}
}
int
PyFunction_AddWatcher(PyFunction_WatchCallback callback)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
assert(interp->_initialized);
for (int i = 0; i < FUNC_MAX_WATCHERS; i++) {
if (interp->func_watchers[i] == NULL) {
interp->func_watchers[i] = callback;
interp->active_func_watchers |= (1 << i);
return i;
}
}
PyErr_SetString(PyExc_RuntimeError, "no more func watcher IDs available");
return -1;
}
int
PyFunction_ClearWatcher(int watcher_id)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
if (watcher_id < 0 || watcher_id >= FUNC_MAX_WATCHERS) {
PyErr_Format(PyExc_ValueError, "invalid func watcher ID %d",
watcher_id);
return -1;
}
if (!interp->func_watchers[watcher_id]) {
PyErr_Format(PyExc_ValueError, "no func watcher set for ID %d",
watcher_id);
return -1;
}
interp->func_watchers[watcher_id] = NULL;
interp->active_func_watchers &= ~(1 << watcher_id);
return 0;
}
PyFunctionObject *
_PyFunction_FromConstructor(PyFrameConstructor *constr)
{
@ -31,6 +89,7 @@ _PyFunction_FromConstructor(PyFrameConstructor *constr)
op->vectorcall = _PyFunction_Vectorcall;
op->func_version = 0;
_PyObject_GC_TRACK(op);
handle_func_event(PyFunction_EVENT_CREATE, op, NULL);
return op;
}
@ -105,6 +164,7 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname
op->vectorcall = _PyFunction_Vectorcall;
op->func_version = 0;
_PyObject_GC_TRACK(op);
handle_func_event(PyFunction_EVENT_CREATE, op, NULL);
return (PyObject *)op;
error:
@ -196,6 +256,8 @@ PyFunction_SetDefaults(PyObject *op, PyObject *defaults)
PyErr_SetString(PyExc_SystemError, "non-tuple default args");
return -1;
}
handle_func_event(PyFunction_EVENT_MODIFY_DEFAULTS,
(PyFunctionObject *) op, defaults);
((PyFunctionObject *)op)->func_version = 0;
Py_XSETREF(((PyFunctionObject *)op)->func_defaults, defaults);
return 0;
@ -236,6 +298,8 @@ PyFunction_SetKwDefaults(PyObject *op, PyObject *defaults)
"non-dict keyword only default args");
return -1;
}
handle_func_event(PyFunction_EVENT_MODIFY_KWDEFAULTS,
(PyFunctionObject *) op, defaults);
((PyFunctionObject *)op)->func_version = 0;
Py_XSETREF(((PyFunctionObject *)op)->func_kwdefaults, defaults);
return 0;
@ -389,6 +453,7 @@ func_set_code(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignored))
nclosure, nfree);
return -1;
}
handle_func_event(PyFunction_EVENT_MODIFY_CODE, op, value);
op->func_version = 0;
Py_XSETREF(op->func_code, Py_NewRef(value));
return 0;
@ -468,6 +533,7 @@ func_set_defaults(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignored
return -1;
}
handle_func_event(PyFunction_EVENT_MODIFY_DEFAULTS, op, value);
op->func_version = 0;
Py_XSETREF(op->func_defaults, Py_XNewRef(value));
return 0;
@ -508,6 +574,7 @@ func_set_kwdefaults(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignor
return -1;
}
handle_func_event(PyFunction_EVENT_MODIFY_KWDEFAULTS, op, value);
op->func_version = 0;
Py_XSETREF(op->func_kwdefaults, Py_XNewRef(value));
return 0;
@ -687,6 +754,7 @@ func_clear(PyFunctionObject *op)
static void
func_dealloc(PyFunctionObject *op)
{
handle_func_event(PyFunction_EVENT_DESTROY, op, NULL);
_PyObject_GC_UNTRACK(op);
if (op->func_weakreflist != NULL) {
PyObject_ClearWeakRefs((PyObject *) op);