Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/cpython/funcobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ typedef struct {
PyObject *func_annotations; /* Annotations, a dict or NULL */
PyObject *func_annotate; /* Callable to fill the annotations dictionary */
PyObject *func_typeparams; /* Tuple of active type variables or NULL */
PyObject *func_old_codes;
vectorcallfunc vectorcall;
/* Version number for use by specializer.
* Can set to non-zero when we want to specialize.
Expand Down
6 changes: 3 additions & 3 deletions Include/internal/pycore_interpframe.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ _PyFrame_NumSlotsForCodeObject(PyCodeObject *code)

static inline void _PyFrame_Copy(_PyInterpreterFrame *src, _PyInterpreterFrame *dest)
{
dest->f_executable = PyStackRef_MakeHeapSafe(src->f_executable);
dest->f_executable = PyStackRef_Borrow(src->f_executable);
// Don't leave a dangling pointer to the old frame when creating generators
// and coroutines:
dest->previous = NULL;
Expand Down Expand Up @@ -191,7 +191,7 @@ _PyFrame_Initialize(
{
frame->previous = previous;
frame->f_funcobj = func;
frame->f_executable = PyStackRef_FromPyObjectNew(code);
frame->f_executable = PyStackRef_FromPyObjectBorrow((PyObject *)code);
PyFunctionObject *func_obj = (PyFunctionObject *)PyStackRef_AsPyObjectBorrow(func);
frame->f_builtins = func_obj->func_builtins;
frame->f_globals = func_obj->func_globals;
Expand Down Expand Up @@ -424,7 +424,7 @@ _PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int
assert(tstate->datastack_top < tstate->datastack_limit);
frame->previous = previous;
frame->f_funcobj = PyStackRef_None;
frame->f_executable = PyStackRef_FromPyObjectNew(code);
frame->f_executable = PyStackRef_FromPyObjectBorrow((PyObject *)code);
#ifdef Py_DEBUG
frame->f_builtins = NULL;
frame->f_globals = NULL;
Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_capi/test_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,28 @@ def annofn(arg: int) -> str:
with self.assertRaises(SystemError):
_testcapi.function_get_annotations(None)

def test_function_old_codes(self):
def f():
pass

def g():
pass

def h():
pass

old_codes = _testcapi.function_get_old_codes(f)
self.assertIsNone(old_codes)

f.__code__ = g.__code__
old_codes = _testcapi.function_get_old_codes(f)
self.assertIsInstance(old_codes, list)
self.assertEqual(len(old_codes), 1)

f.__code__ = h.__code__
old_codes = _testcapi.function_get_old_codes(f)
self.assertEqual(len(old_codes), 2)

# TODO: test PyFunction_New()
# TODO: test PyFunction_NewWithQualName()
# TODO: test PyFunction_SetVectorcall()
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1703,7 +1703,7 @@ def func():
check(x, size('3PiccPPP' + INTERPRETER_FRAME + 'P'))
# function
def func(): pass
check(func, size('16Pi'))
check(func, size('17Pi'))
class c():
@staticmethod
def foo():
Expand Down
14 changes: 14 additions & 0 deletions Modules/_testcapi/function.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,19 @@ function_get_annotations(PyObject *self, PyObject *func)
}


static PyObject *
function_get_old_codes(PyObject *self, PyObject *func)
{
PyFunctionObject *func_o = (PyFunctionObject *) func;
PyObject *old_codes = func_o->func_old_codes;
if (old_codes == NULL) {
Py_RETURN_NONE;
}

return Py_NewRef(old_codes);
}


static PyMethodDef test_methods[] = {
{"function_get_code", function_get_code, METH_O, NULL},
{"function_get_globals", function_get_globals, METH_O, NULL},
Expand All @@ -141,6 +154,7 @@ static PyMethodDef test_methods[] = {
{"function_get_closure", function_get_closure, METH_O, NULL},
{"function_set_closure", function_set_closure, METH_VARARGS, NULL},
{"function_get_annotations", function_get_annotations, METH_O, NULL},
{"function_get_old_codes", function_get_old_codes, METH_O, NULL},
{NULL},
};

Expand Down
15 changes: 15 additions & 0 deletions Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ _PyFunction_FromConstructor(PyFrameConstructor *constr)
op->func_typeparams = NULL;
op->vectorcall = _PyFunction_Vectorcall;
op->func_version = FUNC_VERSION_UNSET;
op->func_old_codes = NULL;
// NOTE: functions created via FrameConstructor do not use deferred
// reference counting because they are typically not part of cycles
// nor accessed by multiple threads.
Expand Down Expand Up @@ -223,6 +224,7 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname
op->func_typeparams = NULL;
op->vectorcall = _PyFunction_Vectorcall;
op->func_version = FUNC_VERSION_UNSET;
op->func_old_codes = NULL;
if (((code_obj->co_flags & CO_NESTED) == 0) ||
(code_obj->co_flags & CO_METHOD)) {
// Use deferred reference counting for top-level functions, but not
Expand Down Expand Up @@ -686,6 +688,17 @@ func_set_code(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))

handle_func_event(PyFunction_EVENT_MODIFY_CODE, op, value);
_PyFunction_ClearVersion(op);
if (op->func_old_codes == NULL) {
op->func_old_codes = PyList_New(0);
if (op->func_old_codes == NULL) {
return -1;
}
}

if (PyList_Append(op->func_old_codes, op->func_code) < 0) {
return -1;
}

Py_XSETREF(op->func_code, Py_NewRef(value));
return 0;
}
Expand Down Expand Up @@ -1114,6 +1127,7 @@ func_clear(PyObject *self)
Py_CLEAR(op->func_annotations);
Py_CLEAR(op->func_annotate);
Py_CLEAR(op->func_typeparams);
Py_CLEAR(op->func_old_codes);
// Don't Py_CLEAR(op->func_code), since code is always required
// to be non-NULL. Similarly, name and qualname shouldn't be NULL.
// However, name and qualname could be str subclasses, so they
Expand Down Expand Up @@ -1169,6 +1183,7 @@ func_traverse(PyObject *self, visitproc visit, void *arg)
Py_VISIT(f->func_annotate);
Py_VISIT(f->func_typeparams);
Py_VISIT(f->func_qualname);
Py_VISIT(f->func_old_codes);
return 0;
}

Expand Down
5 changes: 0 additions & 5 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,6 @@ gen_traverse(PyObject *self, visitproc visit, void *arg)
return err;
}
}
else {
// We still need to visit the code object when the frame is cleared to
// ensure that it's kept alive if the reference is deferred.
_Py_VISIT_STACKREF(gen->gi_iframe.f_executable);
}
/* No need to visit cr_origin, because it's just tuples/str/int, so can't
participate in a reference cycle. */
Py_VISIT(gen->gi_exc_state.exc_value);
Expand Down
Loading