[Python-checkins] GH-97779: Ensure that *all* frame objects are backed by "complete" frames (GH-97845)

brandtbucher webhook-mailer at python.org
Tue Oct 4 20:30:12 EDT 2022


https://github.com/python/cpython/commit/0ff8fd65838f9f9ed90d7a055d26a2ce9fc0ce85
commit: 0ff8fd65838f9f9ed90d7a055d26a2ce9fc0ce85
branch: main
author: Brandt Bucher <brandtbucher at microsoft.com>
committer: brandtbucher <brandtbucher at gmail.com>
date: 2022-10-04T17:30:03-07:00
summary:

GH-97779: Ensure that *all* frame objects are backed by "complete" frames (GH-97845)

files:
A Misc/NEWS.d/next/Core and Builtins/2022-10-04-02-00-10.gh-issue-97779.f3N1hI.rst
M Lib/test/test_code.py
M Objects/codeobject.c
M Objects/frameobject.c
M Python/frame.c

diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py
index 2fdfdd0d309c..4e4d82314a9f 100644
--- a/Lib/test/test_code.py
+++ b/Lib/test/test_code.py
@@ -132,6 +132,7 @@
 import unittest
 import textwrap
 import weakref
+import dis
 
 try:
     import ctypes
@@ -682,6 +683,38 @@ def test_lines(self):
         self.check_lines(misshappen)
         self.check_lines(bug93662)
 
+    @cpython_only
+    def test_code_new_empty(self):
+        # If this test fails, it means that the construction of PyCode_NewEmpty
+        # needs to be modified! Please update this test *and* PyCode_NewEmpty,
+        # so that they both stay in sync.
+        def f():
+            pass
+        PY_CODE_LOCATION_INFO_NO_COLUMNS = 13
+        f.__code__ = f.__code__.replace(
+            co_firstlineno=42,
+            co_code=bytes(
+                [
+                    dis.opmap["RESUME"], 0,
+                    dis.opmap["LOAD_ASSERTION_ERROR"], 0,
+                    dis.opmap["RAISE_VARARGS"], 1,
+                ]
+            ),
+            co_linetable=bytes(
+                [
+                    (1 << 7)
+                    | (PY_CODE_LOCATION_INFO_NO_COLUMNS << 3)
+                    | (3 - 1),
+                    0,
+                ]
+            ),
+        )
+        self.assertRaises(AssertionError, f)
+        self.assertEqual(
+            list(f.__code__.co_positions()),
+            3 * [(42, 42, None, None)],
+        )
+
 
 if check_impl_detail(cpython=True) and ctypes is not None:
     py = ctypes.pythonapi
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-10-04-02-00-10.gh-issue-97779.f3N1hI.rst b/Misc/NEWS.d/next/Core and Builtins/2022-10-04-02-00-10.gh-issue-97779.f3N1hI.rst
new file mode 100644
index 000000000000..611521808865
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-10-04-02-00-10.gh-issue-97779.f3N1hI.rst	
@@ -0,0 +1 @@
+Ensure that all Python frame objects are backed by "complete" frames.
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index 7d0d038f489a..14d1d00684ae 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -638,12 +638,22 @@ PyCode_New(int argcount, int kwonlyargcount,
                                      exceptiontable);
 }
 
-static const char assert0[6] = {
+// NOTE: When modifying the construction of PyCode_NewEmpty, please also change
+// test.test_code.CodeLocationTest.test_code_new_empty to keep it in sync!
+
+static const uint8_t assert0[6] = {
     RESUME, 0,
     LOAD_ASSERTION_ERROR, 0,
     RAISE_VARARGS, 1
 };
 
+static const uint8_t linetable[2] = {
+    (1 << 7)  // New entry.
+    | (PY_CODE_LOCATION_INFO_NO_COLUMNS << 3)
+    | (3 - 1),  // Three code units.
+    0,  // Offset from co_firstlineno.
+};
+
 PyCodeObject *
 PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
 {
@@ -651,6 +661,7 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
     PyObject *filename_ob = NULL;
     PyObject *funcname_ob = NULL;
     PyObject *code_ob = NULL;
+    PyObject *linetable_ob = NULL;
     PyCodeObject *result = NULL;
 
     nulltuple = PyTuple_New(0);
@@ -665,10 +676,14 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
     if (filename_ob == NULL) {
         goto failed;
     }
-    code_ob = PyBytes_FromStringAndSize(assert0, 6);
+    code_ob = PyBytes_FromStringAndSize((const char *)assert0, 6);
     if (code_ob == NULL) {
         goto failed;
     }
+    linetable_ob = PyBytes_FromStringAndSize((const char *)linetable, 2);
+    if (linetable_ob == NULL) {
+        goto failed;
+    }
 
 #define emptystring (PyObject *)&_Py_SINGLETON(bytes_empty)
     struct _PyCodeConstructor con = {
@@ -677,7 +692,7 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
         .qualname = funcname_ob,
         .code = code_ob,
         .firstlineno = firstlineno,
-        .linetable = emptystring,
+        .linetable = linetable_ob,
         .consts = nulltuple,
         .names = nulltuple,
         .localsplusnames = nulltuple,
@@ -692,6 +707,7 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
     Py_XDECREF(funcname_ob);
     Py_XDECREF(filename_ob);
     Py_XDECREF(code_ob);
+    Py_XDECREF(linetable_ob);
     return result;
 }
 
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 2e3777943126..6a51a946ef35 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -588,6 +588,7 @@ first_line_not_before(int *lines, int len, int line)
 static PyFrameState
 _PyFrame_GetState(PyFrameObject *frame)
 {
+    assert(!_PyFrame_IsIncomplete(frame->f_frame));
     if (frame->f_frame->stacktop == 0) {
         return FRAME_CLEARED;
     }
@@ -1094,6 +1095,9 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
     init_frame((_PyInterpreterFrame *)f->_f_frame_data, func, locals);
     f->f_frame = (_PyInterpreterFrame *)f->_f_frame_data;
     f->f_frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
+    // This frame needs to be "complete", so pretend that the first RESUME ran:
+    f->f_frame->prev_instr = _PyCode_CODE(code) + code->_co_firsttraceable;
+    assert(!_PyFrame_IsIncomplete(f->f_frame));
     Py_DECREF(func);
     _PyObject_GC_TRACK(f);
     return f;
@@ -1222,6 +1226,7 @@ _PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame) {
 int
 PyFrame_FastToLocalsWithError(PyFrameObject *f)
 {
+    assert(!_PyFrame_IsIncomplete(f->f_frame));
     if (f == NULL) {
         PyErr_BadInternalCall();
         return -1;
@@ -1237,7 +1242,7 @@ void
 PyFrame_FastToLocals(PyFrameObject *f)
 {
     int res;
-
+    assert(!_PyFrame_IsIncomplete(f->f_frame));
     assert(!PyErr_Occurred());
 
     res = PyFrame_FastToLocalsWithError(f);
@@ -1320,6 +1325,7 @@ _PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear)
 void
 PyFrame_LocalsToFast(PyFrameObject *f, int clear)
 {
+    assert(!_PyFrame_IsIncomplete(f->f_frame));
     if (f && f->f_fast_as_locals && _PyFrame_GetState(f) != FRAME_CLEARED) {
         _PyFrame_LocalsToFast(f->f_frame, clear);
         f->f_fast_as_locals = 0;
@@ -1330,6 +1336,7 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear)
 int _PyFrame_IsEntryFrame(PyFrameObject *frame)
 {
     assert(frame != NULL);
+    assert(!_PyFrame_IsIncomplete(frame->f_frame));
     return frame->f_frame->is_entry;
 }
 
@@ -1338,6 +1345,7 @@ PyCodeObject *
 PyFrame_GetCode(PyFrameObject *frame)
 {
     assert(frame != NULL);
+    assert(!_PyFrame_IsIncomplete(frame->f_frame));
     PyCodeObject *code = frame->f_frame->f_code;
     assert(code != NULL);
     Py_INCREF(code);
@@ -1349,6 +1357,7 @@ PyFrameObject*
 PyFrame_GetBack(PyFrameObject *frame)
 {
     assert(frame != NULL);
+    assert(!_PyFrame_IsIncomplete(frame->f_frame));
     PyFrameObject *back = frame->f_back;
     if (back == NULL) {
         _PyInterpreterFrame *prev = frame->f_frame->previous;
@@ -1366,24 +1375,28 @@ PyFrame_GetBack(PyFrameObject *frame)
 PyObject*
 PyFrame_GetLocals(PyFrameObject *frame)
 {
+    assert(!_PyFrame_IsIncomplete(frame->f_frame));
     return frame_getlocals(frame, NULL);
 }
 
 PyObject*
 PyFrame_GetGlobals(PyFrameObject *frame)
 {
+    assert(!_PyFrame_IsIncomplete(frame->f_frame));
     return frame_getglobals(frame, NULL);
 }
 
 PyObject*
 PyFrame_GetBuiltins(PyFrameObject *frame)
 {
+    assert(!_PyFrame_IsIncomplete(frame->f_frame));
     return frame_getbuiltins(frame, NULL);
 }
 
 int
 PyFrame_GetLasti(PyFrameObject *frame)
 {
+    assert(!_PyFrame_IsIncomplete(frame->f_frame));
     int lasti = _PyInterpreterFrame_LASTI(frame->f_frame);
     if (lasti < 0) {
         return -1;
@@ -1394,6 +1407,7 @@ PyFrame_GetLasti(PyFrameObject *frame)
 PyObject *
 PyFrame_GetGenerator(PyFrameObject *frame)
 {
+    assert(!_PyFrame_IsIncomplete(frame->f_frame));
     if (frame->f_frame->owner != FRAME_OWNED_BY_GENERATOR) {
         return NULL;
     }
diff --git a/Python/frame.c b/Python/frame.c
index 05a8cffcb8a7..96566de63a78 100644
--- a/Python/frame.c
+++ b/Python/frame.c
@@ -70,6 +70,13 @@ take_ownership(PyFrameObject *f, _PyInterpreterFrame *frame)
     frame = (_PyInterpreterFrame *)f->_f_frame_data;
     f->f_frame = frame;
     frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
+    if (_PyFrame_IsIncomplete(frame)) {
+        // This may be a newly-created generator or coroutine frame. Since it's
+        // dead anyways, just pretend that the first RESUME ran:
+        PyCodeObject *code = frame->f_code;
+        frame->prev_instr = _PyCode_CODE(code) + code->_co_firsttraceable;
+    }
+    assert(!_PyFrame_IsIncomplete(frame));
     assert(f->f_back == NULL);
     _PyInterpreterFrame *prev = frame->previous;
     while (prev && _PyFrame_IsIncomplete(prev)) {



More information about the Python-checkins mailing list