Skip to content

Commit

Permalink
pythongh-81137: deprecate assignment of code object to a function of …
Browse files Browse the repository at this point in the history
…a mismatched type (python#111823)
  • Loading branch information
iritkatriel authored and hugovk committed Nov 8, 2023
1 parent 32be960 commit 143cf0f
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.13.rst
Expand Up @@ -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
------------------------------
Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_funcattrs.py
Expand Up @@ -2,6 +2,7 @@
import types
import typing
import unittest
import warnings


def global_function():
Expand Down Expand Up @@ -70,6 +71,27 @@ def test(): pass
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,
Expand Down
@@ -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).
14 changes: 14 additions & 0 deletions Objects/funcobject.c
Expand Up @@ -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));
Expand Down

0 comments on commit 143cf0f

Please sign in to comment.