diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 0623adce693d465..5b8f65357bbc1cf 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -823,6 +823,17 @@ typedef _Py_CODEUNIT *(*_PyJitEntryFuncPtr)(struct _PyExecutorObject *exec, _PyI #define _PyInterpreterGuard_GUARDS_NOT_ALLOWED UINTPTR_MAX +typedef struct { + PyTypeObject *async_gen_hooks_type; + PyTypeObject *flags_type; +#if defined(MS_WINDOWS) + PyTypeObject *windows_version_type; +#endif +#ifdef __EMSCRIPTEN__ + PyTypeObject *emscripten_info_type; +#endif +} _PySys_State; + /* PyInterpreterState holds the global state for one of the runtime's interpreters. Typically the initial (main) interpreter is the only one. @@ -899,6 +910,7 @@ struct _is { // Dictionary of the sys module PyObject *sysdict; + _PySys_State sys_state; // Dictionary of the builtins module PyObject *builtins; diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 74d3794289bf69f..297e35dbe712a78 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -3464,3 +3464,9 @@ def skip_on_low_desktop_heap_memory_subprocess(returncode): if returncode == STATUS_DLL_INIT_FAILED: raise unittest.SkipTest('gh-150436: DLL init failed, likely because ' 'of low desktop heap memory') + + +def check_immutable_type(testcase, type): + regex = r'cannot set .* attribute of immutable type' + with testcase.assertRaisesRegex(TypeError, regex): + setattr(type, 'custom_attr', 123) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index b8c09c7f43e3e3b..a0ba5a8351aebdf 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -32,6 +32,7 @@ import unittest import numbers import locale +from test import support from test.support import (is_resource_enabled, requires_IEEE_754, requires_docstrings, check_disallow_instantiation) @@ -5806,8 +5807,7 @@ def test_c_immutable_types(self): ) for tp in types: with self.subTest(tp=tp): - with self.assertRaisesRegex(TypeError, "immutable"): - tp.foo = 1 + support.check_immutable_type(self, tp) def test_c_disallow_instantiation(self): ContextManager = type(C.localcontext()) diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index cf579d4da4e0dfb..d47f9acf019dca6 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -1541,8 +1541,7 @@ def test_immutable_types(self): ) for tp in dataset: with self.subTest(tp=tp): - with self.assertRaisesRegex(TypeError, "immutable"): - tp.foobar = 1 + support.check_immutable_type(self, tp) class TestExamples(unittest.TestCase): diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index dab03ef06a8b8e5..f2adce532595e70 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -425,6 +425,7 @@ def test_getwindowsversion(self): self.assertEqual(v[2], v.build) self.assertEqual(v[3], v.platform) self.assertEqual(v[4], v.service_pack) + support.check_immutable_type(self, type(v)) # This is how platform.py calls it. Make sure tuple # still has 5 elements @@ -690,6 +691,7 @@ def test_attributes(self): self.assertEqual(algo, 0) self.assertGreaterEqual(sys.hash_info.cutoff, 0) self.assertLess(sys.hash_info.cutoff, 8) + support.check_immutable_type(self, type(sys.hash_info)) self.assertIsInstance(sys.maxsize, int) self.assertIsInstance(sys.maxunicode, int) @@ -893,11 +895,13 @@ def assert_raise_on_new_sys_type(self, sys_attr): # sys.flags, sys.version_info, and sys.getwindowsversion. support.check_disallow_instantiation(self, type(sys_attr), sys_attr) - def test_sys_flags_no_instantiation(self): + def test_sys_flags_type(self): self.assert_raise_on_new_sys_type(sys.flags) + support.check_immutable_type(self, type(sys.flags)) - def test_sys_version_info_no_instantiation(self): + def test_sys_version_info_type(self): self.assert_raise_on_new_sys_type(sys.version_info) + support.check_immutable_type(self, type(sys.version_info)) def test_sys_getwindowsversion_no_instantiation(self): # Skip if not being run on Windows. @@ -1954,6 +1958,7 @@ def test_asyncgen_hooks(self): cur = sys.get_asyncgen_hooks() self.assertIsNone(cur.firstiter) self.assertIsNone(cur.finalizer) + support.check_immutable_type(self, type(cur)) # gh-118473 with self.assertRaises(TypeError): @@ -1997,6 +2002,7 @@ def write(self, s): self.assertEqual(out, b"") self.assertEqual(err, b"") + @test.support.support_remote_exec_only @test.support.cpython_only class TestRemoteExec(unittest.TestCase): diff --git a/Lib/test/test_xml_etree_c.py b/Lib/test/test_xml_etree_c.py index 270b9d6da8e7b9e..2a18396afb60881 100644 --- a/Lib/test/test_xml_etree_c.py +++ b/Lib/test/test_xml_etree_c.py @@ -194,8 +194,7 @@ def test_immutable_types(self): ) for tp in dataset: with self.subTest(tp=tp): - with self.assertRaisesRegex(TypeError, "immutable"): - tp.foo = 1 + support.check_immutable_type(self, tp) @support.cpython_only def test_disallow_instantiation(self): diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 500a1a1949a5a8a..c9563d90c2357c9 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2072,7 +2072,6 @@ finalize_interp_types(PyInterpreterState *interp) { _PyTypes_FiniExtTypes(interp); _PyUnicode_FiniTypes(interp); - _PySys_FiniTypes(interp); _PyXI_FiniTypes(interp); _PyExc_Fini(interp); _PyFloat_FiniType(interp); @@ -2112,14 +2111,16 @@ finalize_interp_types(PyInterpreterState *interp) static void finalize_interp_clear(PyThreadState *tstate) { - int is_main_interp = _Py_IsMainInterpreter(tstate->interp); + PyInterpreterState *interp = tstate->interp; + int is_main_interp = _Py_IsMainInterpreter(interp); - _PyXI_Fini(tstate->interp); - _PyExc_ClearExceptionGroupType(tstate->interp); - _Py_clear_generic_types(tstate->interp); - _PyTypes_FiniCachedDescriptors(tstate->interp); + _PyXI_Fini(interp); + _PyExc_ClearExceptionGroupType(interp); + _Py_clear_generic_types(interp); + _PyTypes_FiniCachedDescriptors(interp); + _PySys_FiniTypes(interp); - /* Clear interpreter state and all thread states */ + /* Clear interpreter state and all thread states: last GC collection! */ _PyInterpreterState_Clear(tstate); /* Clear all loghooks */ @@ -2136,13 +2137,13 @@ finalize_interp_clear(PyThreadState *tstate) _PyPerfTrampoline_Fini(); } - finalize_interp_types(tstate->interp); + finalize_interp_types(interp); /* Finalize dtoa at last so that finalizers calling repr of float doesn't crash */ - _PyDtoa_Fini(tstate->interp); + _PyDtoa_Fini(interp); /* Free any delayed free requests immediately */ - _PyMem_FiniDelayed(tstate->interp); + _PyMem_FiniDelayed(interp); /* finalize_interp_types may allocate Python objects so we may need to abandon mimalloc segments again */ diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 1e6e914b066bc5c..8b1c2b2ea833154 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -35,7 +35,7 @@ Data members: #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_pystats.h" // _Py_PrintSpecializationStats() #include "pycore_runtime.h" // _PyRuntimeState_Get*() -#include "pycore_structseq.h" // _PyStructSequence_InitBuiltinWithFlags() +#include "pycore_structseq.h" // _PyStructSequence_NewType() #include "pycore_sysmodule.h" // export _PySys_GetSizeOf() #include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal() @@ -76,6 +76,13 @@ module sys #include "clinic/sysmodule.c.h" +static _PySys_State* +sys_get_state(PyInterpreterState *interp) +{ + return &interp->sys_state; +} + + PyObject * PySys_GetAttr(PyObject *name) { @@ -1414,8 +1421,6 @@ sys_get_coroutine_origin_tracking_depth_impl(PyObject *module) return _PyEval_GetCoroutineOriginTrackingDepth(); } -static PyTypeObject AsyncGenHooksType; - PyDoc_STRVAR(asyncgen_hooks_doc, "asyncgen_hooks\n\ \n\ @@ -1429,7 +1434,7 @@ static PyStructSequence_Field asyncgen_hooks_fields[] = { }; static PyStructSequence_Desc asyncgen_hooks_desc = { - "asyncgen_hooks", /* name */ + "sys.asyncgen_hooks", /* name */ asyncgen_hooks_doc, /* doc */ asyncgen_hooks_fields , /* fields */ 2 @@ -1514,8 +1519,10 @@ sys_get_asyncgen_hooks_impl(PyObject *module) PyObject *res; PyObject *firstiter = _PyEval_GetAsyncGenFirstiter(); PyObject *finalizer = _PyEval_GetAsyncGenFinalizer(); + PyInterpreterState *interp = _PyInterpreterState_GET(); + _PySys_State *state = sys_get_state(interp); - res = PyStructSequence_New(&AsyncGenHooksType); + res = PyStructSequence_New(state->async_gen_hooks_type); if (res == NULL) { return NULL; } @@ -1535,8 +1542,6 @@ sys_get_asyncgen_hooks_impl(PyObject *module) } -static PyTypeObject Hash_InfoType; - PyDoc_STRVAR(hash_info_doc, "hash_info\n\ \n\ @@ -1566,12 +1571,12 @@ static PyStructSequence_Desc hash_info_desc = { }; static PyObject * -get_hash_info(PyThreadState *tstate) +get_hash_info(PyTypeObject *hash_info_type) { PyObject *hash_info; int field = 0; PyHash_FuncDef *hashfunc; - hash_info = PyStructSequence_New(&Hash_InfoType); + hash_info = PyStructSequence_New(hash_info_type); if (hash_info == NULL) { return NULL; } @@ -1620,8 +1625,6 @@ sys_getrecursionlimit_impl(PyObject *module) #ifdef MS_WINDOWS -static PyTypeObject WindowsVersionType = { 0 }; - static PyStructSequence_Field windows_version_fields[] = { {"major", "Major version number"}, {"minor", "Minor version number"}, @@ -1721,10 +1724,14 @@ sys_getwindowsversion_impl(PyObject *module) int pos = 0; OSVERSIONINFOEXW ver; + PyInterpreterState *interp = _PyInterpreterState_GET(); + _PySys_State *state = sys_get_state(interp); + PyTypeObject *windows_version_type = state->windows_version_type; + if (PyObject_GetOptionalAttrString(module, "_cached_windows_version", &version) < 0) { return NULL; - }; - if (version && PyObject_TypeCheck(version, &WindowsVersionType)) { + } + if (version && PyObject_TypeCheck(version, windows_version_type)) { return version; } Py_XDECREF(version); @@ -1733,7 +1740,7 @@ sys_getwindowsversion_impl(PyObject *module) if (!GetVersionExW((OSVERSIONINFOW*) &ver)) return PyErr_SetFromWindowsErr(0); - version = PyStructSequence_New(&WindowsVersionType); + version = PyStructSequence_New(windows_version_type); if (version == NULL) return NULL; @@ -3437,8 +3444,6 @@ PyDoc_STRVAR(flags__doc__, \n\ Flags provided through command line arguments or environment vars."); -static PyTypeObject FlagsType; - static PyStructSequence_Field flags_fields[] = { {"debug", "-d"}, {"inspect", "-i"}, @@ -3499,7 +3504,9 @@ _PySys_SetFlagObj(Py_ssize_t pos, PyObject *value) goto error; } - new_flags = PyStructSequence_New(&FlagsType); + PyInterpreterState *interp = _PyInterpreterState_GET(); + _PySys_State *state = sys_get_state(interp); + new_flags = PyStructSequence_New(state->flags_type); if (new_flags == NULL) { goto error; } @@ -3598,9 +3605,9 @@ set_flags_from_config(PyInterpreterState *interp, PyObject *flags) static PyObject* -make_flags(PyInterpreterState *interp) +make_flags(PyInterpreterState *interp, PyTypeObject *flags_type) { - PyObject *flags = PyStructSequence_New(&FlagsType); + PyObject *flags = PyStructSequence_New(flags_type); if (flags == NULL) { return NULL; } @@ -3618,8 +3625,6 @@ PyDoc_STRVAR(version_info__doc__, \n\ Version information as a named tuple."); -static PyTypeObject VersionInfoType; - static PyStructSequence_Field version_info_fields[] = { {"major", "Major release number"}, {"minor", "Minor release number"}, @@ -3637,13 +3642,13 @@ static PyStructSequence_Desc version_info_desc = { }; static PyObject * -make_version_info(PyThreadState *tstate) +make_version_info(PyThreadState *tstate, PyTypeObject *version_info_type) { PyObject *version_info; char *s; int pos = 0; - version_info = PyStructSequence_New(&VersionInfoType); + version_info = PyStructSequence_New(version_info_type); if (version_info == NULL) { return NULL; } @@ -3843,8 +3848,6 @@ PyDoc_STRVAR(emscripten_info__doc__, \n\ WebAssembly Emscripten platform information."); -static PyTypeObject *EmscriptenInfoType; - static PyStructSequence_Field emscripten_info_fields[] = { {"emscripten_version", "Emscripten version (major, minor, micro)"}, {"runtime", "Runtime (Node.JS version, browser user agent)"}, @@ -3898,14 +3901,14 @@ EM_JS(char *, _Py_emscripten_runtime, (void), { }); static PyObject * -make_emscripten_info(void) +make_emscripten_info(PyTypeObject *emscripten_info_type) { PyObject *emscripten_info = NULL; PyObject *version = NULL; char *ua; int pos = 0; - emscripten_info = PyStructSequence_New(EmscriptenInfoType); + emscripten_info = PyStructSequence_New(emscripten_info_type); if (emscripten_info == NULL) { return NULL; } @@ -3993,6 +3996,7 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict) PyObject *version_info; int res; PyInterpreterState *interp = tstate->interp; + _PySys_State *state = sys_get_state(interp); /* stdin/stdout/stderr are set in pylifecycle.c */ @@ -4017,13 +4021,17 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict) SET_SYS("maxsize", PyLong_FromSsize_t(PY_SSIZE_T_MAX)); SET_SYS("float_info", PyFloat_GetInfo()); SET_SYS("int_info", PyLong_GetInfo()); + /* initialize hash_info */ - if (_PyStructSequence_InitBuiltin(interp, &Hash_InfoType, - &hash_info_desc) < 0) - { + PyTypeObject *hash_info_type = _PyStructSequence_NewType( + &hash_info_desc, Py_TPFLAGS_IMMUTABLETYPE); + if (hash_info_type == NULL) { goto type_init_failed; } - SET_SYS("hash_info", get_hash_info(tstate)); + PyObject *hash_info = get_hash_info(hash_info_type); + Py_DECREF(hash_info_type); + SET_SYS("hash_info", hash_info); + SET_SYS("maxunicode", PyLong_FromLong(0x10FFFF)); SET_SYS("builtin_module_names", list_builtin_module_names()); SET_SYS("stdlib_module_names", list_stdlib_module_names()); @@ -4041,35 +4049,41 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict) SET_SYS_FROM_STRING("abiflags", ABIFLAGS); #endif -#define ENSURE_INFO_TYPE(TYPE, DESC) \ - do { \ - if (_PyStructSequence_InitBuiltinWithFlags( \ - interp, &TYPE, &DESC, Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) { \ - goto type_init_failed; \ - } \ - } while (0) - /* version_info */ - ENSURE_INFO_TYPE(VersionInfoType, version_info_desc); - version_info = make_version_info(tstate); + PyTypeObject *version_info_type = _PyStructSequence_NewType( + &version_info_desc, + Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION); + if (version_info_type == NULL) { + goto type_init_failed; + } + version_info = make_version_info(tstate, version_info_type); + Py_DECREF(version_info_type); SET_SYS("version_info", version_info); /* implementation */ SET_SYS("implementation", make_impl_info(version_info)); // sys.flags: updated later by _PySys_UpdateConfig() - ENSURE_INFO_TYPE(FlagsType, flags_desc); - SET_SYS("flags", make_flags(tstate->interp)); + state->flags_type = _PyStructSequence_NewType( + &flags_desc, + Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION); + if (state->flags_type == NULL) { + goto type_init_failed; + } + SET_SYS("flags", make_flags(tstate->interp, state->flags_type)); #if defined(MS_WINDOWS) /* getwindowsversion */ - ENSURE_INFO_TYPE(WindowsVersionType, windows_version_desc); + state->windows_version_type = _PyStructSequence_NewType( + &windows_version_desc, + Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION); + if (state->windows_version_type == NULL) { + goto type_init_failed; + } SET_SYS_FROM_STRING("_vpath", VPATH); #endif -#undef ENSURE_INFO_TYPE - /* float repr style: 0.03 (short) vs 0.029999999999999999 (legacy) */ #if _PY_SHORT_FLOAT_REPR == 1 SET_SYS("float_repr_style", &_Py_ID(short)); @@ -4082,20 +4096,18 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict) SET_SYS("abi_info", make_abi_info()); /* initialize asyncgen_hooks */ - if (_PyStructSequence_InitBuiltin(interp, &AsyncGenHooksType, - &asyncgen_hooks_desc) < 0) - { + state->async_gen_hooks_type = _PyStructSequence_NewType( + &asyncgen_hooks_desc, Py_TPFLAGS_IMMUTABLETYPE); + if (state->async_gen_hooks_type == NULL) { goto type_init_failed; } #ifdef __EMSCRIPTEN__ - if (EmscriptenInfoType == NULL) { - EmscriptenInfoType = PyStructSequence_NewType(&emscripten_info_desc); - if (EmscriptenInfoType == NULL) { - goto type_init_failed; - } + state->emscripten_info_type = PyStructSequence_NewType(&emscripten_info_desc); + if (state->emscripten_info_type == NULL) { + goto type_init_failed; } - SET_SYS("_emscripten_info", make_emscripten_info()); + SET_SYS("_emscripten_info", make_emscripten_info(state->emscripten_info_type)); #endif /* adding sys.path_hooks and sys.path_importer_cache */ @@ -4178,7 +4190,8 @@ _PySys_UpdateConfig(PyThreadState *tstate) #undef COPY_WSTR // replace sys.flags - PyObject *new_flags = PyStructSequence_New(&FlagsType); + _PySys_State *state = sys_get_state(interp); + PyObject *new_flags = PyStructSequence_New(state->flags_type); if (new_flags == NULL) { return -1; } @@ -4397,17 +4410,14 @@ _PySys_Create(PyThreadState *tstate, PyObject **sysmod_p) void _PySys_FiniTypes(PyInterpreterState *interp) { - _PyStructSequence_FiniBuiltin(interp, &VersionInfoType); - _PyStructSequence_FiniBuiltin(interp, &FlagsType); + _PySys_State *state = sys_get_state(interp); + Py_CLEAR(state->async_gen_hooks_type); + Py_CLEAR(state->flags_type); #if defined(MS_WINDOWS) - _PyStructSequence_FiniBuiltin(interp, &WindowsVersionType); + Py_CLEAR(state->windows_version_type); #endif - _PyStructSequence_FiniBuiltin(interp, &Hash_InfoType); - _PyStructSequence_FiniBuiltin(interp, &AsyncGenHooksType); #ifdef __EMSCRIPTEN__ - if (_Py_IsMainInterpreter(interp)) { - Py_CLEAR(EmscriptenInfoType); - } + Py_CLEAR(state->emscripten_info_type); #endif }