[Python-checkins] GH-88116: Document that PyCodeNew is dangerous, and make PyCode_NewEmpty less dangerous. (GH-91790)

markshannon webhook-mailer at python.org
Thu Apr 21 14:08:46 EDT 2022


https://github.com/python/cpython/commit/d44815cabc0a8d9932df2fa95cb374eadddb7c17
commit: d44815cabc0a8d9932df2fa95cb374eadddb7c17
branch: main
author: Mark Shannon <mark at hotpy.org>
committer: markshannon <mark at hotpy.org>
date: 2022-04-21T19:08:36+01:00
summary:

GH-88116: Document that PyCodeNew is dangerous, and make PyCode_NewEmpty less dangerous. (GH-91790)

files:
M Doc/c-api/code.rst
M Doc/whatsnew/3.11.rst
M Lib/test/test_code.py
M Objects/codeobject.c

diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst
index 840b8426dbbdc..407d8b481be4a 100644
--- a/Doc/c-api/code.rst
+++ b/Doc/c-api/code.rst
@@ -33,24 +33,33 @@ bound into a function.
 
    Return the number of free variables in *co*.
 
-.. c:function:: PyCodeObject* PyCode_New(int argcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *lnotab)
+.. c:function:: PyCodeObject* PyCode_New(int argcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable)
 
    Return a new code object.  If you need a dummy code object to create a frame,
    use :c:func:`PyCode_NewEmpty` instead.  Calling :c:func:`PyCode_New` directly
-   can bind you to a precise Python version since the definition of the bytecode
-   changes often.
+   will bind you to a precise Python version since the definition of the bytecode
+   changes often. The many arguments of this function are inter-dependent in complex
+   ways, meaning that subtle changes to values are likely to result in incorrect
+   execution or VM crashes. Use this function only with extreme care.
 
-.. c:function:: PyCodeObject* PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *lnotab)
+   .. versionchanged:: 3.11
+      Added ``exceptiontable`` parameter.
+
+.. c:function:: PyCodeObject* PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable)
 
    Similar to :c:func:`PyCode_New`, but with an extra "posonlyargcount" for positional-only arguments.
+   The same caveats that apply to ``PyCode_New`` also apply to this function.
 
    .. versionadded:: 3.8
 
+   .. versionchanged:: 3.11
+      Added ``exceptiontable`` parameter.
+
 .. c:function:: PyCodeObject* PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
 
    Return a new empty code object with the specified filename,
-   function name, and first line number.  It is illegal to
-   :func:`exec` or :func:`eval` the resulting code object.
+   function name, and first line number. The resulting code
+   object will raise an ``Exception`` if executed.
 
 .. c:function:: int PyCode_Addr2Line(PyCodeObject *co, int byte_offset)
 
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index 8d74c9bbebad8..c3a8a7e42a110 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -1159,6 +1159,12 @@ C API Changes
   as its second parameter, instead of ``PyFrameObject*``.
   See :pep:`523` for more details of how to use this function pointer type.
 
+* :c:func:`PyCode_New` and :c:func:`PyCode_NewWithPosOnlyArgs` now take
+  an additional ``exception_table`` argument.
+  Using these functions should be avoided, if at all possible.
+  To get a custom code object: create a code object using the compiler,
+  then get a modified version with the ``replace`` method.
+
 New Features
 ------------
 
diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py
index a37ebd27dc388..1bb138e7f3243 100644
--- a/Lib/test/test_code.py
+++ b/Lib/test/test_code.py
@@ -176,6 +176,9 @@ def test_newempty(self):
         self.assertEqual(co.co_filename, "filename")
         self.assertEqual(co.co_name, "funcname")
         self.assertEqual(co.co_firstlineno, 15)
+        #Empty code object should raise, but not crash the VM
+        with self.assertRaises(Exception):
+            exec(co)
 
     @cpython_only
     def test_closure_injection(self):
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index 9a57815882756..4fc4b8fec68a2 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -626,12 +626,20 @@ PyCode_New(int argcount, int kwonlyargcount,
                                      exceptiontable);
 }
 
+static const char assert0[4] = {
+    LOAD_ASSERTION_ERROR,
+    0,
+    RAISE_VARARGS,
+    1
+};
+
 PyCodeObject *
 PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
 {
     PyObject *nulltuple = NULL;
     PyObject *filename_ob = NULL;
     PyObject *funcname_ob = NULL;
+    PyObject *code_ob = NULL;
     PyCodeObject *result = NULL;
 
     nulltuple = PyTuple_New(0);
@@ -646,13 +654,17 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
     if (filename_ob == NULL) {
         goto failed;
     }
+    code_ob = PyBytes_FromStringAndSize(assert0, 4);
+    if (code_ob == NULL) {
+        goto failed;
+    }
 
 #define emptystring (PyObject *)&_Py_SINGLETON(bytes_empty)
     struct _PyCodeConstructor con = {
         .filename = filename_ob,
         .name = funcname_ob,
         .qualname = funcname_ob,
-        .code = emptystring,
+        .code = code_ob,
         .firstlineno = firstlineno,
         .linetable = emptystring,
         .consts = nulltuple,
@@ -660,6 +672,7 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
         .localsplusnames = nulltuple,
         .localspluskinds = emptystring,
         .exceptiontable = emptystring,
+        .stacksize = 1,
     };
     result = _PyCode_New(&con);
 
@@ -667,6 +680,7 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
     Py_XDECREF(nulltuple);
     Py_XDECREF(funcname_ob);
     Py_XDECREF(filename_ob);
+    Py_XDECREF(code_ob);
     return result;
 }
 



More information about the Python-checkins mailing list