summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>2023-11-07 18:54:36 (GMT)
committerGitHub <noreply@github.com>2023-11-07 18:54:36 (GMT)
commit2f9cb7e095370e38bde58c79c8a8ea7705eefdc2 (patch)
treefe3b2edfda6852374e8467a6682517f645f38452
parent178861b19324c94d98478e4c2bba075964c3baa4 (diff)
downloadcpython-2f9cb7e095370e38bde58c79c8a8ea7705eefdc2.zip
cpython-2f9cb7e095370e38bde58c79c8a8ea7705eefdc2.tar.gz
cpython-2f9cb7e095370e38bde58c79c8a8ea7705eefdc2.tar.bz2
gh-81137: deprecate assignment of code object to a function of a mismatched type (#111823)
-rw-r--r--Doc/whatsnew/3.13.rst6
-rw-r--r--Lib/test/test_funcattrs.py22
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2023-11-07-12-59-02.gh-issue-81137.qFpJCY.rst2
-rw-r--r--Objects/funcobject.c14
4 files changed, 44 insertions, 0 deletions
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index cca2827..84d50a6 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -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
------------------------------
diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py
index 35b473d..b3fc5ad 100644
--- a/Lib/test/test_funcattrs.py
+++ b/Lib/test/test_funcattrs.py
@@ -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
index 0000000..5ca1dda
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-11-07-12-59-02.gh-issue-81137.qFpJCY.rst
@@ -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).
diff --git a/Objects/funcobject.c b/Objects/funcobject.c
index 8ce1bff..b3701eb 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -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));