Repository navigation
Refleak in PyGen_New()? Also, PyGen_New() is not tested #99485
Copy link
Copy link
Closed
Labels
type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
The public C API PyGen_New() is implemented by calling gen_new_with_qualname().
gen_new_with_qualname() code evolved in Python 3.11:
- (A) commit b11a951 (May 2021):
- Py_INCREF(f->f_code);
- gen->gi_code = (PyObject *)(f->f_code);
+ gen->gi_code = PyFrame_GetCode(f);- (B) commit 299483c (Dec 2021):
gen->gi_code = PyFrame_GetCode(f);
+ Py_INCREF(gen->gi_code);
+ Py_DECREF(f);I'm surprised by the added Py_INCREF(gen->gi_code); since PyFrame_GetCode() already returns a strong reference. For me, it's a reference leak.
I tried but failed to write an unit test on the PyGen_New() function. I wrote the test:
static PyObject *
test_gen_new(PyObject *self, PyObject *Py_UNUSED(args))
{
PyCodeObject *code = PyCode_NewEmpty("filename", "func_name", 1);
if (code == NULL) {
return NULL;
}
PyObject *globals = PyDict_New();
if (globals == NULL) {
return NULL;
}
PyObject *locals = globals; // borrowed ref
PyThreadState *tstate = PyThreadState_Get();
PyFrameObject *frame = PyFrame_New(tstate, code, globals, locals);
if (frame == NULL) {
return NULL;
}
PyObject *gen = PyGen_New(frame);
Py_DECREF(gen);
Py_DECREF(code);
Py_DECREF(globals);
Py_DECREF(frame);
Py_RETURN_NONE;
}But when I run it, it fails with an assertion error:
python: Objects/genobject.c:937: gen_new_with_qualname: Assertion `frame->frame_obj == f' failed.
Extract of the code. I added newlines and "HERE" comment around the code causing the assertion error:
/* Copy the frame */
assert(f->f_frame->frame_obj == NULL);
assert(f->f_frame->owner == FRAME_OWNED_BY_FRAME_OBJECT);
_PyInterpreterFrame *frame = (_PyInterpreterFrame *)gen->gi_iframe;
_PyFrame_Copy((_PyInterpreterFrame *)f->_f_frame_data, frame);
gen->gi_frame_state = FRAME_CREATED;
assert(frame->frame_obj == f); // <=== HERE
f->f_frame = frame;
frame->owner = FRAME_OWNED_BY_GENERATOR;
assert(PyObject_GC_IsTracked((PyObject *)f));
gen->gi_code = PyFrame_GetCode(f);
Py_DECREF(f);But the problem doesn't come from _PyFrame_Copy(). If I added the following assertion, it also fails:
assert(f->f_frame->frame_obj == f);I am not sure if PyFrame_New() is supposed to create a frame with frame->f_frame->frame_obj == frame or not.
Linked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Activity
vstinner commentedon Nov 14, 2022
I created PR #99486 which adds the test which fails with the assertion error.
vstinner commentedon Nov 14, 2022
@markshannon @brandtbucher @iritkatriel: Do you have any clue why the assertion fails? Is it expected? Do I misuse PyGen_New() API?
PyGen_New() documentation doesn't explain how the frame should be created: https://docs.python.org/dev/c-api/gen.html#c.PyGen_New
sobolevn commentedon Nov 14, 2022
@vstinner thanks for raising this, I was not able to write a test case for this as well in #94808
Right now this API seems broken, untested, and undocumented :(
Things I've tried:
PyGen_Newwas usedvstinner commentedon Nov 14, 2022
I also failed to find usages of PyGen_New() in the wild (3rd party code). Maybe only CPython used it in the past, but the code was reworked and CPython no longer uses it neither. Maybe it should be deprecated and scheduled from removal? There are similar functions:
These 4 functions take a frame as argument and call internally the private function
gen_new_with_qualname().vstinner commentedon Dec 7, 2022
I cannot find any user of these 4 functions in the PyPI top 5000 projects:
vstinner commentedon Dec 7, 2022
@markshannon: With a small hack, I managed to use PyGen_New() C API in Python 3.10.
I added this code to the _testcapi extension:
Usage of the C function in Python:
Output:
In Python 3.10, PyFrame_New() has the comment:
/* Legacy API */but it's not deprecated (noPy_DEPRECATED(3.10)in the header file).The PyFrame_New() function is not documented, I don't know if it's supposed to be used :-)