Skip to content

Commit 61762b9

Browse files
authored
GH-100126: Skip incomplete frames in more places (GH-100613)
1 parent 2e80c2a commit 61762b9

File tree

12 files changed

+62
-46
lines changed

12 files changed

+62
-46
lines changed

Include/internal/pycore_frame.h

+15
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,21 @@ _PyFrame_IsIncomplete(_PyInterpreterFrame *frame)
166166
frame->prev_instr < _PyCode_CODE(frame->f_code) + frame->f_code->_co_firsttraceable;
167167
}
168168

169+
static inline _PyInterpreterFrame *
170+
_PyFrame_GetFirstComplete(_PyInterpreterFrame *frame)
171+
{
172+
while (frame && _PyFrame_IsIncomplete(frame)) {
173+
frame = frame->previous;
174+
}
175+
return frame;
176+
}
177+
178+
static inline _PyInterpreterFrame *
179+
_PyThreadState_GetFrame(PyThreadState *tstate)
180+
{
181+
return _PyFrame_GetFirstComplete(tstate->cframe->current_frame);
182+
}
183+
169184
/* For use by _PyFrame_GetFrameObject
170185
Do not call directly. */
171186
PyFrameObject *

Lib/test/test_frame.py

+21
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import gc
2+
import operator
23
import re
34
import sys
45
import textwrap
@@ -372,6 +373,26 @@ def run(self):
372373
)
373374
sneaky_frame_object = sneaky_frame_object.f_back
374375

376+
def test_entry_frames_are_invisible_during_teardown(self):
377+
class C:
378+
"""A weakref'able class."""
379+
380+
def f():
381+
"""Try to find globals and locals as this frame is being cleared."""
382+
ref = C()
383+
# Ignore the fact that exec(C()) is a nonsense callback. We're only
384+
# using exec here because it tries to access the current frame's
385+
# globals and locals. If it's trying to get those from a shim frame,
386+
# we'll crash before raising:
387+
return weakref.ref(ref, exec)
388+
389+
with support.catch_unraisable_exception() as catcher:
390+
# Call from C, so there is a shim frame directly above f:
391+
weak = operator.call(f) # BOOM!
392+
# Cool, we didn't crash. Check that the callback actually happened:
393+
self.assertIs(catcher.unraisable.exc_type, TypeError)
394+
self.assertIsNone(weak())
395+
375396
@unittest.skipIf(_testcapi is None, 'need _testcapi')
376397
class TestCAPI(unittest.TestCase):
377398
def getframe(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix an issue where "incomplete" frames could be briefly visible to C code
2+
while other frames are being torn down, possibly resulting in corruption or
3+
hard crashes of the interpreter while running finalizers.

Modules/_tracemalloc.c

+3-10
Original file line numberDiff line numberDiff line change
@@ -347,14 +347,8 @@ traceback_get_frames(traceback_t *traceback)
347347
return;
348348
}
349349

350-
_PyInterpreterFrame *pyframe = tstate->cframe->current_frame;
351-
for (;;) {
352-
while (pyframe && _PyFrame_IsIncomplete(pyframe)) {
353-
pyframe = pyframe->previous;
354-
}
355-
if (pyframe == NULL) {
356-
break;
357-
}
350+
_PyInterpreterFrame *pyframe = _PyThreadState_GetFrame(tstate);
351+
while (pyframe) {
358352
if (traceback->nframe < tracemalloc_config.max_nframe) {
359353
tracemalloc_get_frame(pyframe, &traceback->frames[traceback->nframe]);
360354
assert(traceback->frames[traceback->nframe].filename != NULL);
@@ -363,8 +357,7 @@ traceback_get_frames(traceback_t *traceback)
363357
if (traceback->total_nframe < UINT16_MAX) {
364358
traceback->total_nframe++;
365359
}
366-
367-
pyframe = pyframe->previous;
360+
pyframe = _PyFrame_GetFirstComplete(pyframe->previous);
368361
}
369362
}
370363

Modules/signalmodule.c

+1-4
Original file line numberDiff line numberDiff line change
@@ -1803,10 +1803,7 @@ _PyErr_CheckSignalsTstate(PyThreadState *tstate)
18031803
*/
18041804
_Py_atomic_store(&is_tripped, 0);
18051805

1806-
_PyInterpreterFrame *frame = tstate->cframe->current_frame;
1807-
while (frame && _PyFrame_IsIncomplete(frame)) {
1808-
frame = frame->previous;
1809-
}
1806+
_PyInterpreterFrame *frame = _PyThreadState_GetFrame(tstate);
18101807
signal_state_t *state = &signal_global_state;
18111808
for (int i = 1; i < Py_NSIG; i++) {
18121809
if (!_Py_atomic_load_relaxed(&Handlers[i].tripped)) {

Objects/frameobject.c

+1-3
Original file line numberDiff line numberDiff line change
@@ -1405,9 +1405,7 @@ PyFrame_GetBack(PyFrameObject *frame)
14051405
PyFrameObject *back = frame->f_back;
14061406
if (back == NULL) {
14071407
_PyInterpreterFrame *prev = frame->f_frame->previous;
1408-
while (prev && _PyFrame_IsIncomplete(prev)) {
1409-
prev = prev->previous;
1410-
}
1408+
prev = _PyFrame_GetFirstComplete(prev);
14111409
if (prev) {
14121410
back = _PyFrame_GetFrameObject(prev);
14131411
}

Objects/genobject.c

+7-4
Original file line numberDiff line numberDiff line change
@@ -903,8 +903,11 @@ _Py_MakeCoro(PyFunctionObject *func)
903903
if (origin_depth == 0) {
904904
((PyCoroObject *)coro)->cr_origin_or_finalizer = NULL;
905905
} else {
906-
assert(_PyEval_GetFrame());
907-
PyObject *cr_origin = compute_cr_origin(origin_depth, _PyEval_GetFrame()->previous);
906+
_PyInterpreterFrame *frame = tstate->cframe->current_frame;
907+
assert(frame);
908+
assert(_PyFrame_IsIncomplete(frame));
909+
frame = _PyFrame_GetFirstComplete(frame->previous);
910+
PyObject *cr_origin = compute_cr_origin(origin_depth, frame);
908911
((PyCoroObject *)coro)->cr_origin_or_finalizer = cr_origin;
909912
if (!cr_origin) {
910913
Py_DECREF(coro);
@@ -1286,7 +1289,7 @@ compute_cr_origin(int origin_depth, _PyInterpreterFrame *current_frame)
12861289
/* First count how many frames we have */
12871290
int frame_count = 0;
12881291
for (; frame && frame_count < origin_depth; ++frame_count) {
1289-
frame = frame->previous;
1292+
frame = _PyFrame_GetFirstComplete(frame->previous);
12901293
}
12911294

12921295
/* Now collect them */
@@ -1305,7 +1308,7 @@ compute_cr_origin(int origin_depth, _PyInterpreterFrame *current_frame)
13051308
return NULL;
13061309
}
13071310
PyTuple_SET_ITEM(cr_origin, i, frameinfo);
1308-
frame = frame->previous;
1311+
frame = _PyFrame_GetFirstComplete(frame->previous);
13091312
}
13101313

13111314
return cr_origin;

Objects/typeobject.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -9578,13 +9578,13 @@ super_init_impl(PyObject *self, PyTypeObject *type, PyObject *obj) {
95789578
/* Call super(), without args -- fill in from __class__
95799579
and first local variable on the stack. */
95809580
PyThreadState *tstate = _PyThreadState_GET();
9581-
_PyInterpreterFrame *cframe = tstate->cframe->current_frame;
9582-
if (cframe == NULL) {
9581+
_PyInterpreterFrame *frame = _PyThreadState_GetFrame(tstate);
9582+
if (frame == NULL) {
95839583
PyErr_SetString(PyExc_RuntimeError,
95849584
"super(): no current frame");
95859585
return -1;
95869586
}
9587-
int res = super_init_without_args(cframe, cframe->f_code, &type, &obj);
9587+
int res = super_init_without_args(frame, frame->f_code, &type, &obj);
95889588

95899589
if (res < 0) {
95909590
return -1;

Python/ceval.c

+4-7
Original file line numberDiff line numberDiff line change
@@ -2749,16 +2749,13 @@ _PyInterpreterFrame *
27492749
_PyEval_GetFrame(void)
27502750
{
27512751
PyThreadState *tstate = _PyThreadState_GET();
2752-
return tstate->cframe->current_frame;
2752+
return _PyThreadState_GetFrame(tstate);
27532753
}
27542754

27552755
PyFrameObject *
27562756
PyEval_GetFrame(void)
27572757
{
27582758
_PyInterpreterFrame *frame = _PyEval_GetFrame();
2759-
while (frame && _PyFrame_IsIncomplete(frame)) {
2760-
frame = frame->previous;
2761-
}
27622759
if (frame == NULL) {
27632760
return NULL;
27642761
}
@@ -2772,7 +2769,7 @@ PyEval_GetFrame(void)
27722769
PyObject *
27732770
_PyEval_GetBuiltins(PyThreadState *tstate)
27742771
{
2775-
_PyInterpreterFrame *frame = tstate->cframe->current_frame;
2772+
_PyInterpreterFrame *frame = _PyThreadState_GetFrame(tstate);
27762773
if (frame != NULL) {
27772774
return frame->f_builtins;
27782775
}
@@ -2811,7 +2808,7 @@ PyObject *
28112808
PyEval_GetLocals(void)
28122809
{
28132810
PyThreadState *tstate = _PyThreadState_GET();
2814-
_PyInterpreterFrame *current_frame = tstate->cframe->current_frame;
2811+
_PyInterpreterFrame *current_frame = _PyThreadState_GetFrame(tstate);
28152812
if (current_frame == NULL) {
28162813
_PyErr_SetString(tstate, PyExc_SystemError, "frame does not exist");
28172814
return NULL;
@@ -2830,7 +2827,7 @@ PyObject *
28302827
PyEval_GetGlobals(void)
28312828
{
28322829
PyThreadState *tstate = _PyThreadState_GET();
2833-
_PyInterpreterFrame *current_frame = tstate->cframe->current_frame;
2830+
_PyInterpreterFrame *current_frame = _PyThreadState_GetFrame(tstate);
28342831
if (current_frame == NULL) {
28352832
return NULL;
28362833
}

Python/frame.c

+1-4
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,7 @@ take_ownership(PyFrameObject *f, _PyInterpreterFrame *frame)
9696
}
9797
assert(!_PyFrame_IsIncomplete(frame));
9898
assert(f->f_back == NULL);
99-
_PyInterpreterFrame *prev = frame->previous;
100-
while (prev && _PyFrame_IsIncomplete(prev)) {
101-
prev = prev->previous;
102-
}
99+
_PyInterpreterFrame *prev = _PyFrame_GetFirstComplete(frame->previous);
103100
frame->previous = NULL;
104101
if (prev) {
105102
assert(prev->owner != FRAME_OWNED_BY_CSTACK);

Python/pystate.c

+2-7
Original file line numberDiff line numberDiff line change
@@ -1302,10 +1302,7 @@ PyFrameObject*
13021302
PyThreadState_GetFrame(PyThreadState *tstate)
13031303
{
13041304
assert(tstate != NULL);
1305-
_PyInterpreterFrame *f = tstate->cframe->current_frame;
1306-
while (f && _PyFrame_IsIncomplete(f)) {
1307-
f = f->previous;
1308-
}
1305+
_PyInterpreterFrame *f = _PyThreadState_GetFrame(tstate);
13091306
if (f == NULL) {
13101307
return NULL;
13111308
}
@@ -1431,9 +1428,7 @@ _PyThread_CurrentFrames(void)
14311428
PyThreadState *t;
14321429
for (t = i->threads.head; t != NULL; t = t->next) {
14331430
_PyInterpreterFrame *frame = t->cframe->current_frame;
1434-
while (frame && _PyFrame_IsIncomplete(frame)) {
1435-
frame = frame->previous;
1436-
}
1431+
frame = _PyFrame_GetFirstComplete(frame);
14371432
if (frame == NULL) {
14381433
continue;
14391434
}

Python/sysmodule.c

+1-4
Original file line numberDiff line numberDiff line change
@@ -1884,13 +1884,10 @@ sys__getframe_impl(PyObject *module, int depth)
18841884

18851885
if (frame != NULL) {
18861886
while (depth > 0) {
1887-
frame = frame->previous;
1887+
frame = _PyFrame_GetFirstComplete(frame->previous);
18881888
if (frame == NULL) {
18891889
break;
18901890
}
1891-
if (_PyFrame_IsIncomplete(frame)) {
1892-
continue;
1893-
}
18941891
--depth;
18951892
}
18961893
}

0 commit comments

Comments
 (0)