]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-81137: deprecate assignment of code object to a function of a mismatched type...
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>
Tue, 7 Nov 2023 18:54:36 +0000 (18:54 +0000)
committerGitHub <noreply@github.com>
Tue, 7 Nov 2023 18:54:36 +0000 (18:54 +0000)
Doc/whatsnew/3.13.rst
Lib/test/test_funcattrs.py
Misc/NEWS.d/next/Core and Builtins/2023-11-07-12-59-02.gh-issue-81137.qFpJCY.rst [new file with mode: 0644]
Objects/funcobject.c

index cca282727ab951c20e75125723b14e4d36f0bb5b..84d50a68eace4b9fe1c890777bb603ec05a63d72 100644 (file)
@@ -401,6 +401,12 @@ Deprecated
   (as has always been the case for an executing frame).
   (Contributed by Irit Katriel in :gh:`79932`.)
 
+* Assignment to a function's ``__code__`` attribute where the new code
+  object's type does not match the function's type, is deprecated. The
+  different types are: plain function, generator, async generator and
+  coroutine.
+  (Contributed by Irit Katriel in :gh:`81137`.)
+
 
 Pending Removal in Python 3.14
 ------------------------------
index 35b473d5e9a0b4aac99d0072bbbd23434e277734..b3fc5ad42e7fde472f6c2768fee8d5d6c739db3f 100644 (file)
@@ -2,6 +2,7 @@ import textwrap
 import types
 import typing
 import unittest
+import warnings
 
 
 def global_function():
@@ -70,6 +71,27 @@ class FunctionPropertiesTest(FuncAttrsTest):
         test.__code__ = self.b.__code__
         self.assertEqual(test(), 3) # self.b always returns 3, arbitrarily
 
+    def test_invalid___code___assignment(self):
+        def A(): pass
+        def B(): yield
+        async def C(): yield
+        async def D(x): await x
+
+        for src in [A, B, C, D]:
+            for dst in [A, B, C, D]:
+                if src == dst:
+                    continue
+
+                assert src.__code__.co_flags != dst.__code__.co_flags
+                prev = dst.__code__
+                try:
+                    with self.assertWarnsRegex(DeprecationWarning, 'code object of non-matching type'):
+                        dst.__code__ = src.__code__
+                finally:
+                    with warnings.catch_warnings():
+                        warnings.filterwarnings('ignore', '', DeprecationWarning)
+                        dst.__code__ = prev
+
     def test___globals__(self):
         self.assertIs(self.b.__globals__, globals())
         self.cannot_set_attr(self.b, '__globals__', 2,
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-11-07-12-59-02.gh-issue-81137.qFpJCY.rst b/Misc/NEWS.d/next/Core and Builtins/2023-11-07-12-59-02.gh-issue-81137.qFpJCY.rst
new file mode 100644 (file)
index 0000000..5ca1dda
--- /dev/null
@@ -0,0 +1,2 @@
+Deprecate assignment to a function's ``__code__`` field when the new code
+object is of a mismatched type (e.g., from a generator to a plain function).
index 8ce1bff7660618e1e22197bab7f54560cf05a2db..b3701ebc9de11180a86b14be4389cf42e8caaecf 100644 (file)
@@ -557,6 +557,20 @@ func_set_code(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignored))
                      nclosure, nfree);
         return -1;
     }
+
+    PyObject *func_code = PyFunction_GET_CODE(op);
+    int old_flags = ((PyCodeObject *)func_code)->co_flags;
+    int new_flags = ((PyCodeObject *)value)->co_flags;
+    int mask = CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR;
+    if ((old_flags & mask) != (new_flags & mask)) {
+        if (PyErr_Warn(PyExc_DeprecationWarning,
+            "Assigning a code object of non-matching type is deprecated "
+            "(e.g., from a generator to a plain function)") < 0)
+        {
+            return -1;
+        }
+    }
+
     handle_func_event(PyFunction_EVENT_MODIFY_CODE, op, value);
     _PyFunction_SetVersion(op, 0);
     Py_XSETREF(op->func_code, Py_NewRef(value));