Skip to content

Commit 015b49a

Browse files
[3.11] GH-97779: Ensure that *all* frame objects are backed by "complete" frames (GH-97886)
(cherry picked from commit 0ff8fd6) Co-authored-by: Brandt Bucher <brandtbucher@microsoft.com>
1 parent 8c517d8 commit 015b49a

File tree

5 files changed

+75
-4
lines changed

5 files changed

+75
-4
lines changed

Lib/test/test_code.py

+33
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
import unittest
133133
import textwrap
134134
import weakref
135+
import dis
135136

136137
try:
137138
import ctypes
@@ -671,6 +672,38 @@ def test_lines(self):
671672
self.check_lines(misshappen)
672673
self.check_lines(bug93662)
673674

675+
@cpython_only
676+
def test_code_new_empty(self):
677+
# If this test fails, it means that the construction of PyCode_NewEmpty
678+
# needs to be modified! Please update this test *and* PyCode_NewEmpty,
679+
# so that they both stay in sync.
680+
def f():
681+
pass
682+
PY_CODE_LOCATION_INFO_NO_COLUMNS = 13
683+
f.__code__ = f.__code__.replace(
684+
co_firstlineno=42,
685+
co_code=bytes(
686+
[
687+
dis.opmap["RESUME"], 0,
688+
dis.opmap["LOAD_ASSERTION_ERROR"], 0,
689+
dis.opmap["RAISE_VARARGS"], 1,
690+
]
691+
),
692+
co_linetable=bytes(
693+
[
694+
(1 << 7)
695+
| (PY_CODE_LOCATION_INFO_NO_COLUMNS << 3)
696+
| (3 - 1),
697+
0,
698+
]
699+
),
700+
)
701+
self.assertRaises(AssertionError, f)
702+
self.assertEqual(
703+
list(f.__code__.co_positions()),
704+
3 * [(42, 42, None, None)],
705+
)
706+
674707

675708
if check_impl_detail(cpython=True) and ctypes is not None:
676709
py = ctypes.pythonapi
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ensure that all Python frame objects are backed by "complete" frames.

Objects/codeobject.c

+19-3
Original file line numberDiff line numberDiff line change
@@ -636,19 +636,30 @@ PyCode_New(int argcount, int kwonlyargcount,
636636
exceptiontable);
637637
}
638638

639-
static const char assert0[6] = {
639+
// NOTE: When modifying the construction of PyCode_NewEmpty, please also change
640+
// test.test_code.CodeLocationTest.test_code_new_empty to keep it in sync!
641+
642+
static const uint8_t assert0[6] = {
640643
RESUME, 0,
641644
LOAD_ASSERTION_ERROR, 0,
642645
RAISE_VARARGS, 1
643646
};
644647

648+
static const uint8_t linetable[2] = {
649+
(1 << 7) // New entry.
650+
| (PY_CODE_LOCATION_INFO_NO_COLUMNS << 3)
651+
| (3 - 1), // Three code units.
652+
0, // Offset from co_firstlineno.
653+
};
654+
645655
PyCodeObject *
646656
PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
647657
{
648658
PyObject *nulltuple = NULL;
649659
PyObject *filename_ob = NULL;
650660
PyObject *funcname_ob = NULL;
651661
PyObject *code_ob = NULL;
662+
PyObject *linetable_ob = NULL;
652663
PyCodeObject *result = NULL;
653664

654665
nulltuple = PyTuple_New(0);
@@ -663,10 +674,14 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
663674
if (filename_ob == NULL) {
664675
goto failed;
665676
}
666-
code_ob = PyBytes_FromStringAndSize(assert0, 6);
677+
code_ob = PyBytes_FromStringAndSize((const char *)assert0, 6);
667678
if (code_ob == NULL) {
668679
goto failed;
669680
}
681+
linetable_ob = PyBytes_FromStringAndSize((const char *)linetable, 2);
682+
if (linetable_ob == NULL) {
683+
goto failed;
684+
}
670685

671686
#define emptystring (PyObject *)&_Py_SINGLETON(bytes_empty)
672687
struct _PyCodeConstructor con = {
@@ -675,7 +690,7 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
675690
.qualname = funcname_ob,
676691
.code = code_ob,
677692
.firstlineno = firstlineno,
678-
.linetable = emptystring,
693+
.linetable = linetable_ob,
679694
.consts = nulltuple,
680695
.names = nulltuple,
681696
.localsplusnames = nulltuple,
@@ -690,6 +705,7 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
690705
Py_XDECREF(funcname_ob);
691706
Py_XDECREF(filename_ob);
692707
Py_XDECREF(code_ob);
708+
Py_XDECREF(linetable_ob);
693709
return result;
694710
}
695711

Objects/frameobject.c

+15-1
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ first_line_not_before(int *lines, int len, int line)
590590
static PyFrameState
591591
_PyFrame_GetState(PyFrameObject *frame)
592592
{
593+
assert(!_PyFrame_IsIncomplete(frame->f_frame));
593594
if (frame->f_frame->stacktop == 0) {
594595
return FRAME_CLEARED;
595596
}
@@ -1063,6 +1064,9 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
10631064
init_frame((_PyInterpreterFrame *)f->_f_frame_data, func, locals);
10641065
f->f_frame = (_PyInterpreterFrame *)f->_f_frame_data;
10651066
f->f_frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
1067+
// This frame needs to be "complete", so pretend that the first RESUME ran:
1068+
f->f_frame->prev_instr = _PyCode_CODE(code) + code->_co_firsttraceable;
1069+
assert(!_PyFrame_IsIncomplete(f->f_frame));
10661070
Py_DECREF(func);
10671071
_PyObject_GC_TRACK(f);
10681072
return f;
@@ -1189,6 +1193,7 @@ _PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame) {
11891193
int
11901194
PyFrame_FastToLocalsWithError(PyFrameObject *f)
11911195
{
1196+
assert(!_PyFrame_IsIncomplete(f->f_frame));
11921197
if (f == NULL) {
11931198
PyErr_BadInternalCall();
11941199
return -1;
@@ -1204,7 +1209,7 @@ void
12041209
PyFrame_FastToLocals(PyFrameObject *f)
12051210
{
12061211
int res;
1207-
1212+
assert(!_PyFrame_IsIncomplete(f->f_frame));
12081213
assert(!PyErr_Occurred());
12091214

12101215
res = PyFrame_FastToLocalsWithError(f);
@@ -1282,6 +1287,7 @@ _PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear)
12821287
void
12831288
PyFrame_LocalsToFast(PyFrameObject *f, int clear)
12841289
{
1290+
assert(!_PyFrame_IsIncomplete(f->f_frame));
12851291
if (f && f->f_fast_as_locals && _PyFrame_GetState(f) != FRAME_CLEARED) {
12861292
_PyFrame_LocalsToFast(f->f_frame, clear);
12871293
f->f_fast_as_locals = 0;
@@ -1292,6 +1298,7 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear)
12921298
int _PyFrame_IsEntryFrame(PyFrameObject *frame)
12931299
{
12941300
assert(frame != NULL);
1301+
assert(!_PyFrame_IsIncomplete(frame->f_frame));
12951302
return frame->f_frame->is_entry;
12961303
}
12971304

@@ -1300,6 +1307,7 @@ PyCodeObject *
13001307
PyFrame_GetCode(PyFrameObject *frame)
13011308
{
13021309
assert(frame != NULL);
1310+
assert(!_PyFrame_IsIncomplete(frame->f_frame));
13031311
PyCodeObject *code = frame->f_frame->f_code;
13041312
assert(code != NULL);
13051313
Py_INCREF(code);
@@ -1311,6 +1319,7 @@ PyFrameObject*
13111319
PyFrame_GetBack(PyFrameObject *frame)
13121320
{
13131321
assert(frame != NULL);
1322+
assert(!_PyFrame_IsIncomplete(frame->f_frame));
13141323
PyFrameObject *back = frame->f_back;
13151324
if (back == NULL) {
13161325
_PyInterpreterFrame *prev = frame->f_frame->previous;
@@ -1328,24 +1337,28 @@ PyFrame_GetBack(PyFrameObject *frame)
13281337
PyObject*
13291338
PyFrame_GetLocals(PyFrameObject *frame)
13301339
{
1340+
assert(!_PyFrame_IsIncomplete(frame->f_frame));
13311341
return frame_getlocals(frame, NULL);
13321342
}
13331343

13341344
PyObject*
13351345
PyFrame_GetGlobals(PyFrameObject *frame)
13361346
{
1347+
assert(!_PyFrame_IsIncomplete(frame->f_frame));
13371348
return frame_getglobals(frame, NULL);
13381349
}
13391350

13401351
PyObject*
13411352
PyFrame_GetBuiltins(PyFrameObject *frame)
13421353
{
1354+
assert(!_PyFrame_IsIncomplete(frame->f_frame));
13431355
return frame_getbuiltins(frame, NULL);
13441356
}
13451357

13461358
int
13471359
PyFrame_GetLasti(PyFrameObject *frame)
13481360
{
1361+
assert(!_PyFrame_IsIncomplete(frame->f_frame));
13491362
int lasti = _PyInterpreterFrame_LASTI(frame->f_frame);
13501363
if (lasti < 0) {
13511364
return -1;
@@ -1356,6 +1369,7 @@ PyFrame_GetLasti(PyFrameObject *frame)
13561369
PyObject *
13571370
PyFrame_GetGenerator(PyFrameObject *frame)
13581371
{
1372+
assert(!_PyFrame_IsIncomplete(frame->f_frame));
13591373
if (frame->f_frame->owner != FRAME_OWNED_BY_GENERATOR) {
13601374
return NULL;
13611375
}

Python/frame.c

+7
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ take_ownership(PyFrameObject *f, _PyInterpreterFrame *frame)
6868
frame = (_PyInterpreterFrame *)f->_f_frame_data;
6969
f->f_frame = frame;
7070
frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
71+
if (_PyFrame_IsIncomplete(frame)) {
72+
// This may be a newly-created generator or coroutine frame. Since it's
73+
// dead anyways, just pretend that the first RESUME ran:
74+
PyCodeObject *code = frame->f_code;
75+
frame->prev_instr = _PyCode_CODE(code) + code->_co_firsttraceable;
76+
}
77+
assert(!_PyFrame_IsIncomplete(frame));
7178
assert(f->f_back == NULL);
7279
_PyInterpreterFrame *prev = frame->previous;
7380
while (prev && _PyFrame_IsIncomplete(prev)) {

0 commit comments

Comments
 (0)