Skip to content

Commit 68f5fa6

Browse files
[3.11] GH-94262: Don't create frame objects for frames that aren't yet complete. (GH-94371) (#94482)
Co-authored-by: Mark Shannon <[email protected]>
1 parent 8fe0b1d commit 68f5fa6

File tree

7 files changed

+87
-13
lines changed

7 files changed

+87
-13
lines changed

Include/internal/pycore_frame.h

+17
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,21 @@ _PyFrame_SetStackPointer(_PyInterpreterFrame *frame, PyObject **stack_pointer)
133133
frame->stacktop = (int)(stack_pointer - frame->localsplus);
134134
}
135135

136+
/* Determine whether a frame is incomplete.
137+
* A frame is incomplete if it is part way through
138+
* creating cell objects or a generator or coroutine.
139+
*
140+
* Frames on the frame stack are incomplete until the
141+
* first RESUME instruction.
142+
* Frames owned by a generator are always complete.
143+
*/
144+
static inline bool
145+
_PyFrame_IsIncomplete(_PyInterpreterFrame *frame)
146+
{
147+
return frame->owner != FRAME_OWNED_BY_GENERATOR &&
148+
frame->prev_instr < _PyCode_CODE(frame->f_code) + frame->f_code->_co_firsttraceable;
149+
}
150+
136151
/* For use by _PyFrame_GetFrameObject
137152
Do not call directly. */
138153
PyFrameObject *
@@ -144,6 +159,8 @@ _PyFrame_MakeAndSetFrameObject(_PyInterpreterFrame *frame);
144159
static inline PyFrameObject *
145160
_PyFrame_GetFrameObject(_PyInterpreterFrame *frame)
146161
{
162+
163+
assert(!_PyFrame_IsIncomplete(frame));
147164
PyFrameObject *res = frame->frame_obj;
148165
if (res != NULL) {
149166
return res;

Lib/test/test_generators.py

+37
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,43 @@ def f():
170170
g.send(0)
171171
self.assertEqual(next(g), 1)
172172

173+
def test_handle_frame_object_in_creation(self):
174+
175+
#Attempt to expose partially constructed frames
176+
#See https://github.com/python/cpython/issues/94262
177+
178+
def cb(*args):
179+
inspect.stack()
180+
181+
def gen():
182+
yield 1
183+
184+
thresholds = gc.get_threshold()
185+
186+
gc.callbacks.append(cb)
187+
gc.set_threshold(1, 0, 0)
188+
try:
189+
gen()
190+
finally:
191+
gc.set_threshold(*thresholds)
192+
gc.callbacks.pop()
193+
194+
class Sneaky:
195+
def __del__(self):
196+
inspect.stack()
197+
198+
sneaky = Sneaky()
199+
sneaky._s = Sneaky()
200+
sneaky._s._s = sneaky
201+
202+
gc.set_threshold(1, 0, 0)
203+
try:
204+
del sneaky
205+
gen()
206+
finally:
207+
gc.set_threshold(*thresholds)
208+
209+
173210

174211
class ExceptionTest(unittest.TestCase):
175212
# Tests for the issue #23353: check that the currently handled exception
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Don't create frame objects for incomplete frames. Prevents the creation of
2+
generators and closures from being observable to Python and C extensions,
3+
restoring the behavior of 3.10 and earlier.

Objects/codeobject.c

+5-6
Original file line numberDiff line numberDiff line change
@@ -636,11 +636,10 @@ PyCode_New(int argcount, int kwonlyargcount,
636636
exceptiontable);
637637
}
638638

639-
static const char assert0[4] = {
640-
LOAD_ASSERTION_ERROR,
641-
0,
642-
RAISE_VARARGS,
643-
1
639+
static const char assert0[6] = {
640+
RESUME, 0,
641+
LOAD_ASSERTION_ERROR, 0,
642+
RAISE_VARARGS, 1
644643
};
645644

646645
PyCodeObject *
@@ -664,7 +663,7 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
664663
if (filename_ob == NULL) {
665664
goto failed;
666665
}
667-
code_ob = PyBytes_FromStringAndSize(assert0, 4);
666+
code_ob = PyBytes_FromStringAndSize(assert0, 6);
668667
if (code_ob == NULL) {
669668
goto failed;
670669
}

Objects/frameobject.c

+8-2
Original file line numberDiff line numberDiff line change
@@ -1155,8 +1155,14 @@ PyFrame_GetBack(PyFrameObject *frame)
11551155
{
11561156
assert(frame != NULL);
11571157
PyFrameObject *back = frame->f_back;
1158-
if (back == NULL && frame->f_frame->previous != NULL) {
1159-
back = _PyFrame_GetFrameObject(frame->f_frame->previous);
1158+
if (back == NULL) {
1159+
_PyInterpreterFrame *prev = frame->f_frame->previous;
1160+
while (prev && _PyFrame_IsIncomplete(prev)) {
1161+
prev = prev->previous;
1162+
}
1163+
if (prev) {
1164+
back = _PyFrame_GetFrameObject(prev);
1165+
}
11601166
}
11611167
Py_XINCREF(back);
11621168
return back;

Python/frame.c

+6-2
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,13 @@ take_ownership(PyFrameObject *f, _PyInterpreterFrame *frame)
6666
f->f_frame = frame;
6767
frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
6868
assert(f->f_back == NULL);
69-
if (frame->previous != NULL) {
69+
_PyInterpreterFrame *prev = frame->previous;
70+
while (prev && _PyFrame_IsIncomplete(prev)) {
71+
prev = prev->previous;
72+
}
73+
if (prev) {
7074
/* Link PyFrameObjects.f_back and remove link through _PyInterpreterFrame.previous */
71-
PyFrameObject *back = _PyFrame_GetFrameObject(frame->previous);
75+
PyFrameObject *back = _PyFrame_GetFrameObject(prev);
7276
if (back == NULL) {
7377
/* Memory error here. */
7478
assert(PyErr_ExceptionMatches(PyExc_MemoryError));

Python/sysmodule.c

+11-3
Original file line numberDiff line numberDiff line change
@@ -1784,9 +1784,17 @@ sys__getframe_impl(PyObject *module, int depth)
17841784
return NULL;
17851785
}
17861786

1787-
while (depth > 0 && frame != NULL) {
1788-
frame = frame->previous;
1789-
--depth;
1787+
if (frame != NULL) {
1788+
while (depth > 0) {
1789+
frame = frame->previous;
1790+
if (frame == NULL) {
1791+
break;
1792+
}
1793+
if (_PyFrame_IsIncomplete(frame)) {
1794+
continue;
1795+
}
1796+
--depth;
1797+
}
17901798
}
17911799
if (frame == NULL) {
17921800
_PyErr_SetString(tstate, PyExc_ValueError,

0 commit comments

Comments
 (0)