From eab4c1359010b184f6157a95add3e8363417ac5e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 6 May 2025 08:04:59 -0600 Subject: [PATCH 1/5] Revert "gh-132775: Revert "gh-132775: Add _PyCode_VerifyStateless() (gh-133221)" (#133497)" This reverts commit 3c73cf51df2643cc144b03ac1003588d8822d5b5. --- Include/internal/pycore_code.h | 41 ++++++ Include/internal/pycore_function.h | 7 + Include/internal/pycore_opcode_utils.h | 2 + Lib/test/_code_definitions.py | 26 ++++ Lib/test/test_code.py | 54 +++++--- Modules/_testinternalcapi.c | 43 +++++++ Objects/codeobject.c | 172 ++++++++++++++++++++++--- Objects/funcobject.c | 54 ++++++++ 8 files changed, 362 insertions(+), 37 deletions(-) diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index b135e30b7ad62a..bd68e7ec4f5c4d 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -614,6 +614,47 @@ PyAPI_FUNC(int) _PyCode_SetUnboundVarCounts( PyObject *globalsns, PyObject *builtinsns); + +/* "Stateless" code is a function or code object which does not rely on + * external state or internal state. It may rely on arguments and + * builtins, but not globals or a closure. Thus it does not rely + * on __globals__ or __closure__, and a stateless function + * is equivalent to its code object. + * + * Stateless code also does not keep any persistent state + * of its own, so it can't have any executors, monitoring, + * instrumentation, or "extras" (i.e. co_extra). + * + * Stateless code may create nested functions, including closures. + * However, nested functions must themselves be stateless, except they + * *can* close on the enclosing locals. + * + * Stateless code may return any value, including nested functions and closures. + * + * Stateless code that takes no arguments and doesn't return anything + * may be treated like a script. + * + * We consider stateless code to be "portable" if it does not return any + * any object that holds a reference to any of the code's locals. Thus + * generators and coroutines are not portable. Likewise a function + * that returns a closure is not portable. The concept of + * portability is useful in cases where the code is run + * in a different execution context than where + * the return value will be used. */ + +PyAPI_FUNC(int) _PyCode_CheckNoInternalState(PyCodeObject *, const char **); +PyAPI_FUNC(int) _PyCode_CheckNoExternalState( + PyCodeObject *, + _PyCode_var_counts_t *, + const char **); +PyAPI_FUNC(int) _PyCode_VerifyStateless( + PyThreadState *, + PyCodeObject *, + PyObject *globalnames, + PyObject *globalsns, + PyObject *builtinsns); + +PyAPI_FUNC(int) _PyCode_CheckPureFunction(PyCodeObject *, const char **); PyAPI_FUNC(int) _PyCode_ReturnsOnlyNone(PyCodeObject *); diff --git a/Include/internal/pycore_function.h b/Include/internal/pycore_function.h index 209252b2ddc0de..a30d52d49bdc4d 100644 --- a/Include/internal/pycore_function.h +++ b/Include/internal/pycore_function.h @@ -35,6 +35,13 @@ PyFunctionObject *_PyFunction_LookupByVersion(uint32_t version, PyObject **p_cod extern PyObject *_Py_set_function_type_params( PyThreadState* unused, PyObject *func, PyObject *type_params); + +/* See pycore_code.h for explanation about what "stateless" means. */ + +PyAPI_FUNC(int) +_PyFunction_VerifyStateless(PyThreadState *, PyObject *); + + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_opcode_utils.h b/Include/internal/pycore_opcode_utils.h index 62af06dc01c125..79a1a242556a52 100644 --- a/Include/internal/pycore_opcode_utils.h +++ b/Include/internal/pycore_opcode_utils.h @@ -56,6 +56,8 @@ extern "C" { #define IS_RETURN_OPCODE(opcode) \ (opcode == RETURN_VALUE) +#define IS_RAISE_OPCODE(opcode) \ + (opcode == RAISE_VARARGS || opcode == RERAISE) /* Flags used in the oparg for MAKE_FUNCTION */ diff --git a/Lib/test/_code_definitions.py b/Lib/test/_code_definitions.py index c3daa0dccf5df3..d64ac45d85f396 100644 --- a/Lib/test/_code_definitions.py +++ b/Lib/test/_code_definitions.py @@ -178,6 +178,32 @@ def ham_C_closure(z): *NESTED_FUNCTIONS, ] +STATELESS_FUNCTIONS = [ + spam, + spam_minimal, + spam_with_builtins, + spam_args_attrs_and_builtins, + spam_returns_arg, + spam_annotated, + spam_with_inner_not_closure, + spam_with_inner_closure, + spam_N, + spam_C, + spam_NN, + spam_NC, + spam_CN, + spam_CC, + eggs_nested, + eggs_nested_N, + ham_nested, + ham_C_nested +] +STATELESS_CODE = [ + *STATELESS_FUNCTIONS, + spam_with_globals_and_builtins, + spam_full, +] + # generators diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index b646042a3b84b0..6715ee051336a1 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -220,6 +220,7 @@ import _testinternalcapi except ModuleNotFoundError: _testinternalcapi = None +import test._code_definitions as defs COPY_FREE_VARS = opmap['COPY_FREE_VARS'] @@ -671,7 +672,6 @@ def test_local_kinds(self): VARARGS = CO_FAST_LOCAL | CO_FAST_ARG_VAR | CO_FAST_ARG_POS VARKWARGS = CO_FAST_LOCAL | CO_FAST_ARG_VAR | CO_FAST_ARG_KW - import test._code_definitions as defs funcs = { defs.spam_minimal: {}, defs.spam_with_builtins: { @@ -897,7 +897,6 @@ def new_var_counts(*, }, } - import test._code_definitions as defs funcs = { defs.spam_minimal: new_var_counts(), defs.spam_with_builtins: new_var_counts( @@ -1025,42 +1024,35 @@ def new_var_counts(*, counts = _testinternalcapi.get_code_var_counts(func.__code__) self.assertEqual(counts, expected) - def func_with_globals_and_builtins(): - mod1 = _testinternalcapi - mod2 = dis - mods = (mod1, mod2) - checks = tuple(callable(m) for m in mods) - return callable(mod2), tuple(mods), list(mods), checks - - func = func_with_globals_and_builtins + func = defs.spam_with_globals_and_builtins with self.subTest(f'{func} code'): expected = new_var_counts( - purelocals=4, - globalvars=5, + purelocals=5, + globalvars=6, ) counts = _testinternalcapi.get_code_var_counts(func.__code__) self.assertEqual(counts, expected) with self.subTest(f'{func} with own globals and builtins'): expected = new_var_counts( - purelocals=4, - globalvars=(2, 3), + purelocals=5, + globalvars=(2, 4), ) counts = _testinternalcapi.get_code_var_counts(func) self.assertEqual(counts, expected) with self.subTest(f'{func} without globals'): expected = new_var_counts( - purelocals=4, - globalvars=(0, 3, 2), + purelocals=5, + globalvars=(0, 4, 2), ) counts = _testinternalcapi.get_code_var_counts(func, globalsns={}) self.assertEqual(counts, expected) with self.subTest(f'{func} without both'): expected = new_var_counts( - purelocals=4, - globalvars=5, + purelocals=5, + globalvars=6, ) counts = _testinternalcapi.get_code_var_counts(func, globalsns={}, builtinsns={}) @@ -1068,12 +1060,34 @@ def func_with_globals_and_builtins(): with self.subTest(f'{func} without builtins'): expected = new_var_counts( - purelocals=4, - globalvars=(2, 0, 3), + purelocals=5, + globalvars=(2, 0, 4), ) counts = _testinternalcapi.get_code_var_counts(func, builtinsns={}) self.assertEqual(counts, expected) + @unittest.skipIf(_testinternalcapi is None, "missing _testinternalcapi") + def test_stateless(self): + self.maxDiff = None + + for func in defs.STATELESS_CODE: + with self.subTest((func, '(code)')): + _testinternalcapi.verify_stateless_code(func.__code__) + for func in defs.STATELESS_FUNCTIONS: + with self.subTest((func, '(func)')): + _testinternalcapi.verify_stateless_code(func) + + for func in defs.FUNCTIONS: + if func not in defs.STATELESS_CODE: + with self.subTest((func, '(code)')): + with self.assertRaises(Exception): + _testinternalcapi.verify_stateless_code(func.__code__) + + if func not in defs.STATELESS_FUNCTIONS: + with self.subTest((func, '(func)')): + with self.assertRaises(Exception): + _testinternalcapi.verify_stateless_code(func) + def isinterned(s): return s is sys.intern(('_' + s + '_')[1:-1]) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index ae060c95fd5a01..63f1d079d8d312 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1165,6 +1165,47 @@ get_code_var_counts(PyObject *self, PyObject *_args, PyObject *_kwargs) return NULL; } +static PyObject * +verify_stateless_code(PyObject *self, PyObject *args, PyObject *kwargs) +{ + PyThreadState *tstate = _PyThreadState_GET(); + PyObject *codearg; + PyObject *globalnames = NULL; + PyObject *globalsns = NULL; + PyObject *builtinsns = NULL; + static char *kwlist[] = {"code", "globalnames", + "globalsns", "builtinsns", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "O|O!O!O!:get_code_var_counts", kwlist, + &codearg, &PySet_Type, &globalnames, + &PyDict_Type, &globalsns, &PyDict_Type, &builtinsns)) + { + return NULL; + } + if (PyFunction_Check(codearg)) { + if (globalsns == NULL) { + globalsns = PyFunction_GET_GLOBALS(codearg); + } + if (builtinsns == NULL) { + builtinsns = PyFunction_GET_BUILTINS(codearg); + } + codearg = PyFunction_GET_CODE(codearg); + } + else if (!PyCode_Check(codearg)) { + PyErr_SetString(PyExc_TypeError, + "argument must be a code object or a function"); + return NULL; + } + PyCodeObject *code = (PyCodeObject *)codearg; + + if (_PyCode_VerifyStateless( + tstate, code, globalnames, globalsns, builtinsns) < 0) + { + return NULL; + } + Py_RETURN_NONE; +} + #ifdef _Py_TIER2 static PyObject * @@ -2292,6 +2333,8 @@ static PyMethodDef module_functions[] = { {"get_co_localskinds", get_co_localskinds, METH_O, NULL}, {"get_code_var_counts", _PyCFunction_CAST(get_code_var_counts), METH_VARARGS | METH_KEYWORDS, NULL}, + {"verify_stateless_code", _PyCFunction_CAST(verify_stateless_code), + METH_VARARGS | METH_KEYWORDS, NULL}, #ifdef _Py_TIER2 {"add_executor_dependency", add_executor_dependency, METH_VARARGS, NULL}, {"invalidate_executors", invalidate_executors, METH_O, NULL}, diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 92eaf5e00bc7dd..e9ac209a95f6c7 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -1955,12 +1955,134 @@ _PyCode_SetUnboundVarCounts(PyThreadState *tstate, } +int +_PyCode_CheckNoInternalState(PyCodeObject *co, const char **p_errmsg) +{ + const char *errmsg = NULL; + if (_PyCode_HAS_EXECUTORS(co) || _PyCode_HAS_INSTRUMENTATION(co)) { + errmsg = "only basic code objects are supported"; + } + else if (co->_co_monitoring != NULL) { + errmsg = "only basic code objects are supported"; + } + else if (co->co_extra != NULL) { + errmsg = "only basic code objects are supported"; + } + + if (errmsg != NULL) { + if (p_errmsg != NULL) { + *p_errmsg = errmsg; + } + return 0; + } + return 1; +} + +int +_PyCode_CheckNoExternalState(PyCodeObject *co, _PyCode_var_counts_t *counts, + const char **p_errmsg) +{ + const char *errmsg = NULL; + assert(counts->locals.hidden.total == 0); + if (counts->numfree > 0) { // It's a closure. + errmsg = "closures not supported"; + } + else if (counts->unbound.globals.numglobal > 0) { + errmsg = "globals not supported"; + } + else if (counts->unbound.globals.numbuiltin > 0 + && counts->unbound.globals.numunknown > 0) + { + errmsg = "globals not supported"; + } + // Otherwise we don't check counts.unbound.globals.numunknown since we can't + // distinguish beween globals and builtins here. + + if (errmsg != NULL) { + if (p_errmsg != NULL) { + *p_errmsg = errmsg; + } + return 0; + } + return 1; +} + +int +_PyCode_VerifyStateless(PyThreadState *tstate, + PyCodeObject *co, PyObject *globalnames, + PyObject *globalsns, PyObject *builtinsns) +{ + const char *errmsg; + _PyCode_var_counts_t counts = {0}; + _PyCode_GetVarCounts(co, &counts); + if (_PyCode_SetUnboundVarCounts( + tstate, co, &counts, globalnames, NULL, + globalsns, builtinsns) < 0) + { + return -1; + } + // We may consider relaxing the internal state constraints + // if it becomes a problem. + if (!_PyCode_CheckNoInternalState(co, &errmsg)) { + _PyErr_SetString(tstate, PyExc_ValueError, errmsg); + return -1; + } + if (builtinsns != NULL) { + // Make sure the next check will fail for globals, + // even if there aren't any builtins. + counts.unbound.globals.numbuiltin += 1; + } + if (!_PyCode_CheckNoExternalState(co, &counts, &errmsg)) { + _PyErr_SetString(tstate, PyExc_ValueError, errmsg); + return -1; + } + // Note that we don't check co->co_flags & CO_NESTED for anything here. + return 0; +} + + +int +_PyCode_CheckPureFunction(PyCodeObject *co, const char **p_errmsg) +{ + const char *errmsg = NULL; + if (co->co_flags & CO_GENERATOR) { + errmsg = "generators not supported"; + } + else if (co->co_flags & CO_COROUTINE) { + errmsg = "coroutines not supported"; + } + else if (co->co_flags & CO_ITERABLE_COROUTINE) { + errmsg = "coroutines not supported"; + } + else if (co->co_flags & CO_ASYNC_GENERATOR) { + errmsg = "generators not supported"; + } + + if (errmsg != NULL) { + if (p_errmsg != NULL) { + *p_errmsg = errmsg; + } + return 0; + } + return 1; +} + /* Here "value" means a non-None value, since a bare return is identical * to returning None explicitly. Likewise a missing return statement * at the end of the function is turned into "return None". */ static int code_returns_only_none(PyCodeObject *co) { + if (!_PyCode_CheckPureFunction(co, NULL)) { + return 0; + } + int len = (int)Py_SIZE(co); + assert(len > 0); + + // The last instruction either returns or raises. We can take advantage + // of that for a quick exit. + _Py_CODEUNIT final = _Py_GetBaseCodeUnit(co, len-1); + // Look up None in co_consts. Py_ssize_t nconsts = PyTuple_Size(co->co_consts); int none_index = 0; @@ -1971,26 +2093,42 @@ code_returns_only_none(PyCodeObject *co) } if (none_index == nconsts) { // None wasn't there, which means there was no implicit return, - // "return", or "return None". That means there must be - // an explicit return (non-None). - return 0; - } + // "return", or "return None". - // Walk the bytecode, looking for RETURN_VALUE. - Py_ssize_t len = Py_SIZE(co); - for (int i = 0; i < len; i += _PyInstruction_GetLength(co, i)) { - _Py_CODEUNIT inst = _Py_GetBaseCodeUnit(co, i); - if (IS_RETURN_OPCODE(inst.op.code)) { - assert(i != 0); - // Ignore it if it returns None. - _Py_CODEUNIT prev = _Py_GetBaseCodeUnit(co, i-1); - if (prev.op.code == LOAD_CONST) { - // We don't worry about EXTENDED_ARG for now. - if (prev.op.arg == none_index) { - continue; + // That means there must be + // an explicit return (non-None), or it only raises. + if (IS_RETURN_OPCODE(final.op.code)) { + // It was an explicit return (non-None). + return 0; + } + // It must end with a raise then. We still have to walk the + // bytecode to see if there's any explicit return (non-None). + assert(IS_RAISE_OPCODE(final.op.code)); + for (int i = 0; i < len; i += _PyInstruction_GetLength(co, i)) { + _Py_CODEUNIT inst = _Py_GetBaseCodeUnit(co, i); + if (IS_RETURN_OPCODE(inst.op.code)) { + // We alraedy know it isn't returning None. + return 0; + } + } + // It must only raise. + } + else { + // Walk the bytecode, looking for RETURN_VALUE. + for (int i = 0; i < len; i += _PyInstruction_GetLength(co, i)) { + _Py_CODEUNIT inst = _Py_GetBaseCodeUnit(co, i); + if (IS_RETURN_OPCODE(inst.op.code)) { + assert(i != 0); + // Ignore it if it returns None. + _Py_CODEUNIT prev = _Py_GetBaseCodeUnit(co, i-1); + if (prev.op.code == LOAD_CONST) { + // We don't worry about EXTENDED_ARG for now. + if (prev.op.arg == none_index) { + continue; + } } + return 0; } - return 0; } } return 1; diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 56df5730db0c55..27214a129c2fb8 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -1,12 +1,14 @@ /* Function object implementation */ #include "Python.h" +#include "pycore_code.h" // _PyCode_VerifyStateless() #include "pycore_dict.h" // _Py_INCREF_DICT() #include "pycore_function.h" // _PyFunction_Vectorcall #include "pycore_long.h" // _PyLong_GetOne() #include "pycore_modsupport.h" // _PyArg_NoKeywords() #include "pycore_object.h" // _PyObject_GC_UNTRACK() #include "pycore_pyerrors.h" // _PyErr_Occurred() +#include "pycore_setobject.h" // _PySet_NextEntry() #include "pycore_stats.h" @@ -1240,6 +1242,58 @@ PyTypeObject PyFunction_Type = { }; +int +_PyFunction_VerifyStateless(PyThreadState *tstate, PyObject *func) +{ + assert(!PyErr_Occurred()); + assert(PyFunction_Check(func)); + + // Check the globals. + PyObject *globalsns = PyFunction_GET_GLOBALS(func); + if (globalsns != NULL && !PyDict_Check(globalsns)) { + _PyErr_Format(tstate, PyExc_TypeError, + "unsupported globals %R", globalsns); + return -1; + } + // Check the builtins. + PyObject *builtinsns = PyFunction_GET_BUILTINS(func); + if (builtinsns != NULL && !PyDict_Check(builtinsns)) { + _PyErr_Format(tstate, PyExc_TypeError, + "unsupported builtins %R", builtinsns); + return -1; + } + // Disallow __defaults__. + PyObject *defaults = PyFunction_GET_DEFAULTS(func); + if (defaults != NULL && defaults != Py_None && PyDict_Size(defaults) > 0) + { + _PyErr_SetString(tstate, PyExc_ValueError, "defaults not supported"); + return -1; + } + // Disallow __kwdefaults__. + PyObject *kwdefaults = PyFunction_GET_KW_DEFAULTS(func); + if (kwdefaults != NULL && kwdefaults != Py_None + && PyDict_Size(kwdefaults) > 0) + { + _PyErr_SetString(tstate, PyExc_ValueError, + "keyword defaults not supported"); + return -1; + } + // Disallow __closure__. + PyObject *closure = PyFunction_GET_CLOSURE(func); + if (closure != NULL && closure != Py_None && PyTuple_GET_SIZE(closure) > 0) + { + _PyErr_SetString(tstate, PyExc_ValueError, "closures not supported"); + return -1; + } + // Check the code. + PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func); + if (_PyCode_VerifyStateless(tstate, co, NULL, globalsns, builtinsns) < 0) { + return -1; + } + return 0; +} + + static int functools_copy_attr(PyObject *wrapper, PyObject *wrapped, PyObject *name) { From c36747cd1a8a4b80656ff36a08025d6dc3458ac1 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 6 May 2025 10:25:29 -0600 Subject: [PATCH 2/5] Relax the constraints in _PyCode_CheckNoInternalState(). --- Objects/codeobject.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Objects/codeobject.c b/Objects/codeobject.c index e9ac209a95f6c7..4f06a36a130207 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -1959,13 +1959,9 @@ int _PyCode_CheckNoInternalState(PyCodeObject *co, const char **p_errmsg) { const char *errmsg = NULL; - if (_PyCode_HAS_EXECUTORS(co) || _PyCode_HAS_INSTRUMENTATION(co)) { - errmsg = "only basic code objects are supported"; - } - else if (co->_co_monitoring != NULL) { - errmsg = "only basic code objects are supported"; - } - else if (co->co_extra != NULL) { + // We don't worry about co_executors, co_instrumentation, + // or co_monitoring. They are essentially ephemeral. + if (co->co_extra != NULL) { errmsg = "only basic code objects are supported"; } From b943bb0183f385256ca0b5cfe352af713c83dbd4 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 6 May 2025 10:25:55 -0600 Subject: [PATCH 3/5] Add a test for co_extra. --- Lib/test/test_code.py | 12 +++++++++ Modules/_testinternalcapi.c | 54 +++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 6715ee051336a1..1fa110f5335408 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -1088,6 +1088,18 @@ def test_stateless(self): with self.assertRaises(Exception): _testinternalcapi.verify_stateless_code(func) + def spam(): + pass + + with self.subTest('with co_extra'): + _testinternalcapi.verify_stateless_code(spam) + extra = 'spam' + _testinternalcapi.code_set_co_extra(spam.__code__, 0, extra) + with self.assertRaises(ValueError): + _testinternalcapi.verify_stateless_code(spam) + _testinternalcapi.code_set_co_extra(spam.__code__, 0, expect=extra) + _testinternalcapi.verify_stateless_code(spam) + def isinterned(s): return s is sys.intern(('_' + s + '_')[1:-1]) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 63f1d079d8d312..7233620a3da861 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1206,6 +1206,58 @@ verify_stateless_code(PyObject *self, PyObject *args, PyObject *kwargs) Py_RETURN_NONE; } +static PyObject * +code_set_co_extra(PyObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *codearg; + Py_ssize_t index; + PyObject *value = NULL; + PyObject *expected = NULL; + PyObject *notset = Py_None; + static char *kwlist[] = + {"code", "index", "value", "expect", "notset", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "O!n|OOO:code_set_co_extra", kwlist, + &PyCode_Type, &codearg, &index, &value, &expected, ¬set)) + { + return NULL; + } + + void *extra; + if (PyUnstable_Code_GetExtra(codearg, index, &extra) < 0) { + return NULL; + } + + PyObject *old; + if (extra == NULL) { + old = Py_NewRef(notset); + } + else if (extra == expected) { + old = Py_NewRef(expected); + } + else if (extra == value) { + old = Py_NewRef(value); + } + else { + if (expected == NULL) { + PyErr_SetString(PyExc_ValueError, + "expected existing co_extra to be NULL"); + } + else { + PyErr_Format(PyExc_ValueError, + "expected existing co_extra to be %R", expected); + } + return NULL; + } + + if (PyUnstable_Code_SetExtra(codearg, index, value) < 0) { + Py_DECREF(old); + return NULL; + } + + return old; +} + #ifdef _Py_TIER2 static PyObject * @@ -2335,6 +2387,8 @@ static PyMethodDef module_functions[] = { METH_VARARGS | METH_KEYWORDS, NULL}, {"verify_stateless_code", _PyCFunction_CAST(verify_stateless_code), METH_VARARGS | METH_KEYWORDS, NULL}, + {"code_set_co_extra", _PyCFunction_CAST(code_set_co_extra), + METH_VARARGS | METH_KEYWORDS, NULL}, #ifdef _Py_TIER2 {"add_executor_dependency", add_executor_dependency, METH_VARARGS, NULL}, {"invalidate_executors", invalidate_executors, METH_O, NULL}, From bcc081ba3b5df7c564dba228ff226b6f3792664f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 6 May 2025 10:26:07 -0600 Subject: [PATCH 4/5] Drop _testinternalcapi.code_set_co_extra(). This reverts commit b943bb0183f385256ca0b5cfe352af713c83dbd4. --- Lib/test/test_code.py | 12 --------- Modules/_testinternalcapi.c | 54 ------------------------------------- 2 files changed, 66 deletions(-) diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 1fa110f5335408..6715ee051336a1 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -1088,18 +1088,6 @@ def test_stateless(self): with self.assertRaises(Exception): _testinternalcapi.verify_stateless_code(func) - def spam(): - pass - - with self.subTest('with co_extra'): - _testinternalcapi.verify_stateless_code(spam) - extra = 'spam' - _testinternalcapi.code_set_co_extra(spam.__code__, 0, extra) - with self.assertRaises(ValueError): - _testinternalcapi.verify_stateless_code(spam) - _testinternalcapi.code_set_co_extra(spam.__code__, 0, expect=extra) - _testinternalcapi.verify_stateless_code(spam) - def isinterned(s): return s is sys.intern(('_' + s + '_')[1:-1]) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 7233620a3da861..63f1d079d8d312 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1206,58 +1206,6 @@ verify_stateless_code(PyObject *self, PyObject *args, PyObject *kwargs) Py_RETURN_NONE; } -static PyObject * -code_set_co_extra(PyObject *self, PyObject *args, PyObject *kwargs) -{ - PyObject *codearg; - Py_ssize_t index; - PyObject *value = NULL; - PyObject *expected = NULL; - PyObject *notset = Py_None; - static char *kwlist[] = - {"code", "index", "value", "expect", "notset", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "O!n|OOO:code_set_co_extra", kwlist, - &PyCode_Type, &codearg, &index, &value, &expected, ¬set)) - { - return NULL; - } - - void *extra; - if (PyUnstable_Code_GetExtra(codearg, index, &extra) < 0) { - return NULL; - } - - PyObject *old; - if (extra == NULL) { - old = Py_NewRef(notset); - } - else if (extra == expected) { - old = Py_NewRef(expected); - } - else if (extra == value) { - old = Py_NewRef(value); - } - else { - if (expected == NULL) { - PyErr_SetString(PyExc_ValueError, - "expected existing co_extra to be NULL"); - } - else { - PyErr_Format(PyExc_ValueError, - "expected existing co_extra to be %R", expected); - } - return NULL; - } - - if (PyUnstable_Code_SetExtra(codearg, index, value) < 0) { - Py_DECREF(old); - return NULL; - } - - return old; -} - #ifdef _Py_TIER2 static PyObject * @@ -2387,8 +2335,6 @@ static PyMethodDef module_functions[] = { METH_VARARGS | METH_KEYWORDS, NULL}, {"verify_stateless_code", _PyCFunction_CAST(verify_stateless_code), METH_VARARGS | METH_KEYWORDS, NULL}, - {"code_set_co_extra", _PyCFunction_CAST(code_set_co_extra), - METH_VARARGS | METH_KEYWORDS, NULL}, #ifdef _Py_TIER2 {"add_executor_dependency", add_executor_dependency, METH_VARARGS, NULL}, {"invalidate_executors", invalidate_executors, METH_O, NULL}, From 28733837a9ece8665dadf5bf46a456f5680089e4 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 7 May 2025 17:18:25 -0600 Subject: [PATCH 5/5] Fix a typo. --- Include/internal/pycore_code.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index bd68e7ec4f5c4d..37a747aa4e3e46 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -634,7 +634,7 @@ PyAPI_FUNC(int) _PyCode_SetUnboundVarCounts( * Stateless code that takes no arguments and doesn't return anything * may be treated like a script. * - * We consider stateless code to be "portable" if it does not return any + * We consider stateless code to be "portable" if it does not return * any object that holds a reference to any of the code's locals. Thus * generators and coroutines are not portable. Likewise a function * that returns a closure is not portable. The concept of pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy