Skip to content
Open
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; /* List of past code objects or NULL */
vectorcallfunc vectorcall;
/* Version number for use by specializer.
* Can set to non-zero when we want to specialize.
Expand Down
20 changes: 11 additions & 9 deletions Include/internal/pycore_interpframe.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ extern "C" {
((int)((IF)->instr_ptr - _PyFrame_GetBytecode((IF))))

static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) {
assert(!PyStackRef_IsNull(f->f_executable));
PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable);
assert(PyCode_Check(executable));
return (PyCodeObject *)executable;
assert(f->f_executable != NULL);
assert(PyCode_Check(f->f_executable));
return (PyCodeObject *)f->f_executable;
}

// Similar to _PyFrame_GetCode(), but return NULL if the frame is invalid or
Expand All @@ -36,15 +35,15 @@ _PyFrame_SafeGetCode(_PyInterpreterFrame *f)
return NULL;
}

if (PyStackRef_IsNull(f->f_executable)) {
if (f->f_executable == NULL) {
return NULL;
}
void *ptr;
memcpy(&ptr, &f->f_executable, sizeof(f->f_executable));
if (_PyMem_IsPtrFreed(ptr)) {
return NULL;
}
PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable);
PyObject *executable = f->f_executable;
if (_PyObject_IsFreed(executable)) {
return NULL;
}
Expand Down Expand Up @@ -132,7 +131,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 = 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 +190,7 @@ _PyFrame_Initialize(
{
frame->previous = previous;
frame->f_funcobj = func;
frame->f_executable = PyStackRef_FromPyObjectNew(code);
frame->f_executable = (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 @@ -369,6 +368,9 @@ _PyFrame_ClearLocals(_PyInterpreterFrame *frame);
PyAPI_FUNC(void)
_PyFrame_ClearExceptCode(_PyInterpreterFrame * frame);

void
_PyFrame_ClearExceptCodeAndFunction(_PyInterpreterFrame *frame);

int
_PyFrame_Traverse(_PyInterpreterFrame *frame, visitproc visit, void *arg);

Expand Down Expand Up @@ -424,7 +426,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 = (PyObject *)code;
#ifdef Py_DEBUG
frame->f_builtins = NULL;
frame->f_globals = NULL;
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_interpframe_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ enum _frameowner {
};

struct _PyInterpreterFrame {
_PyStackRef f_executable; /* Deferred or strong reference (code object or None) */
PyObject *f_executable; /* Borrowed reference (code object or None) */
struct _PyInterpreterFrame *previous;
_PyStackRef f_funcobj; /* Deferred or strong reference. Only valid if not on C stack */
PyObject *f_globals; /* Borrowed reference. Only valid if not on C stack */
Expand Down
1 change: 0 additions & 1 deletion Include/internal/pycore_stackref.h
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,6 @@ PyStackRef_TYPE(_PyStackRef stackref) {
STACKREF_CHECK_FUNC(Gen)
STACKREF_CHECK_FUNC(Bool)
STACKREF_CHECK_FUNC(ExceptionInstance)
STACKREF_CHECK_FUNC(Code)
STACKREF_CHECK_FUNC(Function)

static inline bool
Expand Down
2 changes: 1 addition & 1 deletion InternalDocs/generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ the generator object.
When a generator object is destroyed in [`gen_dealloc`](../Objects/genobject.c),
its embedded `_PyInterpreterFrame` field may need to be preserved, if it is exposed
to Python as part of a [`PyFrameObject`](frames.md#frame-objects). This is detected
in [`_PyFrame_ClearExceptCode`](../Python/frame.c) by the fact that the interpreter
in [`_PyFrame_ClearExceptCodeAndFunction`](../Python/frame.c) by the fact that the interpreter
frame's `frame_obj` field is set, and the frame object it points to has refcount
greater than 1. If so, the `take_ownership()` function is called to create a new
copy of the interpreter frame and transfer ownership of it from the generator to
Expand Down
35 changes: 35 additions & 0 deletions Lib/test/test_ctypes/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from test.support import import_helper
_ctypes_test = import_helper.import_module("_ctypes_test")
from _ctypes import _Pointer, _SimpleCData
import gc
import weakref


try:
Expand Down Expand Up @@ -463,6 +465,39 @@ def callback(*args):
callback = proto(callback)
self.assertRaises(ArgumentError, lambda: callback((1, 2, 3, 4), POINT()))

def test_reassign_code_while_running(self):
freevar1 = None
freevar2 = None

def replacement():
freevar1
freevar2
return "replacement"

def original():
original.__code__ = replacement.__code__
return "original"

self.assertEqual(original(), "original")
self.assertEqual(original(), "replacement")

def test_function_code_object_no_leak(self):
def get_code(i):
ns = {}
exec(f"def f(): return {i}", ns)
return ns["f"].__code__

refs = []
def f(): pass
for i in range(100):
f.__code__ = get_code(i)
refs.append(weakref.ref(f.__code__))
self.assertEqual(f(), i)

del f
gc.collect()
self.assertTrue(all(ref() is None for ref in refs))


if __name__ == '__main__':
unittest.main()
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('3PiccPPPP' + INTERPRETER_FRAME + 'P'))
# function
def func(): pass
check(func, size('16Pi'))
check(func, size('17Pi'))
class c():
@staticmethod
def foo():
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Avoid reference counting of :class:`code` objects when creating and destroying
frames by having functions retain a list of all past :class:`code` objects.
2 changes: 1 addition & 1 deletion Modules/_testinternalcapi/interpreter.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Test_EvalFrame(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
entry.frame.f_globals = (PyObject*)0xaaa3;
entry.frame.f_builtins = (PyObject*)0xaaa4;
#endif
entry.frame.f_executable = PyStackRef_None;
entry.frame.f_executable = NULL;
entry.frame.instr_ptr = (_Py_CODEUNIT *)_Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS_PTR + 1;
entry.frame.stackpointer = entry.stack;
entry.frame.owner = FRAME_OWNED_BY_INTERPRETER;
Expand Down
8 changes: 4 additions & 4 deletions Modules/_testinternalcapi/test_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1942,7 +1942,7 @@ frame_dealloc(PyObject *op)

/* Kill all local variables including specials, if we own them */
if (f->f_frame == frame && frame->owner == FRAME_OWNED_BY_FRAME_OBJECT) {
PyStackRef_CLEAR(frame->f_executable);
frame->f_executable = NULL;
PyStackRef_CLEAR(frame->f_funcobj);
Py_CLEAR(frame->f_locals);
_PyStackRef *locals = _PyFrame_GetLocalsArray(frame);
Expand Down
17 changes: 17 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 @@ -698,6 +700,19 @@ func_set_code(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyEval_StopTheWorld(interp);
func_clear_version(interp, op);
if (op->func_old_codes == NULL) {
op->func_old_codes = PyList_New(0);
if (op->func_old_codes == NULL) {
_PyEval_StartTheWorld(interp);
return -1;
}
}

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

PyObject *old_code = op->func_code;
op->func_code = Py_NewRef(value);
_PyEval_StartTheWorld(interp);
Expand Down Expand Up @@ -1201,6 +1216,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 @@ -1256,6 +1272,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
10 changes: 5 additions & 5 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ gen_traverse(PyObject *self, visitproc visit, void *arg)
}
}
else {
// We still need to visit the code object when the frame is cleared to
// We still need to visit the function 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);
_Py_VISIT_STACKREF(gen->gi_iframe.f_funcobj);
}
/* No need to visit cr_origin, because it's just tuples/str/int, so can't
participate in a reference cycle. */
Expand Down Expand Up @@ -170,7 +170,7 @@ gen_clear_frame(PyGenObject *gen)
_PyInterpreterFrame *frame = &gen->gi_iframe;
_PyThreadState_UpdateLastProfiledFrame(_PyThreadState_GET(), frame, frame->previous);
frame->previous = NULL;
_PyFrame_ClearExceptCode(frame);
_PyFrame_ClearExceptCodeAndFunction(frame);
_PyErr_ClearExcState(&gen->gi_exc_state);
}

Expand Down Expand Up @@ -231,7 +231,7 @@ gen_dealloc(PyObject *self)
gen_clear_frame(gen);
}
assert(gen->gi_exc_state.exc_value == NULL);
PyStackRef_CLEAR(gen->gi_iframe.f_executable);
gen->gi_iframe.f_executable = NULL;
Py_CLEAR(gen->gi_name);
Py_CLEAR(gen->gi_qualname);

Expand Down Expand Up @@ -1105,7 +1105,7 @@ make_gen(PyTypeObject *type, PyFunctionObject *func)
gen->gi_weakreflist = NULL;
gen->gi_exc_state.exc_value = NULL;
gen->gi_exc_state.previous_item = NULL;
gen->gi_iframe.f_executable = PyStackRef_None;
gen->gi_iframe.f_executable = NULL;
assert(func->func_name != NULL);
gen->gi_name = Py_NewRef(func->func_name);
assert(func->func_qualname != NULL);
Expand Down
14 changes: 7 additions & 7 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1870,7 +1870,7 @@ dummy_func(
/* We don't know which of these is relevant here, so keep them equal */
assert(INLINE_CACHE_ENTRIES_SEND == INLINE_CACHE_ENTRIES_FOR_ITER);
#if TIER_ONE && defined(Py_DEBUG)
if (!PyStackRef_IsNone(frame->f_executable)) {
if (frame->f_executable != NULL) {
Py_ssize_t i = frame->instr_ptr - _PyFrame_GetBytecode(frame);
assert(i >= 0 && i <= INT_MAX);
int opcode = _Py_GetBaseCodeUnit(_PyFrame_GetCode(frame), (int)i).op.code;
Expand Down Expand Up @@ -6300,15 +6300,15 @@ dummy_func(
}

tier2 op(_GUARD_CODE_VERSION__PUSH_FRAME, (version/2 -- )) {
PyObject *code = PyStackRef_AsPyObjectBorrow(frame->f_executable);
PyObject *code = frame->f_executable;
assert(PyCode_Check(code));
if (((PyCodeObject *)code)->co_version != version) {
EXIT_IF(true);
}
}

tier2 op(_GUARD_CODE_VERSION_YIELD_VALUE, (version/2 -- )) {
PyObject *code = PyStackRef_AsPyObjectBorrow(frame->f_executable);
PyObject *code = frame->f_executable;
assert(PyCode_Check(code));
if (((PyCodeObject *)code)->co_version != version) {
frame->instr_ptr += 1 + INLINE_CACHE_ENTRIES_SEND;
Expand All @@ -6317,7 +6317,7 @@ dummy_func(
}

tier2 op(_GUARD_CODE_VERSION_RETURN_VALUE, (version/2 -- )) {
PyObject *code = PyStackRef_AsPyObjectBorrow(frame->f_executable);
PyObject *code = frame->f_executable;
assert(PyCode_Check(code));
if (((PyCodeObject *)code)->co_version != version) {
frame->instr_ptr += frame->return_offset;
Expand All @@ -6326,7 +6326,7 @@ dummy_func(
}

tier2 op(_GUARD_CODE_VERSION_RETURN_GENERATOR, (version/2 -- )) {
PyObject *code = PyStackRef_AsPyObjectBorrow(frame->f_executable);
PyObject *code = frame->f_executable;
assert(PyCode_Check(code));
if (((PyCodeObject *)code)->co_version != version) {
frame->instr_ptr += frame->return_offset;
Expand Down Expand Up @@ -6620,14 +6620,14 @@ dummy_func(
}
tracer->prev_state.recorded_count = 0;
tracer->prev_state.instr = next_instr;
PyObject *prev_code = PyStackRef_AsPyObjectBorrow(frame->f_executable);
PyObject *prev_code = frame->f_executable;
if (tracer->prev_state.instr_code != (PyCodeObject *)prev_code) {
Py_SETREF(tracer->prev_state.instr_code, (PyCodeObject*)Py_NewRef((prev_code)));
}

tracer->prev_state.instr_frame = frame;
tracer->prev_state.instr_oparg = oparg;
tracer->prev_state.instr_stacklevel = PyStackRef_IsNone(frame->f_executable) ? 2 : STACK_LEVEL();
tracer->prev_state.instr_stacklevel = frame->f_executable == NULL ? 2 : STACK_LEVEL();
if (_PyOpcode_Caches[_PyOpcode_Deopt[opcode]]
// Branch opcodes use the cache for branch history, not
// specialization counters. Don't reset it.
Expand Down
6 changes: 3 additions & 3 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1273,7 +1273,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
entry.frame.f_globals = (PyObject*)0xaaa3;
entry.frame.f_builtins = (PyObject*)0xaaa4;
#endif
entry.frame.f_executable = PyStackRef_None;
entry.frame.f_executable = NULL;
entry.frame.instr_ptr = (_Py_CODEUNIT *)_Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS + 1;
entry.frame.stackpointer = entry.stack;
entry.frame.owner = FRAME_OWNED_BY_INTERPRETER;
Expand Down Expand Up @@ -1983,7 +1983,7 @@ clear_thread_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)
tstate->datastack_top);
assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame);
_PyFrame_ClearExceptCode(frame);
PyStackRef_CLEAR(frame->f_executable);
frame->f_executable = NULL;
_PyThreadState_PopFrame(tstate, frame);
}

Expand All @@ -1998,7 +1998,7 @@ clear_gen_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)
gen->gi_exc_state.previous_item = NULL;
assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame);
frame->previous = NULL;
_PyFrame_ClearExceptCode(frame);
_PyFrame_ClearExceptCodeAndFunction(frame);
_PyErr_ClearExcState(&gen->gi_exc_state);
// gh-143939: There must not be any escaping calls between setting
// the generator return kind and returning from _PyEval_EvalFrame.
Expand Down
Loading
Loading