Skip to content

Commit 6a5c49b

Browse files
committed
gh-152666: Avoid reference counting of code objects
1 parent 3428959 commit 6a5c49b

5 files changed

Lines changed: 57 additions & 3 deletions

File tree

Include/cpython/funcobject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ typedef struct {
4343
PyObject *func_annotations; /* Annotations, a dict or NULL */
4444
PyObject *func_annotate; /* Callable to fill the annotations dictionary */
4545
PyObject *func_typeparams; /* Tuple of active type variables or NULL */
46+
PyObject *func_old_codes;
4647
vectorcallfunc vectorcall;
4748
/* Version number for use by specializer.
4849
* Can set to non-zero when we want to specialize.

Include/internal/pycore_interpframe.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ _PyFrame_NumSlotsForCodeObject(PyCodeObject *code)
132132

133133
static inline void _PyFrame_Copy(_PyInterpreterFrame *src, _PyInterpreterFrame *dest)
134134
{
135-
dest->f_executable = PyStackRef_MakeHeapSafe(src->f_executable);
135+
dest->f_executable = PyStackRef_Borrow(src->f_executable);
136136
// Don't leave a dangling pointer to the old frame when creating generators
137137
// and coroutines:
138138
dest->previous = NULL;
@@ -191,7 +191,7 @@ _PyFrame_Initialize(
191191
{
192192
frame->previous = previous;
193193
frame->f_funcobj = func;
194-
frame->f_executable = PyStackRef_FromPyObjectNew(code);
194+
frame->f_executable = PyStackRef_FromPyObjectBorrow((PyObject *)code);
195195
PyFunctionObject *func_obj = (PyFunctionObject *)PyStackRef_AsPyObjectBorrow(func);
196196
frame->f_builtins = func_obj->func_builtins;
197197
frame->f_globals = func_obj->func_globals;
@@ -424,7 +424,7 @@ _PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int
424424
assert(tstate->datastack_top < tstate->datastack_limit);
425425
frame->previous = previous;
426426
frame->f_funcobj = PyStackRef_None;
427-
frame->f_executable = PyStackRef_FromPyObjectNew(code);
427+
frame->f_executable = PyStackRef_FromPyObjectBorrow((PyObject *)code);
428428
#ifdef Py_DEBUG
429429
frame->f_builtins = NULL;
430430
frame->f_globals = NULL;

Lib/test/test_capi/test_function.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,28 @@ def annofn(arg: int) -> str:
325325
with self.assertRaises(SystemError):
326326
_testcapi.function_get_annotations(None)
327327

328+
def test_function_old_codes(self):
329+
def f():
330+
pass
331+
332+
def g():
333+
pass
334+
335+
def h():
336+
pass
337+
338+
old_codes = _testcapi.function_get_old_codes(f)
339+
self.assertIsNone(old_codes)
340+
341+
f.__code__ = g.__code__
342+
old_codes = _testcapi.function_get_old_codes(f)
343+
self.assertIsInstance(old_codes, list)
344+
self.assertEqual(len(old_codes), 1)
345+
346+
f.__code__ = h.__code__
347+
old_codes = _testcapi.function_get_old_codes(f)
348+
self.assertEqual(len(old_codes), 2)
349+
328350
# TODO: test PyFunction_New()
329351
# TODO: test PyFunction_NewWithQualName()
330352
# TODO: test PyFunction_SetVectorcall()

Modules/_testcapi/function.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,19 @@ function_get_annotations(PyObject *self, PyObject *func)
130130
}
131131

132132

133+
static PyObject *
134+
function_get_old_codes(PyObject *self, PyObject *func)
135+
{
136+
PyFunctionObject *func_o = (PyFunctionObject *) func;
137+
PyObject *old_codes = func_o->func_old_codes;
138+
if (old_codes == NULL) {
139+
Py_RETURN_NONE;
140+
}
141+
142+
return Py_NewRef(old_codes);
143+
}
144+
145+
133146
static PyMethodDef test_methods[] = {
134147
{"function_get_code", function_get_code, METH_O, NULL},
135148
{"function_get_globals", function_get_globals, METH_O, NULL},
@@ -141,6 +154,7 @@ static PyMethodDef test_methods[] = {
141154
{"function_get_closure", function_get_closure, METH_O, NULL},
142155
{"function_set_closure", function_set_closure, METH_VARARGS, NULL},
143156
{"function_get_annotations", function_get_annotations, METH_O, NULL},
157+
{"function_get_old_codes", function_get_old_codes, METH_O, NULL},
144158
{NULL},
145159
};
146160

Objects/funcobject.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ _PyFunction_FromConstructor(PyFrameConstructor *constr)
145145
op->func_typeparams = NULL;
146146
op->vectorcall = _PyFunction_Vectorcall;
147147
op->func_version = FUNC_VERSION_UNSET;
148+
op->func_old_codes = NULL;
148149
// NOTE: functions created via FrameConstructor do not use deferred
149150
// reference counting because they are typically not part of cycles
150151
// nor accessed by multiple threads.
@@ -223,6 +224,7 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname
223224
op->func_typeparams = NULL;
224225
op->vectorcall = _PyFunction_Vectorcall;
225226
op->func_version = FUNC_VERSION_UNSET;
227+
op->func_old_codes = NULL;
226228
if (((code_obj->co_flags & CO_NESTED) == 0) ||
227229
(code_obj->co_flags & CO_METHOD)) {
228230
// Use deferred reference counting for top-level functions, but not
@@ -686,6 +688,19 @@ func_set_code(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
686688

687689
handle_func_event(PyFunction_EVENT_MODIFY_CODE, op, value);
688690
_PyFunction_ClearVersion(op);
691+
if (op->func_old_codes == NULL) {
692+
op->func_old_codes = PyList_New(0);
693+
if (op->func_old_codes == NULL) {
694+
PyErr_NoMemory();
695+
return -1;
696+
}
697+
}
698+
699+
if (PyList_Append(op->func_old_codes, op->func_code) < 0) {
700+
PyErr_NoMemory();
701+
return -1;
702+
}
703+
689704
Py_XSETREF(op->func_code, Py_NewRef(value));
690705
return 0;
691706
}
@@ -1114,6 +1129,7 @@ func_clear(PyObject *self)
11141129
Py_CLEAR(op->func_annotations);
11151130
Py_CLEAR(op->func_annotate);
11161131
Py_CLEAR(op->func_typeparams);
1132+
Py_CLEAR(op->func_old_codes);
11171133
// Don't Py_CLEAR(op->func_code), since code is always required
11181134
// to be non-NULL. Similarly, name and qualname shouldn't be NULL.
11191135
// However, name and qualname could be str subclasses, so they
@@ -1169,6 +1185,7 @@ func_traverse(PyObject *self, visitproc visit, void *arg)
11691185
Py_VISIT(f->func_annotate);
11701186
Py_VISIT(f->func_typeparams);
11711187
Py_VISIT(f->func_qualname);
1188+
Py_VISIT(f->func_old_codes);
11721189
return 0;
11731190
}
11741191

0 commit comments

Comments
 (0)