8000 gh-91054: Add API to allow extensions to set callback function on cre… · itamaro/cpython@2475ce4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2475ce4

Browse files
itamaroYe11ow-Flash
andcommitted
pythongh-91054: Add API to allow extensions to set callback function on creation and destruction of PyCodeObject
Co-authored-by: Ye11ow-Flash <janshah@cs.stonybrook.edu>
1 parent 7bae15c commit 2475ce4

File tree

5 files changed

+101
-0
lines changed

5 files changed

+101
-0
lines changed

Include/cpython/code.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,41 @@ PyAPI_FUNC(int) PyCode_Addr2Line(PyCodeObject *, int);
181181

182182
PyAPI_FUNC(int) PyCode_Addr2Location(PyCodeObject *, int, int *, int *, int *, int *);
183183

184+
typedef enum PyCodeEvent {
185+
PY_CODE_EVENT_CREATE,
186+
PY_CODE_EVENT_DESTROY
187+
} PyCodeEvent;
188+
189+
190+
/*
191+
* A callback that is invoked for different events in a code object's lifecycle.
192+
*
193+
* The callback is invoked with a borrowed reference to co, after it is
194+
* created and before it is destroyed.
195+
*
196+
* If the callback returns with an exception set, it must return -1. Otherwise
197+
* it should return 0.
198+
*/
199+
typedef int (*PyCode_WatchCallback)(
200+
PyCodeEvent event,
201+
PyCodeObject* co);
202+
203+
/*
204+
* Register a per-interpreter callback that will be invoked for code object
205+
* lifecycle events.
206+
*
207+
* Returns a handle that may be passed to PyCode_ClearWatcher on success,
208+
* or -1 and sets an error if no more handles are available.
209+
*/
210+
PyAPI_FUNC(int) PyCode_AddWatcher(PyCode_WatchCallback callback);
211+
212+
/*
213+
* Clear the watcher associated with the watcher_id handle.
214+
*
215+
* Returns 0 on success or -1 if no watcher exists for the provided id.
216+
*/
217+
PyAPI_FUNC(int) PyCode_ClearWatcher(int watcher_id);
218+
184219
/* for internal use only */
185220
struct _opaque {
186221
int computed_line;

Include/internal/pycore_code.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
extern "C" {
55
#endif
66

7+
#define CODE_MAX_WATCHERS 8
8+
79
/* PEP 659
810
* Specialization and quickening structs and helper functions
911
*/

Include/internal/pycore_interp.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ struct _is {
191191

192192
PyObject *audit_hooks;
193193
PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS];
194+
PyCode_WatchCallback code_watchers[CODE_MAX_WATCHERS];
194195

195196
struct _Py_unicode_state unicode;
196197
struct _Py_float_state float_state;

Objects/codeobject.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,62 @@
1212
#include "clinic/codeobject.c.h"
1313

1414

15+
static void
16+
notify_code_watchers(PyCodeEvent event, PyCodeObject *co)
17+
{
18+
PyInterpreterState *interp = _PyInterpreterState_GET();
19+
assert(interp->_initialized);
20+
for (int i = 0; i < CODE_MAX_WATCHERS; i++) {
21+
PyCode_WatchCallback cb = interp->code_watchers[i];
22+
if ((cb != NULL) && (cb(event, co) < 0)) {
23+
PyErr_WriteUnraisable((PyObject *) co);
24+
}
25+
}
26+
}
27+
28+
int
29+
PyCode_AddWatcher(PyCode_WatchCallback callback)
30+
{
31+
PyInterpreterState *interp = _PyInterpreterState_GET();
32+
assert(interp->_initialized);
33+
34+
for (int i = 0; i < CODE_MAX_WATCHERS; i++) {
35+
if (!interp->code_watchers[i]) {
36+
interp->code_watchers[i] = callback;
37+
return i;
38+
}
39+
}
40+
41+
PyErr_SetString(PyExc_RuntimeError, "no more code watcher IDs available");
42+
return -1;
43+
}
44+
45+
static inline int
46+
validate_watcher_id(PyInterpreterState *interp, int watcher_id)
47+
{
48+
if (watcher_id < 0 || watcher_id >= CODE_MAX_WATCHERS) {
49+
PyErr_Format(PyExc_ValueError, "Invalid code watcher ID %d", watcher_id);
50+
return -1;
51+
}
52+
if (!interp->code_watchers[watcher_id]) {
53+
PyErr_Format(PyExc_ValueError, "No code watcher set for ID %d", watcher_id);
54+
return -1;
55+
}
56+
return 0;
57+
}
58+
59+
int
60+
PyCode_ClearWatcher(int watcher_id)
61+
{
62+
PyInterpreterState *interp = _PyInterpreterState_GET();
63+
assert(interp->_initialized);
64+
if (validate_watcher_id(interp, watcher_id) < 0) {
65+
return -1;
66+
}
67+
interp->code_watchers[watcher_id] = NULL;
68+
return 0;
69+
}
70+
1571
/******************
1672
* generic helpers
1773
******************/
@@ -355,6 +411,7 @@ init_code(PyCodeObject *co, struct _PyCodeConstructor *con)
355411
}
356412
co->_co_firsttraceable = entry_point;
357413
_PyCode_Quicken(co);
414+
notify_code_watchers(PY_CODE_EVENT_CREATE, co);
358415
}
359416

360417
static int
@@ -1615,6 +1672,8 @@ code_new_impl(PyTypeObject *type, int argcount, int posonlyargcount,
16151672
static void
16161673
code_dealloc(PyCodeObject *co)
16171674
{
1675+
notify_code_watchers(PY_CODE_EVENT_DESTROY, co);
1676+
16181677
if (co->co_extra != NULL) {
16191678
PyInterpreterState *interp = _PyInterpreterState_GET();
16201679
_PyCodeObjectExtra *co_extra = co->co_extra;

Python/pystate.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,10 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
466466
}
467467
interp->active_func_watchers = 0;
468468

469+
for (int i=0; i < CODE_MAX_WATCHERS; i++) {
470+
interp->code_watchers[i] = NULL;
471+
}
472+
469473
// XXX Once we have one allocator per interpreter (i.e.
470474
// per-interpreter GC) we must ensure that all of the interpreter's
471475
// objects have been cleaned up at the point.

0 commit comments

Comments
 (0)
0