Python-checkins
Threads by month
- ----- 2025 -----
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
January 2025
- 1 participants
- 705 discussions
Jan. 17, 2025
https://github.com/python/cpython/commit/34ded1a1a10204635cad27830fcbee2f85…
commit: 34ded1a1a10204635cad27830fcbee2f8547e8ed
branch: 3.13
author: Victor Stinner <vstinner(a)python.org>
committer: vstinner <vstinner(a)python.org>
date: 2025-01-17T22:44:51+01:00
summary:
[3.13] gh-128911: Add tests on the PyImport C API (#128915) (#128960)
gh-128911: Add tests on the PyImport C API (#128915)
* Add Modules/_testlimitedcapi/import.c
* Add Lib/test/test_capi/test_import.py
* Remove _testcapi.check_pyimport_addmodule(): tests already covered
by newly added tests.
Co-authored-by: Serhiy Storchaka <storchaka(a)gmail.com>
(cherry picked from commit d95ba9fa1110534b7247fa2ff12b90e930c93256)
files:
A Lib/test/test_capi/test_import.py
A Modules/_testlimitedcapi/import.c
M Lib/test/test_import/__init__.py
M Modules/Setup.stdlib.in
M Modules/_testcapimodule.c
M Modules/_testlimitedcapi.c
M Modules/_testlimitedcapi/parts.h
M PCbuild/_testlimitedcapi.vcxproj
M PCbuild/_testlimitedcapi.vcxproj.filters
diff --git a/Lib/test/test_capi/test_import.py b/Lib/test/test_capi/test_import.py
new file mode 100644
index 00000000000000..3abd1cd7470111
--- /dev/null
+++ b/Lib/test/test_capi/test_import.py
@@ -0,0 +1,322 @@
+import importlib.util
+import os.path
+import sys
+import types
+import unittest
+from test.support import os_helper
+from test.support import import_helper
+from test.support.warnings_helper import check_warnings
+
+_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+NULL = None
+
+
+class ImportTests(unittest.TestCase):
+ def test_getmagicnumber(self):
+ # Test PyImport_GetMagicNumber()
+ magic = _testlimitedcapi.PyImport_GetMagicNumber()
+ self.assertEqual(magic,
+ int.from_bytes(importlib.util.MAGIC_NUMBER, 'little'))
+
+ def test_getmagictag(self):
+ # Test PyImport_GetMagicTag()
+ tag = _testlimitedcapi.PyImport_GetMagicTag()
+ self.assertEqual(tag, sys.implementation.cache_tag)
+
+ def test_getmoduledict(self):
+ # Test PyImport_GetModuleDict()
+ modules = _testlimitedcapi.PyImport_GetModuleDict()
+ self.assertIs(modules, sys.modules)
+
+ def check_import_loaded_module(self, import_module):
+ for name in ('os', 'sys', 'test', 'unittest'):
+ with self.subTest(name=name):
+ self.assertIn(name, sys.modules)
+ old_module = sys.modules[name]
+ module = import_module(name)
+ self.assertIsInstance(module, types.ModuleType)
+ self.assertIs(module, old_module)
+
+ def check_import_fresh_module(self, import_module):
+ old_modules = dict(sys.modules)
+ try:
+ for name in ('colorsys', 'math'):
+ with self.subTest(name=name):
+ sys.modules.pop(name, None)
+ module = import_module(name)
+ self.assertIsInstance(module, types.ModuleType)
+ self.assertIs(module, sys.modules[name])
+ self.assertEqual(module.__name__, name)
+ finally:
+ sys.modules.clear()
+ sys.modules.update(old_modules)
+
+ def test_getmodule(self):
+ # Test PyImport_GetModule()
+ getmodule = _testlimitedcapi.PyImport_GetModule
+ self.check_import_loaded_module(getmodule)
+
+ nonexistent = 'nonexistent'
+ self.assertNotIn(nonexistent, sys.modules)
+ self.assertIs(getmodule(nonexistent), KeyError)
+ self.assertIs(getmodule(''), KeyError)
+ self.assertIs(getmodule(object()), KeyError)
+
+ self.assertRaises(TypeError, getmodule, []) # unhashable
+ # CRASHES getmodule(NULL)
+
+ def check_addmodule(self, add_module, accept_nonstr=False):
+ # create a new module
+ names = ['nonexistent']
+ if accept_nonstr:
+ names.append(b'\xff') # non-UTF-8
+ for name in names:
+ with self.subTest(name=name):
+ self.assertNotIn(name, sys.modules)
+ try:
+ module = add_module(name)
+ self.assertIsInstance(module, types.ModuleType)
+ self.assertEqual(module.__name__, name)
+ self.assertIs(module, sys.modules[name])
+ finally:
+ sys.modules.pop(name, None)
+
+ # get an existing module
+ self.check_import_loaded_module(add_module)
+
+ def test_addmoduleobject(self):
+ # Test PyImport_AddModuleObject()
+ addmoduleobject = _testlimitedcapi.PyImport_AddModuleObject
+ self.check_addmodule(addmoduleobject, accept_nonstr=True)
+
+ self.assertRaises(TypeError, addmoduleobject, []) # unhashable
+ # CRASHES addmoduleobject(NULL)
+
+ def test_addmodule(self):
+ # Test PyImport_AddModule()
+ addmodule = _testlimitedcapi.PyImport_AddModule
+ self.check_addmodule(addmodule)
+
+ self.assertRaises(UnicodeDecodeError, addmodule, b'\xff')
+ # CRASHES addmodule(NULL)
+
+ def test_addmoduleref(self):
+ # Test PyImport_AddModuleRef()
+ addmoduleref = _testlimitedcapi.PyImport_AddModuleRef
+ self.check_addmodule(addmoduleref)
+
+ self.assertRaises(UnicodeDecodeError, addmoduleref, b'\xff')
+ # CRASHES addmoduleref(NULL)
+
+ def check_import_func(self, import_module):
+ self.check_import_loaded_module(import_module)
+ self.check_import_fresh_module(import_module)
+ self.assertRaises(ModuleNotFoundError, import_module, 'nonexistent')
+ self.assertRaises(ValueError, import_module, '')
+
+ def test_import(self):
+ # Test PyImport_Import()
+ import_ = _testlimitedcapi.PyImport_Import
+ self.check_import_func(import_)
+
+ self.assertRaises(TypeError, import_, b'os')
+ self.assertRaises(SystemError, import_, NULL)
+
+ def test_importmodule(self):
+ # Test PyImport_ImportModule()
+ importmodule = _testlimitedcapi.PyImport_ImportModule
+ self.check_import_func(importmodule)
+
+ self.assertRaises(UnicodeDecodeError, importmodule, b'\xff')
+ # CRASHES importmodule(NULL)
+
+ def test_importmodulenoblock(self):
+ # Test deprecated PyImport_ImportModuleNoBlock()
+ importmodulenoblock = _testlimitedcapi.PyImport_ImportModuleNoBlock
+ with check_warnings(('', DeprecationWarning)):
+ self.check_import_func(importmodulenoblock)
+ self.assertRaises(UnicodeDecodeError, importmodulenoblock, b'\xff')
+
+ # CRASHES importmodulenoblock(NULL)
+
+ def check_frozen_import(self, import_frozen_module):
+ # Importing a frozen module executes its code, so start by unloading
+ # the module to execute the code in a new (temporary) module.
+ old_zipimport = sys.modules.pop('zipimport')
+ try:
+ self.assertEqual(import_frozen_module('zipimport'), 1)
+
+ # import zipimport again
+ self.assertEqual(import_frozen_module('zipimport'), 1)
+ finally:
+ sys.modules['zipimport'] = old_zipimport
+
+ # not a frozen module
+ self.assertEqual(import_frozen_module('sys'), 0)
+ self.assertEqual(import_frozen_module('nonexistent'), 0)
+ self.assertEqual(import_frozen_module(''), 0)
+
+ def test_importfrozenmodule(self):
+ # Test PyImport_ImportFrozenModule()
+ importfrozenmodule = _testlimitedcapi.PyImport_ImportFrozenModule
+ self.check_frozen_import(importfrozenmodule)
+
+ self.assertRaises(UnicodeDecodeError, importfrozenmodule, b'\xff')
+ # CRASHES importfrozenmodule(NULL)
+
+ def test_importfrozenmoduleobject(self):
+ # Test PyImport_ImportFrozenModuleObject()
+ importfrozenmoduleobject = _testlimitedcapi.PyImport_ImportFrozenModuleObject
+ self.check_frozen_import(importfrozenmoduleobject)
+ self.assertEqual(importfrozenmoduleobject(b'zipimport'), 0)
+ self.assertEqual(importfrozenmoduleobject(NULL), 0)
+
+ def test_importmoduleex(self):
+ # Test PyImport_ImportModuleEx()
+ importmoduleex = _testlimitedcapi.PyImport_ImportModuleEx
+ self.check_import_func(lambda name: importmoduleex(name, NULL, NULL, NULL))
+
+ self.assertRaises(ModuleNotFoundError, importmoduleex, 'nonexistent', NULL, NULL, NULL)
+ self.assertRaises(ValueError, importmoduleex, '', NULL, NULL, NULL)
+ self.assertRaises(UnicodeDecodeError, importmoduleex, b'\xff', NULL, NULL, NULL)
+ # CRASHES importmoduleex(NULL, NULL, NULL, NULL)
+
+ def check_importmodulelevel(self, importmodulelevel):
+ self.check_import_func(lambda name: importmodulelevel(name, NULL, NULL, NULL, 0))
+
+ self.assertRaises(ModuleNotFoundError, importmodulelevel, 'nonexistent', NULL, NULL, NULL, 0)
+ self.assertRaises(ValueError, importmodulelevel, '', NULL, NULL, NULL, 0)
+
+ if __package__:
+ self.assertIs(importmodulelevel('test_import', globals(), NULL, NULL, 1),
+ sys.modules['test.test_capi.test_import'])
+ self.assertIs(importmodulelevel('test_capi', globals(), NULL, NULL, 2),
+ sys.modules['test.test_capi'])
+ self.assertRaises(ValueError, importmodulelevel, 'os', NULL, NULL, NULL, -1)
+ with self.assertWarns(ImportWarning):
+ self.assertRaises(KeyError, importmodulelevel, 'test_import', {}, NULL, NULL, 1)
+ self.assertRaises(TypeError, importmodulelevel, 'test_import', [], NULL, NULL, 1)
+
+ def test_importmodulelevel(self):
+ # Test PyImport_ImportModuleLevel()
+ importmodulelevel = _testlimitedcapi.PyImport_ImportModuleLevel
+ self.check_importmodulelevel(importmodulelevel)
+
+ self.assertRaises(UnicodeDecodeError, importmodulelevel, b'\xff', NULL, NULL, NULL, 0)
+ # CRASHES importmodulelevel(NULL, NULL, NULL, NULL, 0)
+
+ def test_importmodulelevelobject(self):
+ # Test PyImport_ImportModuleLevelObject()
+ importmodulelevel = _testlimitedcapi.PyImport_ImportModuleLevelObject
+ self.check_importmodulelevel(importmodulelevel)
+
+ self.assertRaises(TypeError, importmodulelevel, b'os', NULL, NULL, NULL, 0)
+ self.assertRaises(ValueError, importmodulelevel, NULL, NULL, NULL, NULL, 0)
+
+ def check_executecodemodule(self, execute_code, *args):
+ name = 'test_import_executecode'
+ try:
+ # Create a temporary module where the code will be executed
+ self.assertNotIn(name, sys.modules)
+ module = _testlimitedcapi.PyImport_AddModuleRef(name)
+ self.assertFalse(hasattr(module, 'attr'))
+
+ # Execute the code
+ code = compile('attr = 1', '<test>', 'exec')
+ module2 = execute_code(name, code, *args)
+ self.assertIs(module2, module)
+
+ # Check the function side effects
+ self.assertEqual(module.attr, 1)
+ finally:
+ sys.modules.pop(name, None)
+ return module.__spec__.origin
+
+ def test_executecodemodule(self):
+ # Test PyImport_ExecCodeModule()
+ execcodemodule = _testlimitedcapi.PyImport_ExecCodeModule
+ self.check_executecodemodule(execcodemodule)
+
+ code = compile('attr = 1', '<test>', 'exec')
+ self.assertRaises(UnicodeDecodeError, execcodemodule, b'\xff', code)
+ # CRASHES execcodemodule(NULL, code)
+ # CRASHES execcodemodule(name, NULL)
+
+ def test_executecodemoduleex(self):
+ # Test PyImport_ExecCodeModuleEx()
+ execcodemoduleex = _testlimitedcapi.PyImport_ExecCodeModuleEx
+
+ # Test NULL path (it should not crash)
+ self.check_executecodemodule(execcodemoduleex, NULL)
+
+ # Test non-NULL path
+ pathname = b'pathname'
+ origin = self.check_executecodemodule(execcodemoduleex, pathname)
+ self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
+
+ pathname = os_helper.TESTFN_UNDECODABLE
+ if pathname:
+ origin = self.check_executecodemodule(execcodemoduleex, pathname)
+ self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
+
+ code = compile('attr = 1', '<test>', 'exec')
+ self.assertRaises(UnicodeDecodeError, execcodemoduleex, b'\xff', code, NULL)
+ # CRASHES execcodemoduleex(NULL, code, NULL)
+ # CRASHES execcodemoduleex(name, NULL, NULL)
+
+ def check_executecode_pathnames(self, execute_code_func, object=False):
+ # Test non-NULL pathname and NULL cpathname
+
+ # Test NULL paths (it should not crash)
+ self.check_executecodemodule(execute_code_func, NULL, NULL)
+
+ pathname = 'pathname'
+ origin = self.check_executecodemodule(execute_code_func, pathname, NULL)
+ self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
+ origin = self.check_executecodemodule(execute_code_func, NULL, pathname)
+ if not object:
+ self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
+
+ pathname = os_helper.TESTFN_UNDECODABLE
+ if pathname:
+ if object:
+ pathname = os.fsdecode(pathname)
+ origin = self.check_executecodemodule(execute_code_func, pathname, NULL)
+ self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
+ self.check_executecodemodule(execute_code_func, NULL, pathname)
+
+ # Test NULL pathname and non-NULL cpathname
+ pyc_filename = importlib.util.cache_from_source(__file__)
+ py_filename = importlib.util.source_from_cache(pyc_filename)
+ origin = self.check_executecodemodule(execute_code_func, NULL, pyc_filename)
+ if not object:
+ self.assertEqual(origin, py_filename)
+
+ def test_executecodemodulewithpathnames(self):
+ # Test PyImport_ExecCodeModuleWithPathnames()
+ execute_code_func = _testlimitedcapi.PyImport_ExecCodeModuleWithPathnames
+ self.check_executecode_pathnames(execute_code_func)
+
+ code = compile('attr = 1', '<test>', 'exec')
+ self.assertRaises(UnicodeDecodeError, execute_code_func, b'\xff', code, NULL, NULL)
+ # CRASHES execute_code_func(NULL, code, NULL, NULL)
+ # CRASHES execute_code_func(name, NULL, NULL, NULL)
+
+ def test_executecodemoduleobject(self):
+ # Test PyImport_ExecCodeModuleObject()
+ execute_code_func = _testlimitedcapi.PyImport_ExecCodeModuleObject
+ self.check_executecode_pathnames(execute_code_func, object=True)
+
+ code = compile('attr = 1', '<test>', 'exec')
+ self.assertRaises(TypeError, execute_code_func, [], code, NULL, NULL)
+ # CRASHES execute_code_func(NULL, code, NULL, NULL)
+ # CRASHES execute_code_func(name, NULL, NULL, NULL)
+
+ # TODO: test PyImport_GetImporter()
+ # TODO: test PyImport_ReloadModule()
+ # TODO: test PyImport_ExtendInittab()
+ # PyImport_AppendInittab() is tested by test_embed
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index 364ff08f03533b..df712ec5a9d97d 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -3330,30 +3330,6 @@ def test_basic_multiple_interpreters_reset_each(self):
# * module's global state was initialized, not reset
-@cpython_only
-class CAPITests(unittest.TestCase):
- def test_pyimport_addmodule(self):
- # gh-105922: Test PyImport_AddModuleRef(), PyImport_AddModule()
- # and PyImport_AddModuleObject()
- _testcapi = import_module("_testcapi")
- for name in (
- 'sys', # frozen module
- 'test', # package
- __name__, # package.module
- ):
- _testcapi.check_pyimport_addmodule(name)
-
- def test_pyimport_addmodule_create(self):
- # gh-105922: Test PyImport_AddModuleRef(), create a new module
- _testcapi = import_module("_testcapi")
- name = 'dontexist'
- self.assertNotIn(name, sys.modules)
- self.addCleanup(unload, name)
-
- mod = _testcapi.check_pyimport_addmodule(name)
- self.assertIs(mod, sys.modules[name])
-
-
if __name__ == '__main__':
# Test needs to be a package, so we can do relative imports.
unittest.main()
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 06b30feef43e40..89a8e5a34de65a 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -164,7 +164,7 @@
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c
-@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c
+@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index b8c13c63f95153..1feaaa78a75bfd 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -3081,52 +3081,6 @@ function_set_closure(PyObject *self, PyObject *args)
Py_RETURN_NONE;
}
-static PyObject *
-check_pyimport_addmodule(PyObject *self, PyObject *args)
-{
- const char *name;
- if (!PyArg_ParseTuple(args, "s", &name)) {
- return NULL;
- }
-
- // test PyImport_AddModuleRef()
- PyObject *module = PyImport_AddModuleRef(name);
- if (module == NULL) {
- return NULL;
- }
- assert(PyModule_Check(module));
- // module is a strong reference
-
- // test PyImport_AddModule()
- PyObject *module2 = PyImport_AddModule(name);
- if (module2 == NULL) {
- goto error;
- }
- assert(PyModule_Check(module2));
- assert(module2 == module);
- // module2 is a borrowed ref
-
- // test PyImport_AddModuleObject()
- PyObject *name_obj = PyUnicode_FromString(name);
- if (name_obj == NULL) {
- goto error;
- }
- PyObject *module3 = PyImport_AddModuleObject(name_obj);
- Py_DECREF(name_obj);
- if (module3 == NULL) {
- goto error;
- }
- assert(PyModule_Check(module3));
- assert(module3 == module);
- // module3 is a borrowed ref
-
- return module;
-
-error:
- Py_DECREF(module);
- return NULL;
-}
-
static PyObject *
test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
@@ -3526,7 +3480,6 @@ static PyMethodDef TestMethods[] = {
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
{"function_get_closure", function_get_closure, METH_O, NULL},
{"function_set_closure", function_set_closure, METH_VARARGS, NULL},
- {"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS},
{"test_weakref_capi", test_weakref_capi, METH_NOARGS},
{"function_set_warning", function_set_warning, METH_NOARGS},
{"test_critical_sections", test_critical_sections, METH_NOARGS},
diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c
index ec19da217d8223..2eeb1cf135f2cb 100644
--- a/Modules/_testlimitedcapi.c
+++ b/Modules/_testlimitedcapi.c
@@ -50,6 +50,9 @@ PyInit__testlimitedcapi(void)
if (_PyTestLimitedCAPI_Init_HeaptypeRelative(mod) < 0) {
return NULL;
}
+ if (_PyTestLimitedCAPI_Init_Import(mod) < 0) {
+ return NULL;
+ }
if (_PyTestLimitedCAPI_Init_List(mod) < 0) {
return NULL;
}
diff --git a/Modules/_testlimitedcapi/import.c b/Modules/_testlimitedcapi/import.c
new file mode 100644
index 00000000000000..3707dbedeea0d9
--- /dev/null
+++ b/Modules/_testlimitedcapi/import.c
@@ -0,0 +1,306 @@
+// Need limited C API version 3.13 for PyImport_AddModuleRef()
+#include "pyconfig.h" // Py_GIL_DISABLED
+#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API)
+# define Py_LIMITED_API 0x030d0000
+#endif
+
+#include "parts.h"
+#include "util.h"
+
+
+/* Test PyImport_GetMagicNumber() */
+static PyObject *
+pyimport_getmagicnumber(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ long magic = PyImport_GetMagicNumber();
+ return PyLong_FromLong(magic);
+}
+
+
+/* Test PyImport_GetMagicTag() */
+static PyObject *
+pyimport_getmagictag(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ const char *tag = PyImport_GetMagicTag();
+ return PyUnicode_FromString(tag);
+}
+
+
+/* Test PyImport_GetModuleDict() */
+static PyObject *
+pyimport_getmoduledict(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ return Py_XNewRef(PyImport_GetModuleDict());
+}
+
+
+/* Test PyImport_GetModule() */
+static PyObject *
+pyimport_getmodule(PyObject *Py_UNUSED(module), PyObject *name)
+{
+ assert(!PyErr_Occurred());
+ NULLABLE(name);
+ PyObject *module = PyImport_GetModule(name);
+ if (module == NULL && !PyErr_Occurred()) {
+ return Py_NewRef(PyExc_KeyError);
+ }
+ return module;
+}
+
+
+/* Test PyImport_AddModuleObject() */
+static PyObject *
+pyimport_addmoduleobject(PyObject *Py_UNUSED(module), PyObject *name)
+{
+ NULLABLE(name);
+ return Py_XNewRef(PyImport_AddModuleObject(name));
+}
+
+
+/* Test PyImport_AddModule() */
+static PyObject *
+pyimport_addmodule(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_ParseTuple(args, "z#", &name, &size)) {
+ return NULL;
+ }
+
+ return Py_XNewRef(PyImport_AddModule(name));
+}
+
+
+/* Test PyImport_AddModuleRef() */
+static PyObject *
+pyimport_addmoduleref(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_ParseTuple(args, "z#", &name, &size)) {
+ return NULL;
+ }
+
+ return PyImport_AddModuleRef(name);
+}
+
+
+/* Test PyImport_Import() */
+static PyObject *
+pyimport_import(PyObject *Py_UNUSED(module), PyObject *name)
+{
+ NULLABLE(name);
+ return PyImport_Import(name);
+}
+
+
+/* Test PyImport_ImportModule() */
+static PyObject *
+pyimport_importmodule(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_ParseTuple(args, "z#", &name, &size)) {
+ return NULL;
+ }
+
+ return PyImport_ImportModule(name);
+}
+
+
+/* Test PyImport_ImportModuleNoBlock() */
+static PyObject *
+pyimport_importmodulenoblock(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_ParseTuple(args, "z#", &name, &size)) {
+ return NULL;
+ }
+
+ _Py_COMP_DIAG_PUSH
+ _Py_COMP_DIAG_IGNORE_DEPR_DECLS
+ return PyImport_ImportModuleNoBlock(name);
+ _Py_COMP_DIAG_POP
+}
+
+
+/* Test PyImport_ImportModuleEx() */
+static PyObject *
+pyimport_importmoduleex(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ PyObject *globals, *locals, *fromlist;
+ if (!PyArg_ParseTuple(args, "z#OOO",
+ &name, &size, &globals, &locals, &fromlist)) {
+ return NULL;
+ }
+ NULLABLE(globals);
+ NULLABLE(locals);
+ NULLABLE(fromlist);
+
+ return PyImport_ImportModuleEx(name, globals, locals, fromlist);
+}
+
+
+/* Test PyImport_ImportModuleLevel() */
+static PyObject *
+pyimport_importmodulelevel(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ PyObject *globals, *locals, *fromlist;
+ int level;
+ if (!PyArg_ParseTuple(args, "z#OOOi",
+ &name, &size, &globals, &locals, &fromlist, &level)) {
+ return NULL;
+ }
+ NULLABLE(globals);
+ NULLABLE(locals);
+ NULLABLE(fromlist);
+
+ return PyImport_ImportModuleLevel(name, globals, locals, fromlist, level);
+}
+
+
+/* Test PyImport_ImportModuleLevelObject() */
+static PyObject *
+pyimport_importmodulelevelobject(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ PyObject *name, *globals, *locals, *fromlist;
+ int level;
+ if (!PyArg_ParseTuple(args, "OOOOi",
+ &name, &globals, &locals, &fromlist, &level)) {
+ return NULL;
+ }
+ NULLABLE(name);
+ NULLABLE(globals);
+ NULLABLE(locals);
+ NULLABLE(fromlist);
+
+ return PyImport_ImportModuleLevelObject(name, globals, locals, fromlist, level);
+}
+
+
+/* Test PyImport_ImportFrozenModule() */
+static PyObject *
+pyimport_importfrozenmodule(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_ParseTuple(args, "z#", &name, &size)) {
+ return NULL;
+ }
+
+ RETURN_INT(PyImport_ImportFrozenModule(name));
+}
+
+
+/* Test PyImport_ImportFrozenModuleObject() */
+static PyObject *
+pyimport_importfrozenmoduleobject(PyObject *Py_UNUSED(module), PyObject *name)
+{
+ NULLABLE(name);
+ RETURN_INT(PyImport_ImportFrozenModuleObject(name));
+}
+
+
+/* Test PyImport_ExecCodeModule() */
+static PyObject *
+pyimport_executecodemodule(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ PyObject *code;
+ if (!PyArg_ParseTuple(args, "z#O", &name, &size, &code)) {
+ return NULL;
+ }
+ NULLABLE(code);
+
+ return PyImport_ExecCodeModule(name, code);
+}
+
+
+/* Test PyImport_ExecCodeModuleEx() */
+static PyObject *
+pyimport_executecodemoduleex(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ PyObject *code;
+ const char *pathname;
+ if (!PyArg_ParseTuple(args, "z#Oz#", &name, &size, &code, &pathname, &size)) {
+ return NULL;
+ }
+ NULLABLE(code);
+
+ return PyImport_ExecCodeModuleEx(name, code, pathname);
+}
+
+
+/* Test PyImport_ExecCodeModuleWithPathnames() */
+static PyObject *
+pyimport_executecodemodulewithpathnames(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ PyObject *code;
+ const char *pathname;
+ const char *cpathname;
+ if (!PyArg_ParseTuple(args, "z#Oz#z#", &name, &size, &code, &pathname, &size, &cpathname, &size)) {
+ return NULL;
+ }
+ NULLABLE(code);
+
+ return PyImport_ExecCodeModuleWithPathnames(name, code,
+ pathname, cpathname);
+}
+
+
+/* Test PyImport_ExecCodeModuleObject() */
+static PyObject *
+pyimport_executecodemoduleobject(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ PyObject *name, *code, *pathname, *cpathname;
+ if (!PyArg_ParseTuple(args, "OOOO", &name, &code, &pathname, &cpathname)) {
+ return NULL;
+ }
+ NULLABLE(name);
+ NULLABLE(code);
+ NULLABLE(pathname);
+ NULLABLE(cpathname);
+
+ return PyImport_ExecCodeModuleObject(name, code, pathname, cpathname);
+}
+
+
+static PyMethodDef test_methods[] = {
+ {"PyImport_GetMagicNumber", pyimport_getmagicnumber, METH_NOARGS},
+ {"PyImport_GetMagicTag", pyimport_getmagictag, METH_NOARGS},
+ {"PyImport_GetModuleDict", pyimport_getmoduledict, METH_NOARGS},
+ {"PyImport_GetModule", pyimport_getmodule, METH_O},
+ {"PyImport_AddModuleObject", pyimport_addmoduleobject, METH_O},
+ {"PyImport_AddModule", pyimport_addmodule, METH_VARARGS},
+ {"PyImport_AddModuleRef", pyimport_addmoduleref, METH_VARARGS},
+ {"PyImport_Import", pyimport_import, METH_O},
+ {"PyImport_ImportModule", pyimport_importmodule, METH_VARARGS},
+ {"PyImport_ImportModuleNoBlock", pyimport_importmodulenoblock, METH_VARARGS},
+ {"PyImport_ImportModuleEx", pyimport_importmoduleex, METH_VARARGS},
+ {"PyImport_ImportModuleLevel", pyimport_importmodulelevel, METH_VARARGS},
+ {"PyImport_ImportModuleLevelObject", pyimport_importmodulelevelobject, METH_VARARGS},
+ {"PyImport_ImportFrozenModule", pyimport_importfrozenmodule, METH_VARARGS},
+ {"PyImport_ImportFrozenModuleObject", pyimport_importfrozenmoduleobject, METH_O},
+ {"PyImport_ExecCodeModule", pyimport_executecodemodule, METH_VARARGS},
+ {"PyImport_ExecCodeModuleEx", pyimport_executecodemoduleex, METH_VARARGS},
+ {"PyImport_ExecCodeModuleWithPathnames", pyimport_executecodemodulewithpathnames, METH_VARARGS},
+ {"PyImport_ExecCodeModuleObject", pyimport_executecodemoduleobject, METH_VARARGS},
+ {NULL},
+};
+
+
+int
+_PyTestLimitedCAPI_Init_Import(PyObject *module)
+{
+ return PyModule_AddFunctions(module, test_methods);
+}
diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h
index 140396d6b990ff..ac938a13d5984e 100644
--- a/Modules/_testlimitedcapi/parts.h
+++ b/Modules/_testlimitedcapi/parts.h
@@ -29,6 +29,7 @@ int _PyTestLimitedCAPI_Init_Complex(PyObject *module);
int _PyTestLimitedCAPI_Init_Dict(PyObject *module);
int _PyTestLimitedCAPI_Init_Float(PyObject *module);
int _PyTestLimitedCAPI_Init_HeaptypeRelative(PyObject *module);
+int _PyTestLimitedCAPI_Init_Import(PyObject *module);
int _PyTestLimitedCAPI_Init_Object(PyObject *module);
int _PyTestLimitedCAPI_Init_List(PyObject *module);
int _PyTestLimitedCAPI_Init_Long(PyObject *module);
diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj
index 7e5809fec31791..afadfb9d060bf2 100644
--- a/PCbuild/_testlimitedcapi.vcxproj
+++ b/PCbuild/_testlimitedcapi.vcxproj
@@ -101,6 +101,7 @@
<ClCompile Include="..\Modules\_testlimitedcapi\dict.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\float.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\heaptype_relative.c" />
+ <ClCompile Include="..\Modules\_testlimitedcapi\import.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\list.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\long.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\object.c" />
diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters
index 47f059040bed91..89559a27d647fc 100644
--- a/PCbuild/_testlimitedcapi.vcxproj.filters
+++ b/PCbuild/_testlimitedcapi.vcxproj.filters
@@ -16,6 +16,7 @@
<ClCompile Include="..\Modules\_testlimitedcapi\dict.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\float.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\heaptype_relative.c" />
+ <ClCompile Include="..\Modules\_testlimitedcapi\import.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\list.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\long.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\object.c" />
1
0
gh-128955: Fix goto if tlbc creation fails when throwing into a generator (#128957)
by mpage Jan. 17, 2025
by mpage Jan. 17, 2025
Jan. 17, 2025
https://github.com/python/cpython/commit/13c4def692228f09df0b30c5f93bc515e8…
commit: 13c4def692228f09df0b30c5f93bc515e89fc77f
branch: main
author: mpage <mpage(a)meta.com>
committer: mpage <mpage(a)cs.stanford.edu>
date: 2025-01-17T12:53:29-08:00
summary:
gh-128955: Fix goto if tlbc creation fails when throwing into a generator (#128957)
We don't have the correct copy of the bytecode and can't update next_instr
appropriately, so just unwind.
files:
M Python/ceval.c
diff --git a/Python/ceval.c b/Python/ceval.c
index e0362c3c89fe6a..28b0b4c6de39a7 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -846,7 +846,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
_Py_CODEUNIT *bytecode =
_PyEval_GetExecutableCode(tstate, _PyFrame_GetCode(frame));
if (bytecode == NULL) {
- goto error;
+ goto exit_unwind;
}
ptrdiff_t off = frame->instr_ptr - _PyFrame_GetBytecode(frame);
frame->tlbc_index = ((_PyThreadStateImpl *)tstate)->tlbc_index;
1
0
Jan. 17, 2025
https://github.com/python/cpython/commit/f45da82c11a0fcbc28c099af27e47806f5…
commit: f45da82c11a0fcbc28c099af27e47806f530a97b
branch: 3.12
author: Tian Gao <gaogaotiantian(a)hotmail.com>
committer: gaogaotiantian <gaogaotiantian(a)hotmail.com>
date: 2025-01-17T14:19:37-05:00
summary:
[3.12] gh-58956: Fix a frame refleak in bdb (GH-128190) (#128953)
* [3.12] gh-58956: Fix a frame refleak in bdb (GH-128190)
(cherry picked from commit 767c89ba7c5a70626df6e75eb56b546bf911b997)
Co-authored-by: Tian Gao <gaogaotiantian(a)hotmail.com>
files:
A Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst
M Lib/bdb.py
M Lib/pdb.py
M Lib/test/test_pdb.py
diff --git a/Lib/bdb.py b/Lib/bdb.py
index 196e6b178cb9fd..3486deacd86a7c 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -340,6 +340,7 @@ def set_trace(self, frame=None):
self.botframe = frame
frame = frame.f_back
self.set_step()
+ self.enterframe = None
sys.settrace(self.trace_dispatch)
def set_continue(self):
@@ -356,6 +357,7 @@ def set_continue(self):
while frame and frame is not self.botframe:
del frame.f_trace
frame = frame.f_back
+ self.enterframe = None
def set_quit(self):
"""Set quitting attribute to True.
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 1e1b5ea4f0a184..2a6e994dac1073 100755
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -281,6 +281,7 @@ def forget(self):
if hasattr(self, 'curframe') and self.curframe:
self.curframe.f_globals.pop('__pdb_convenience_variables', None)
self.curframe = None
+ self.curframe_locals = {}
self.tb_lineno.clear()
def setup(self, f, tb):
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index bd61de0ad3494c..778aa03a63ab63 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -1981,6 +1981,58 @@ def test_pdb_ambiguous_statements():
(Pdb) continue
"""
+def test_pdb_frame_refleak():
+ """
+ pdb should not leak reference to frames
+
+ >>> def frame_leaker(container):
+ ... import sys
+ ... container.append(sys._getframe())
+ ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ ... pass
+
+ >>> def test_function():
+ ... import gc
+ ... container = []
+ ... frame_leaker(container) # c
+ ... print(len(gc.get_referrers(container[0])))
+ ... container = []
+ ... frame_leaker(container) # n c
+ ... print(len(gc.get_referrers(container[0])))
+ ... container = []
+ ... frame_leaker(container) # r c
+ ... print(len(gc.get_referrers(container[0])))
+
+ >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
+ ... 'continue',
+ ... 'next',
+ ... 'continue',
+ ... 'return',
+ ... 'continue',
+ ... ]):
+ ... test_function()
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(5)frame_leaker()
+ -> pass
+ (Pdb) continue
+ 1
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(5)frame_leaker()
+ -> pass
+ (Pdb) next
+ --Return--
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(5)frame_leaker()->None
+ -> pass
+ (Pdb) continue
+ 1
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(5)frame_leaker()
+ -> pass
+ (Pdb) return
+ --Return--
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(5)frame_leaker()->None
+ -> pass
+ (Pdb) continue
+ 1
+ """
+
def test_pdb_issue_gh_65052():
"""See GH-65052
diff --git a/Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst b/Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst
new file mode 100644
index 00000000000000..b78bc5aaf44217
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst
@@ -0,0 +1 @@
+Fixed a frame reference leak in :mod:`bdb`.
1
0
https://github.com/python/cpython/commit/d95ba9fa1110534b7247fa2ff12b90e930…
commit: d95ba9fa1110534b7247fa2ff12b90e930c93256
branch: main
author: Victor Stinner <vstinner(a)python.org>
committer: vstinner <vstinner(a)python.org>
date: 2025-01-17T17:52:18Z
summary:
gh-128911: Add tests on the PyImport C API (#128915)
* Add Modules/_testlimitedcapi/import.c
* Add Lib/test/test_capi/test_import.py
* Remove _testcapi.check_pyimport_addmodule(): tests already covered
by newly added tests.
Co-authored-by: Serhiy Storchaka <storchaka(a)gmail.com>
files:
A Lib/test/test_capi/test_import.py
A Modules/_testlimitedcapi/import.c
M Lib/test/test_import/__init__.py
M Modules/Setup.stdlib.in
M Modules/_testcapimodule.c
M Modules/_testlimitedcapi.c
M Modules/_testlimitedcapi/parts.h
M PCbuild/_testlimitedcapi.vcxproj
M PCbuild/_testlimitedcapi.vcxproj.filters
diff --git a/Lib/test/test_capi/test_import.py b/Lib/test/test_capi/test_import.py
new file mode 100644
index 00000000000000..94f96728d9174b
--- /dev/null
+++ b/Lib/test/test_capi/test_import.py
@@ -0,0 +1,327 @@
+import importlib.util
+import os.path
+import sys
+import types
+import unittest
+from test.support import os_helper
+from test.support import import_helper
+from test.support.warnings_helper import check_warnings
+
+_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+NULL = None
+
+
+class ImportTests(unittest.TestCase):
+ def test_getmagicnumber(self):
+ # Test PyImport_GetMagicNumber()
+ magic = _testlimitedcapi.PyImport_GetMagicNumber()
+ self.assertEqual(magic,
+ int.from_bytes(importlib.util.MAGIC_NUMBER, 'little'))
+
+ def test_getmagictag(self):
+ # Test PyImport_GetMagicTag()
+ tag = _testlimitedcapi.PyImport_GetMagicTag()
+ self.assertEqual(tag, sys.implementation.cache_tag)
+
+ def test_getmoduledict(self):
+ # Test PyImport_GetModuleDict()
+ modules = _testlimitedcapi.PyImport_GetModuleDict()
+ self.assertIs(modules, sys.modules)
+
+ def check_import_loaded_module(self, import_module):
+ for name in ('os', 'sys', 'test', 'unittest'):
+ with self.subTest(name=name):
+ self.assertIn(name, sys.modules)
+ old_module = sys.modules[name]
+ module = import_module(name)
+ self.assertIsInstance(module, types.ModuleType)
+ self.assertIs(module, old_module)
+
+ def check_import_fresh_module(self, import_module):
+ old_modules = dict(sys.modules)
+ try:
+ for name in ('colorsys', 'math'):
+ with self.subTest(name=name):
+ sys.modules.pop(name, None)
+ module = import_module(name)
+ self.assertIsInstance(module, types.ModuleType)
+ self.assertIs(module, sys.modules[name])
+ self.assertEqual(module.__name__, name)
+ finally:
+ sys.modules.clear()
+ sys.modules.update(old_modules)
+
+ def test_getmodule(self):
+ # Test PyImport_GetModule()
+ getmodule = _testlimitedcapi.PyImport_GetModule
+ self.check_import_loaded_module(getmodule)
+
+ nonexistent = 'nonexistent'
+ self.assertNotIn(nonexistent, sys.modules)
+ self.assertIs(getmodule(nonexistent), KeyError)
+ self.assertIs(getmodule(''), KeyError)
+ self.assertIs(getmodule(object()), KeyError)
+
+ self.assertRaises(TypeError, getmodule, []) # unhashable
+ # CRASHES getmodule(NULL)
+
+ def check_addmodule(self, add_module, accept_nonstr=False):
+ # create a new module
+ names = ['nonexistent']
+ if accept_nonstr:
+ names.append(b'\xff') # non-UTF-8
+ # PyImport_AddModuleObject() accepts non-string names
+ names.append(tuple(['hashable non-string']))
+ for name in names:
+ with self.subTest(name=name):
+ self.assertNotIn(name, sys.modules)
+ try:
+ module = add_module(name)
+ self.assertIsInstance(module, types.ModuleType)
+ self.assertEqual(module.__name__, name)
+ self.assertIs(module, sys.modules[name])
+ finally:
+ sys.modules.pop(name, None)
+
+ # get an existing module
+ self.check_import_loaded_module(add_module)
+
+ def test_addmoduleobject(self):
+ # Test PyImport_AddModuleObject()
+ addmoduleobject = _testlimitedcapi.PyImport_AddModuleObject
+ self.check_addmodule(addmoduleobject, accept_nonstr=True)
+
+ self.assertRaises(TypeError, addmoduleobject, []) # unhashable
+ # CRASHES addmoduleobject(NULL)
+
+ def test_addmodule(self):
+ # Test PyImport_AddModule()
+ addmodule = _testlimitedcapi.PyImport_AddModule
+ self.check_addmodule(addmodule)
+
+ self.assertRaises(UnicodeDecodeError, addmodule, b'\xff')
+ # CRASHES addmodule(NULL)
+
+ def test_addmoduleref(self):
+ # Test PyImport_AddModuleRef()
+ addmoduleref = _testlimitedcapi.PyImport_AddModuleRef
+ self.check_addmodule(addmoduleref)
+
+ self.assertRaises(UnicodeDecodeError, addmoduleref, b'\xff')
+ # CRASHES addmoduleref(NULL)
+
+ def check_import_func(self, import_module):
+ self.check_import_loaded_module(import_module)
+ self.check_import_fresh_module(import_module)
+ self.assertRaises(ModuleNotFoundError, import_module, 'nonexistent')
+ self.assertRaises(ValueError, import_module, '')
+
+ def test_import(self):
+ # Test PyImport_Import()
+ import_ = _testlimitedcapi.PyImport_Import
+ self.check_import_func(import_)
+
+ self.assertRaises(TypeError, import_, b'os')
+ self.assertRaises(SystemError, import_, NULL)
+
+ def test_importmodule(self):
+ # Test PyImport_ImportModule()
+ importmodule = _testlimitedcapi.PyImport_ImportModule
+ self.check_import_func(importmodule)
+
+ self.assertRaises(UnicodeDecodeError, importmodule, b'\xff')
+ # CRASHES importmodule(NULL)
+
+ def test_importmodulenoblock(self):
+ # Test deprecated PyImport_ImportModuleNoBlock()
+ importmodulenoblock = _testlimitedcapi.PyImport_ImportModuleNoBlock
+ with check_warnings(('', DeprecationWarning)):
+ self.check_import_func(importmodulenoblock)
+ self.assertRaises(UnicodeDecodeError, importmodulenoblock, b'\xff')
+
+ # CRASHES importmodulenoblock(NULL)
+
+ def check_frozen_import(self, import_frozen_module):
+ # Importing a frozen module executes its code, so start by unloading
+ # the module to execute the code in a new (temporary) module.
+ old_zipimport = sys.modules.pop('zipimport')
+ try:
+ self.assertEqual(import_frozen_module('zipimport'), 1)
+
+ # import zipimport again
+ self.assertEqual(import_frozen_module('zipimport'), 1)
+ finally:
+ sys.modules['zipimport'] = old_zipimport
+
+ # not a frozen module
+ self.assertEqual(import_frozen_module('sys'), 0)
+ self.assertEqual(import_frozen_module('nonexistent'), 0)
+ self.assertEqual(import_frozen_module(''), 0)
+
+ def test_importfrozenmodule(self):
+ # Test PyImport_ImportFrozenModule()
+ importfrozenmodule = _testlimitedcapi.PyImport_ImportFrozenModule
+ self.check_frozen_import(importfrozenmodule)
+
+ self.assertRaises(UnicodeDecodeError, importfrozenmodule, b'\xff')
+ # CRASHES importfrozenmodule(NULL)
+
+ def test_importfrozenmoduleobject(self):
+ # Test PyImport_ImportFrozenModuleObject()
+ importfrozenmoduleobject = _testlimitedcapi.PyImport_ImportFrozenModuleObject
+ self.check_frozen_import(importfrozenmoduleobject)
+ self.assertEqual(importfrozenmoduleobject(b'zipimport'), 0)
+ self.assertEqual(importfrozenmoduleobject(NULL), 0)
+
+ def test_importmoduleex(self):
+ # Test PyImport_ImportModuleEx()
+ importmoduleex = _testlimitedcapi.PyImport_ImportModuleEx
+ self.check_import_func(lambda name: importmoduleex(name, NULL, NULL, NULL))
+
+ self.assertRaises(ModuleNotFoundError, importmoduleex, 'nonexistent', NULL, NULL, NULL)
+ self.assertRaises(ValueError, importmoduleex, '', NULL, NULL, NULL)
+ self.assertRaises(UnicodeDecodeError, importmoduleex, b'\xff', NULL, NULL, NULL)
+ # CRASHES importmoduleex(NULL, NULL, NULL, NULL)
+
+ def check_importmodulelevel(self, importmodulelevel):
+ self.check_import_func(lambda name: importmodulelevel(name, NULL, NULL, NULL, 0))
+
+ self.assertRaises(ModuleNotFoundError, importmodulelevel, 'nonexistent', NULL, NULL, NULL, 0)
+ self.assertRaises(ValueError, importmodulelevel, '', NULL, NULL, NULL, 0)
+
+ if __package__:
+ self.assertIs(importmodulelevel('test_import', globals(), NULL, NULL, 1),
+ sys.modules['test.test_capi.test_import'])
+ self.assertIs(importmodulelevel('test_capi', globals(), NULL, NULL, 2),
+ sys.modules['test.test_capi'])
+ self.assertRaises(ValueError, importmodulelevel, 'os', NULL, NULL, NULL, -1)
+ with self.assertWarns(ImportWarning):
+ self.assertRaises(KeyError, importmodulelevel, 'test_import', {}, NULL, NULL, 1)
+ self.assertRaises(TypeError, importmodulelevel, 'test_import', [], NULL, NULL, 1)
+
+ def test_importmodulelevel(self):
+ # Test PyImport_ImportModuleLevel()
+ importmodulelevel = _testlimitedcapi.PyImport_ImportModuleLevel
+ self.check_importmodulelevel(importmodulelevel)
+
+ self.assertRaises(UnicodeDecodeError, importmodulelevel, b'\xff', NULL, NULL, NULL, 0)
+ # CRASHES importmodulelevel(NULL, NULL, NULL, NULL, 0)
+
+ def test_importmodulelevelobject(self):
+ # Test PyImport_ImportModuleLevelObject()
+ importmodulelevel = _testlimitedcapi.PyImport_ImportModuleLevelObject
+ self.check_importmodulelevel(importmodulelevel)
+
+ self.assertRaises(TypeError, importmodulelevel, b'os', NULL, NULL, NULL, 0)
+ self.assertRaises(ValueError, importmodulelevel, NULL, NULL, NULL, NULL, 0)
+
+ def check_executecodemodule(self, execute_code, *args):
+ name = 'test_import_executecode'
+ try:
+ # Create a temporary module where the code will be executed
+ self.assertNotIn(name, sys.modules)
+ module = _testlimitedcapi.PyImport_AddModuleRef(name)
+ self.assertNotHasAttr(module, 'attr')
+
+ # Execute the code
+ code = compile('attr = 1', '<test>', 'exec')
+ module2 = execute_code(name, code, *args)
+ self.assertIs(module2, module)
+
+ # Check the function side effects
+ self.assertEqual(module.attr, 1)
+ finally:
+ sys.modules.pop(name, None)
+ return module.__spec__.origin
+
+ def test_executecodemodule(self):
+ # Test PyImport_ExecCodeModule()
+ execcodemodule = _testlimitedcapi.PyImport_ExecCodeModule
+ self.check_executecodemodule(execcodemodule)
+
+ code = compile('attr = 1', '<test>', 'exec')
+ self.assertRaises(UnicodeDecodeError, execcodemodule, b'\xff', code)
+ # CRASHES execcodemodule(NULL, code)
+ # CRASHES execcodemodule(name, NULL)
+
+ def test_executecodemoduleex(self):
+ # Test PyImport_ExecCodeModuleEx()
+ execcodemoduleex = _testlimitedcapi.PyImport_ExecCodeModuleEx
+
+ # Test NULL path (it should not crash)
+ self.check_executecodemodule(execcodemoduleex, NULL)
+
+ # Test non-NULL path
+ pathname = b'pathname'
+ origin = self.check_executecodemodule(execcodemoduleex, pathname)
+ self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
+
+ pathname = os_helper.TESTFN_UNDECODABLE
+ if pathname:
+ origin = self.check_executecodemodule(execcodemoduleex, pathname)
+ self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
+
+ code = compile('attr = 1', '<test>', 'exec')
+ self.assertRaises(UnicodeDecodeError, execcodemoduleex, b'\xff', code, NULL)
+ # CRASHES execcodemoduleex(NULL, code, NULL)
+ # CRASHES execcodemoduleex(name, NULL, NULL)
+
+ def check_executecode_pathnames(self, execute_code_func, object=False):
+ # Test non-NULL pathname and NULL cpathname
+
+ # Test NULL paths (it should not crash)
+ self.check_executecodemodule(execute_code_func, NULL, NULL)
+
+ pathname = 'pathname'
+ origin = self.check_executecodemodule(execute_code_func, pathname, NULL)
+ self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
+ origin = self.check_executecodemodule(execute_code_func, NULL, pathname)
+ if not object:
+ self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
+
+ pathname = os_helper.TESTFN_UNDECODABLE
+ if pathname:
+ if object:
+ pathname = os.fsdecode(pathname)
+ origin = self.check_executecodemodule(execute_code_func, pathname, NULL)
+ self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
+ self.check_executecodemodule(execute_code_func, NULL, pathname)
+
+ # Test NULL pathname and non-NULL cpathname
+ pyc_filename = importlib.util.cache_from_source(__file__)
+ py_filename = importlib.util.source_from_cache(pyc_filename)
+ origin = self.check_executecodemodule(execute_code_func, NULL, pyc_filename)
+ if not object:
+ self.assertEqual(origin, py_filename)
+
+ def test_executecodemodulewithpathnames(self):
+ # Test PyImport_ExecCodeModuleWithPathnames()
+ execute_code_func = _testlimitedcapi.PyImport_ExecCodeModuleWithPathnames
+ self.check_executecode_pathnames(execute_code_func)
+
+ code = compile('attr = 1', '<test>', 'exec')
+ self.assertRaises(UnicodeDecodeError, execute_code_func, b'\xff', code, NULL, NULL)
+ # CRASHES execute_code_func(NULL, code, NULL, NULL)
+ # CRASHES execute_code_func(name, NULL, NULL, NULL)
+
+ def test_executecodemoduleobject(self):
+ # Test PyImport_ExecCodeModuleObject()
+ execute_code_func = _testlimitedcapi.PyImport_ExecCodeModuleObject
+ self.check_executecode_pathnames(execute_code_func, object=True)
+
+ code = compile('attr = 1', '<test>', 'exec')
+ self.assertRaises(TypeError, execute_code_func, [], code, NULL, NULL)
+ nonstring = tuple(['hashable non-string'])
+ self.assertRaises(AttributeError, execute_code_func, nonstring, code, NULL, NULL)
+ sys.modules.pop(nonstring, None)
+ # CRASHES execute_code_func(NULL, code, NULL, NULL)
+ # CRASHES execute_code_func(name, NULL, NULL, NULL)
+
+ # TODO: test PyImport_GetImporter()
+ # TODO: test PyImport_ReloadModule()
+ # TODO: test PyImport_ExtendInittab()
+ # PyImport_AppendInittab() is tested by test_embed
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index c2cec6444cb43a..1e706023c795b6 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -3311,30 +3311,6 @@ def test_basic_multiple_interpreters_reset_each(self):
# * module's global state was initialized, not reset
-@cpython_only
-class CAPITests(unittest.TestCase):
- def test_pyimport_addmodule(self):
- # gh-105922: Test PyImport_AddModuleRef(), PyImport_AddModule()
- # and PyImport_AddModuleObject()
- _testcapi = import_module("_testcapi")
- for name in (
- 'sys', # frozen module
- 'test', # package
- __name__, # package.module
- ):
- _testcapi.check_pyimport_addmodule(name)
-
- def test_pyimport_addmodule_create(self):
- # gh-105922: Test PyImport_AddModuleRef(), create a new module
- _testcapi = import_module("_testcapi")
- name = 'dontexist'
- self.assertNotIn(name, sys.modules)
- self.addCleanup(unload, name)
-
- mod = _testcapi.check_pyimport_addmodule(name)
- self.assertIs(mod, sys.modules[name])
-
-
@cpython_only
class TestMagicNumber(unittest.TestCase):
def test_magic_number_endianness(self):
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index b7357f41768a2f..6b6a8ae57a5119 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -163,7 +163,7 @@
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c
-@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c
+@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index b657bb665bd5c5..996b96bc000450 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -3059,52 +3059,6 @@ function_set_closure(PyObject *self, PyObject *args)
Py_RETURN_NONE;
}
-static PyObject *
-check_pyimport_addmodule(PyObject *self, PyObject *args)
-{
- const char *name;
- if (!PyArg_ParseTuple(args, "s", &name)) {
- return NULL;
- }
-
- // test PyImport_AddModuleRef()
- PyObject *module = PyImport_AddModuleRef(name);
- if (module == NULL) {
- return NULL;
- }
- assert(PyModule_Check(module));
- // module is a strong reference
-
- // test PyImport_AddModule()
- PyObject *module2 = PyImport_AddModule(name);
- if (module2 == NULL) {
- goto error;
- }
- assert(PyModule_Check(module2));
- assert(module2 == module);
- // module2 is a borrowed ref
-
- // test PyImport_AddModuleObject()
- PyObject *name_obj = PyUnicode_FromString(name);
- if (name_obj == NULL) {
- goto error;
- }
- PyObject *module3 = PyImport_AddModuleObject(name_obj);
- Py_DECREF(name_obj);
- if (module3 == NULL) {
- goto error;
- }
- assert(PyModule_Check(module3));
- assert(module3 == module);
- // module3 is a borrowed ref
-
- return module;
-
-error:
- Py_DECREF(module);
- return NULL;
-}
-
static PyObject *
test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
@@ -3668,7 +3622,6 @@ static PyMethodDef TestMethods[] = {
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
{"function_get_closure", function_get_closure, METH_O, NULL},
{"function_set_closure", function_set_closure, METH_VARARGS, NULL},
- {"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS},
{"test_weakref_capi", test_weakref_capi, METH_NOARGS},
{"function_set_warning", function_set_warning, METH_NOARGS},
{"test_critical_sections", test_critical_sections, METH_NOARGS},
diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c
index bcc69a339ec5c4..82dac1c999470f 100644
--- a/Modules/_testlimitedcapi.c
+++ b/Modules/_testlimitedcapi.c
@@ -56,6 +56,9 @@ PyInit__testlimitedcapi(void)
if (_PyTestLimitedCAPI_Init_HeaptypeRelative(mod) < 0) {
return NULL;
}
+ if (_PyTestLimitedCAPI_Init_Import(mod) < 0) {
+ return NULL;
+ }
if (_PyTestLimitedCAPI_Init_List(mod) < 0) {
return NULL;
}
diff --git a/Modules/_testlimitedcapi/import.c b/Modules/_testlimitedcapi/import.c
new file mode 100644
index 00000000000000..3707dbedeea0d9
--- /dev/null
+++ b/Modules/_testlimitedcapi/import.c
@@ -0,0 +1,306 @@
+// Need limited C API version 3.13 for PyImport_AddModuleRef()
+#include "pyconfig.h" // Py_GIL_DISABLED
+#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API)
+# define Py_LIMITED_API 0x030d0000
+#endif
+
+#include "parts.h"
+#include "util.h"
+
+
+/* Test PyImport_GetMagicNumber() */
+static PyObject *
+pyimport_getmagicnumber(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ long magic = PyImport_GetMagicNumber();
+ return PyLong_FromLong(magic);
+}
+
+
+/* Test PyImport_GetMagicTag() */
+static PyObject *
+pyimport_getmagictag(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ const char *tag = PyImport_GetMagicTag();
+ return PyUnicode_FromString(tag);
+}
+
+
+/* Test PyImport_GetModuleDict() */
+static PyObject *
+pyimport_getmoduledict(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ return Py_XNewRef(PyImport_GetModuleDict());
+}
+
+
+/* Test PyImport_GetModule() */
+static PyObject *
+pyimport_getmodule(PyObject *Py_UNUSED(module), PyObject *name)
+{
+ assert(!PyErr_Occurred());
+ NULLABLE(name);
+ PyObject *module = PyImport_GetModule(name);
+ if (module == NULL && !PyErr_Occurred()) {
+ return Py_NewRef(PyExc_KeyError);
+ }
+ return module;
+}
+
+
+/* Test PyImport_AddModuleObject() */
+static PyObject *
+pyimport_addmoduleobject(PyObject *Py_UNUSED(module), PyObject *name)
+{
+ NULLABLE(name);
+ return Py_XNewRef(PyImport_AddModuleObject(name));
+}
+
+
+/* Test PyImport_AddModule() */
+static PyObject *
+pyimport_addmodule(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_ParseTuple(args, "z#", &name, &size)) {
+ return NULL;
+ }
+
+ return Py_XNewRef(PyImport_AddModule(name));
+}
+
+
+/* Test PyImport_AddModuleRef() */
+static PyObject *
+pyimport_addmoduleref(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_ParseTuple(args, "z#", &name, &size)) {
+ return NULL;
+ }
+
+ return PyImport_AddModuleRef(name);
+}
+
+
+/* Test PyImport_Import() */
+static PyObject *
+pyimport_import(PyObject *Py_UNUSED(module), PyObject *name)
+{
+ NULLABLE(name);
+ return PyImport_Import(name);
+}
+
+
+/* Test PyImport_ImportModule() */
+static PyObject *
+pyimport_importmodule(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_ParseTuple(args, "z#", &name, &size)) {
+ return NULL;
+ }
+
+ return PyImport_ImportModule(name);
+}
+
+
+/* Test PyImport_ImportModuleNoBlock() */
+static PyObject *
+pyimport_importmodulenoblock(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_ParseTuple(args, "z#", &name, &size)) {
+ return NULL;
+ }
+
+ _Py_COMP_DIAG_PUSH
+ _Py_COMP_DIAG_IGNORE_DEPR_DECLS
+ return PyImport_ImportModuleNoBlock(name);
+ _Py_COMP_DIAG_POP
+}
+
+
+/* Test PyImport_ImportModuleEx() */
+static PyObject *
+pyimport_importmoduleex(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ PyObject *globals, *locals, *fromlist;
+ if (!PyArg_ParseTuple(args, "z#OOO",
+ &name, &size, &globals, &locals, &fromlist)) {
+ return NULL;
+ }
+ NULLABLE(globals);
+ NULLABLE(locals);
+ NULLABLE(fromlist);
+
+ return PyImport_ImportModuleEx(name, globals, locals, fromlist);
+}
+
+
+/* Test PyImport_ImportModuleLevel() */
+static PyObject *
+pyimport_importmodulelevel(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ PyObject *globals, *locals, *fromlist;
+ int level;
+ if (!PyArg_ParseTuple(args, "z#OOOi",
+ &name, &size, &globals, &locals, &fromlist, &level)) {
+ return NULL;
+ }
+ NULLABLE(globals);
+ NULLABLE(locals);
+ NULLABLE(fromlist);
+
+ return PyImport_ImportModuleLevel(name, globals, locals, fromlist, level);
+}
+
+
+/* Test PyImport_ImportModuleLevelObject() */
+static PyObject *
+pyimport_importmodulelevelobject(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ PyObject *name, *globals, *locals, *fromlist;
+ int level;
+ if (!PyArg_ParseTuple(args, "OOOOi",
+ &name, &globals, &locals, &fromlist, &level)) {
+ return NULL;
+ }
+ NULLABLE(name);
+ NULLABLE(globals);
+ NULLABLE(locals);
+ NULLABLE(fromlist);
+
+ return PyImport_ImportModuleLevelObject(name, globals, locals, fromlist, level);
+}
+
+
+/* Test PyImport_ImportFrozenModule() */
+static PyObject *
+pyimport_importfrozenmodule(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_ParseTuple(args, "z#", &name, &size)) {
+ return NULL;
+ }
+
+ RETURN_INT(PyImport_ImportFrozenModule(name));
+}
+
+
+/* Test PyImport_ImportFrozenModuleObject() */
+static PyObject *
+pyimport_importfrozenmoduleobject(PyObject *Py_UNUSED(module), PyObject *name)
+{
+ NULLABLE(name);
+ RETURN_INT(PyImport_ImportFrozenModuleObject(name));
+}
+
+
+/* Test PyImport_ExecCodeModule() */
+static PyObject *
+pyimport_executecodemodule(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ PyObject *code;
+ if (!PyArg_ParseTuple(args, "z#O", &name, &size, &code)) {
+ return NULL;
+ }
+ NULLABLE(code);
+
+ return PyImport_ExecCodeModule(name, code);
+}
+
+
+/* Test PyImport_ExecCodeModuleEx() */
+static PyObject *
+pyimport_executecodemoduleex(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ PyObject *code;
+ const char *pathname;
+ if (!PyArg_ParseTuple(args, "z#Oz#", &name, &size, &code, &pathname, &size)) {
+ return NULL;
+ }
+ NULLABLE(code);
+
+ return PyImport_ExecCodeModuleEx(name, code, pathname);
+}
+
+
+/* Test PyImport_ExecCodeModuleWithPathnames() */
+static PyObject *
+pyimport_executecodemodulewithpathnames(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ PyObject *code;
+ const char *pathname;
+ const char *cpathname;
+ if (!PyArg_ParseTuple(args, "z#Oz#z#", &name, &size, &code, &pathname, &size, &cpathname, &size)) {
+ return NULL;
+ }
+ NULLABLE(code);
+
+ return PyImport_ExecCodeModuleWithPathnames(name, code,
+ pathname, cpathname);
+}
+
+
+/* Test PyImport_ExecCodeModuleObject() */
+static PyObject *
+pyimport_executecodemoduleobject(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ PyObject *name, *code, *pathname, *cpathname;
+ if (!PyArg_ParseTuple(args, "OOOO", &name, &code, &pathname, &cpathname)) {
+ return NULL;
+ }
+ NULLABLE(name);
+ NULLABLE(code);
+ NULLABLE(pathname);
+ NULLABLE(cpathname);
+
+ return PyImport_ExecCodeModuleObject(name, code, pathname, cpathname);
+}
+
+
+static PyMethodDef test_methods[] = {
+ {"PyImport_GetMagicNumber", pyimport_getmagicnumber, METH_NOARGS},
+ {"PyImport_GetMagicTag", pyimport_getmagictag, METH_NOARGS},
+ {"PyImport_GetModuleDict", pyimport_getmoduledict, METH_NOARGS},
+ {"PyImport_GetModule", pyimport_getmodule, METH_O},
+ {"PyImport_AddModuleObject", pyimport_addmoduleobject, METH_O},
+ {"PyImport_AddModule", pyimport_addmodule, METH_VARARGS},
+ {"PyImport_AddModuleRef", pyimport_addmoduleref, METH_VARARGS},
+ {"PyImport_Import", pyimport_import, METH_O},
+ {"PyImport_ImportModule", pyimport_importmodule, METH_VARARGS},
+ {"PyImport_ImportModuleNoBlock", pyimport_importmodulenoblock, METH_VARARGS},
+ {"PyImport_ImportModuleEx", pyimport_importmoduleex, METH_VARARGS},
+ {"PyImport_ImportModuleLevel", pyimport_importmodulelevel, METH_VARARGS},
+ {"PyImport_ImportModuleLevelObject", pyimport_importmodulelevelobject, METH_VARARGS},
+ {"PyImport_ImportFrozenModule", pyimport_importfrozenmodule, METH_VARARGS},
+ {"PyImport_ImportFrozenModuleObject", pyimport_importfrozenmoduleobject, METH_O},
+ {"PyImport_ExecCodeModule", pyimport_executecodemodule, METH_VARARGS},
+ {"PyImport_ExecCodeModuleEx", pyimport_executecodemoduleex, METH_VARARGS},
+ {"PyImport_ExecCodeModuleWithPathnames", pyimport_executecodemodulewithpathnames, METH_VARARGS},
+ {"PyImport_ExecCodeModuleObject", pyimport_executecodemoduleobject, METH_VARARGS},
+ {NULL},
+};
+
+
+int
+_PyTestLimitedCAPI_Init_Import(PyObject *module)
+{
+ return PyModule_AddFunctions(module, test_methods);
+}
diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h
index 56d566b66565a3..9efcd8dcb71e5b 100644
--- a/Modules/_testlimitedcapi/parts.h
+++ b/Modules/_testlimitedcapi/parts.h
@@ -31,6 +31,7 @@ int _PyTestLimitedCAPI_Init_Dict(PyObject *module);
int _PyTestLimitedCAPI_Init_Eval(PyObject *module);
int _PyTestLimitedCAPI_Init_Float(PyObject *module);
int _PyTestLimitedCAPI_Init_HeaptypeRelative(PyObject *module);
+int _PyTestLimitedCAPI_Init_Import(PyObject *module);
int _PyTestLimitedCAPI_Init_Object(PyObject *module);
int _PyTestLimitedCAPI_Init_List(PyObject *module);
int _PyTestLimitedCAPI_Init_Long(PyObject *module);
diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj
index 0ea5edba3aa9a7..87abff52493098 100644
--- a/PCbuild/_testlimitedcapi.vcxproj
+++ b/PCbuild/_testlimitedcapi.vcxproj
@@ -103,6 +103,7 @@
<ClCompile Include="..\Modules\_testlimitedcapi\eval.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\float.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\heaptype_relative.c" />
+ <ClCompile Include="..\Modules\_testlimitedcapi\import.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\list.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\long.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\object.c" />
diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters
index b379090eb599f5..a975a508506905 100644
--- a/PCbuild/_testlimitedcapi.vcxproj.filters
+++ b/PCbuild/_testlimitedcapi.vcxproj.filters
@@ -18,6 +18,7 @@
<ClCompile Include="..\Modules\_testlimitedcapi\eval.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\float.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\heaptype_relative.c" />
+ <ClCompile Include="..\Modules\_testlimitedcapi\import.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\list.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\long.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\object.c" />
1
0
Jan. 17, 2025
https://github.com/python/cpython/commit/dc77f1914d614bab04769616d7bc6674ee…
commit: dc77f1914d614bab04769616d7bc6674ee723f47
branch: 3.13
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: gaogaotiantian <gaogaotiantian(a)hotmail.com>
date: 2025-01-17T12:49:15-05:00
summary:
[3.13] gh-58956: Fix a frame refleak in bdb (GH-128190) (#128947)
* gh-58956: Fix a frame refleak in bdb (GH-128190)
(cherry picked from commit 767c89ba7c5a70626df6e75eb56b546bf911b997)
Co-authored-by: Tian Gao <gaogaotiantian(a)hotmail.com>
files:
A Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst
M Lib/bdb.py
M Lib/pdb.py
M Lib/test/test_pdb.py
diff --git a/Lib/bdb.py b/Lib/bdb.py
index ece0a29fe9f3b1..3a4453d95f6596 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -382,6 +382,7 @@ def set_trace(self, frame=None):
frame.f_trace_lines = True
frame = frame.f_back
self.set_stepinstr()
+ self.enterframe = None
sys.settrace(self.trace_dispatch)
def set_continue(self):
@@ -401,6 +402,7 @@ def set_continue(self):
for frame, (trace_lines, trace_opcodes) in self.frame_trace_lines_opcodes.items():
frame.f_trace_lines, frame.f_trace_opcodes = trace_lines, trace_opcodes
self.frame_trace_lines_opcodes = {}
+ self.enterframe = None
def set_quit(self):
"""Set quitting attribute to True.
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 9b6dffda1cfcd1..cb0a3405c58e55 100755
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -383,6 +383,7 @@ def forget(self):
if hasattr(self, 'curframe') and self.curframe:
self.curframe.f_globals.pop('__pdb_convenience_variables', None)
self.curframe = None
+ self.curframe_locals = {}
self.tb_lineno.clear()
def setup(self, f, tb):
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index 5b25f514b9e772..c34046f0e5bb6f 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -2810,6 +2810,57 @@ def test_pdb_f_trace_lines():
(Pdb) continue
"""
+def test_pdb_frame_refleak():
+ """
+ pdb should not leak reference to frames
+
+ >>> def frame_leaker(container):
+ ... import sys
+ ... container.append(sys._getframe())
+ ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ ... pass
+
+ >>> def test_function():
+ ... import gc
+ ... container = []
+ ... frame_leaker(container) # c
+ ... print(len(gc.get_referrers(container[0])))
+ ... container = []
+ ... frame_leaker(container) # n c
+ ... print(len(gc.get_referrers(container[0])))
+ ... container = []
+ ... frame_leaker(container) # r c
+ ... print(len(gc.get_referrers(container[0])))
+
+ >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
+ ... 'continue',
+ ... 'next',
+ ... 'continue',
+ ... 'return',
+ ... 'continue',
+ ... ]):
+ ... test_function()
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(4)frame_leaker()
+ -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ (Pdb) continue
+ 1
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(4)frame_leaker()
+ -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ (Pdb) next
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(5)frame_leaker()
+ -> pass
+ (Pdb) continue
+ 1
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(4)frame_leaker()
+ -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ (Pdb) return
+ --Return--
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(5)frame_leaker()->None
+ -> pass
+ (Pdb) continue
+ 1
+ """
+
def test_pdb_function_break():
"""Testing the line number of break on function
diff --git a/Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst b/Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst
new file mode 100644
index 00000000000000..b78bc5aaf44217
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst
@@ -0,0 +1 @@
+Fixed a frame reference leak in :mod:`bdb`.
1
0
https://github.com/python/cpython/commit/b5558cd63c62855ed43d66a55907f9d439…
commit: b5558cd63c62855ed43d66a55907f9d4398134e3
branch: main
author: Mark Shannon <mark(a)hotpy.org>
committer: markshannon <mark(a)hotpy.org>
date: 2025-01-17T16:59:30Z
summary:
Refactor code generators a bit (GH-128920)
Refactor code generators a bit to avoid passing stack property around all over the place
files:
M Tools/cases_generator/optimizer_generator.py
M Tools/cases_generator/stack.py
diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py
index d08b621aed552b..2928440fecca0c 100644
--- a/Tools/cases_generator/optimizer_generator.py
+++ b/Tools/cases_generator/optimizer_generator.py
@@ -126,7 +126,7 @@ def write_uop(
try:
out.start_line()
if override:
- code_list, storage = Storage.for_uop(stack, prototype, extract_bits=False)
+ code_list, storage = Storage.for_uop(stack, prototype)
for code in code_list:
out.emit(code)
if debug:
@@ -151,11 +151,11 @@ def write_uop(
var.defined = False
storage = emitter.emit_tokens(override, storage, None)
out.start_line()
- storage.flush(out, cast_type="_Py_UopsSymbol *", extract_bits=False)
+ storage.flush(out, cast_type="_Py_UopsSymbol *")
else:
emit_default(out, uop, stack)
out.start_line()
- stack.flush(out, cast_type="_Py_UopsSymbol *", extract_bits=False)
+ stack.flush(out, cast_type="_Py_UopsSymbol *")
except StackError as ex:
raise analysis_error(ex.args[0], prototype.body[0]) # from None
@@ -198,7 +198,7 @@ def generate_abstract_interpreter(
declare_variables(override, out, skip_inputs=False)
else:
declare_variables(uop, out, skip_inputs=True)
- stack = Stack()
+ stack = Stack(False)
write_uop(override, uop, out, stack, debug, skip_inputs=(override is None))
out.start_line()
out.emit("break;\n")
diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py
index 286f47d0cfb11b..5121837ed8334b 100644
--- a/Tools/cases_generator/stack.py
+++ b/Tools/cases_generator/stack.py
@@ -224,13 +224,14 @@ def array_or_scalar(var: StackItem | Local) -> str:
return "array" if var.is_array() else "scalar"
class Stack:
- def __init__(self) -> None:
+ def __init__(self, extract_bits: bool=True) -> None:
self.top_offset = StackOffset.empty()
self.base_offset = StackOffset.empty()
self.variables: list[Local] = []
self.defined: set[str] = set()
+ self.extract_bits = extract_bits
- def pop(self, var: StackItem, extract_bits: bool = True) -> tuple[str, Local]:
+ def pop(self, var: StackItem) -> tuple[str, Local]:
self.top_offset.pop(var)
indirect = "&" if var.is_array() else ""
if self.variables:
@@ -272,7 +273,7 @@ def pop(self, var: StackItem, extract_bits: bool = True) -> tuple[str, Local]:
return "", Local.unused(var)
self.defined.add(var.name)
cast = f"({var.type})" if (not indirect and var.type) else ""
- bits = ".bits" if cast and extract_bits else ""
+ bits = ".bits" if cast and self.extract_bits else ""
assign = f"{var.name} = {cast}{indirect}stack_pointer[{self.base_offset.to_c()}]{bits};"
if var.condition:
if var.condition == "1":
@@ -315,7 +316,7 @@ def _adjust_stack_pointer(self, out: CWriter, number: str) -> None:
out.emit("assert(WITHIN_STACK_BOUNDS());\n")
def flush(
- self, out: CWriter, cast_type: str = "uintptr_t", extract_bits: bool = True
+ self, out: CWriter, cast_type: str = "uintptr_t"
) -> None:
out.start_line()
var_offset = self.base_offset.copy()
@@ -324,7 +325,7 @@ def flush(
var.defined and
not var.in_memory
):
- Stack._do_emit(out, var.item, var_offset, cast_type, extract_bits)
+ Stack._do_emit(out, var.item, var_offset, cast_type, self.extract_bits)
var.in_memory = True
var_offset.push(var.item)
number = self.top_offset.to_c()
@@ -346,7 +347,7 @@ def as_comment(self) -> str:
)
def copy(self) -> "Stack":
- other = Stack()
+ other = Stack(self.extract_bits)
other.top_offset = self.top_offset.copy()
other.base_offset = self.base_offset.copy()
other.variables = [var.copy() for var in self.variables]
@@ -507,10 +508,10 @@ def locals_cached(self) -> bool:
return True
return False
- def flush(self, out: CWriter, cast_type: str = "uintptr_t", extract_bits: bool = True) -> None:
+ def flush(self, out: CWriter, cast_type: str = "uintptr_t") -> None:
self.clear_dead_inputs()
self._push_defined_outputs()
- self.stack.flush(out, cast_type, extract_bits)
+ self.stack.flush(out, cast_type)
def save(self, out: CWriter) -> None:
assert self.spilled >= 0
@@ -530,12 +531,12 @@ def reload(self, out: CWriter) -> None:
out.emit("stack_pointer = _PyFrame_GetStackPointer(frame);\n")
@staticmethod
- def for_uop(stack: Stack, uop: Uop, extract_bits: bool = True) -> tuple[list[str], "Storage"]:
+ def for_uop(stack: Stack, uop: Uop) -> tuple[list[str], "Storage"]:
code_list: list[str] = []
inputs: list[Local] = []
peeks: list[Local] = []
for input in reversed(uop.stack.inputs):
- code, local = stack.pop(input, extract_bits)
+ code, local = stack.pop(input)
code_list.append(code)
if input.peek:
peeks.append(local)
1
0
[3.13] gh-128017: Make a note that sys variables are read-only (GH-128887) (#128908)
by hugovk Jan. 17, 2025
by hugovk Jan. 17, 2025
Jan. 17, 2025
https://github.com/python/cpython/commit/9974e7185dcbf86c9cfe80cde62fbfff8e…
commit: 9974e7185dcbf86c9cfe80cde62fbfff8e43be69
branch: 3.13
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: hugovk <1324225+hugovk(a)users.noreply.github.com>
date: 2025-01-17T18:04:00+02:00
summary:
[3.13] gh-128017: Make a note that sys variables are read-only (GH-128887) (#128908)
Co-authored-by: Srinivas Reddy Thatiparthy (తాటిపర్తి శ్రీనివాస్ రెడ్డి) <thatiparthysreenivas(a)gmail.com>
files:
M Doc/library/sys.rst
diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
index fdacf5aff8c455..7af7a7c0d7c47b 100644
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -8,7 +8,7 @@
This module provides access to some variables used or maintained by the
interpreter and to functions that interact strongly with the interpreter. It is
-always available.
+always available. Unless explicitly noted otherwise, all variables are read-only.
.. data:: abiflags
1
0
https://github.com/python/cpython/commit/d66c08aa757f221c0e8893300edac105df…
commit: d66c08aa757f221c0e8893300edac105dfcde7e8
branch: main
author: Sam Gross <colesbury(a)gmail.com>
committer: Yhg1s <thomas(a)python.org>
date: 2025-01-17T16:42:27+01:00
summary:
gh-128923: Use zero to indicate unassigned unique id (#128925)
In the free threading build, the per thread reference counting uses a
unique id for some objects to index into the local reference count
table. Use 0 instead of -1 to indicate that the id is not assigned. This
avoids bugs where zero-initialized heap type objects look like they have
a unique id assigned.
files:
M Include/internal/pycore_dict.h
M Include/internal/pycore_object.h
M Include/internal/pycore_uniqueid.h
M Lib/test/test_capi/test_type.py
M Modules/_testcapimodule.c
M Objects/codeobject.c
M Objects/dictobject.c
M Objects/typeobject.c
M Python/uniqueid.c
diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h
index 74ac8f2148174c..f4c55ca6cf64d2 100644
--- a/Include/internal/pycore_dict.h
+++ b/Include/internal/pycore_dict.h
@@ -347,8 +347,7 @@ PyDictObject *_PyObject_MaterializeManagedDict_LockHeld(PyObject *);
static inline Py_ssize_t
_PyDict_UniqueId(PyDictObject *mp)
{
- // Offset by one so that _ma_watcher_tag=0 represents an unassigned id
- return (Py_ssize_t)(mp->_ma_watcher_tag >> DICT_UNIQUE_ID_SHIFT) - 1;
+ return (Py_ssize_t)(mp->_ma_watcher_tag >> DICT_UNIQUE_ID_SHIFT);
}
static inline void
diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index 322305bc8c664a..19d657070ff221 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -336,20 +336,20 @@ _Py_THREAD_INCREF_OBJECT(PyObject *obj, Py_ssize_t unique_id)
{
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
- // Unsigned comparison so that `unique_id=-1`, which indicates that
- // per-thread refcounting has been disabled on this object, is handled by
- // the "else".
- if ((size_t)unique_id < (size_t)tstate->refcounts.size) {
+ // The table index is `unique_id - 1` because 0 is not a valid unique id.
+ // Unsigned comparison so that `idx=-1` is handled by the "else".
+ size_t idx = (size_t)(unique_id - 1);
+ if (idx < (size_t)tstate->refcounts.size) {
# ifdef Py_REF_DEBUG
_Py_INCREF_IncRefTotal();
# endif
_Py_INCREF_STAT_INC();
- tstate->refcounts.values[unique_id]++;
+ tstate->refcounts.values[idx]++;
}
else {
// The slow path resizes the per-thread refcount array if necessary.
- // It handles the unique_id=-1 case to keep the inlinable function smaller.
- _PyObject_ThreadIncrefSlow(obj, unique_id);
+ // It handles the unique_id=0 case to keep the inlinable function smaller.
+ _PyObject_ThreadIncrefSlow(obj, idx);
}
}
@@ -386,15 +386,15 @@ _Py_THREAD_DECREF_OBJECT(PyObject *obj, Py_ssize_t unique_id)
{
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
- // Unsigned comparison so that `unique_id=-1`, which indicates that
- // per-thread refcounting has been disabled on this object, is handled by
- // the "else".
- if ((size_t)unique_id < (size_t)tstate->refcounts.size) {
+ // The table index is `unique_id - 1` because 0 is not a valid unique id.
+ // Unsigned comparison so that `idx=-1` is handled by the "else".
+ size_t idx = (size_t)(unique_id - 1);
+ if (idx < (size_t)tstate->refcounts.size) {
# ifdef Py_REF_DEBUG
_Py_DECREF_DecRefTotal();
# endif
_Py_DECREF_STAT_INC();
- tstate->refcounts.values[unique_id]--;
+ tstate->refcounts.values[idx]--;
}
else {
// Directly decref the object if the id is not assigned or if
diff --git a/Include/internal/pycore_uniqueid.h b/Include/internal/pycore_uniqueid.h
index d3db49ddb78103..9d3c866a704894 100644
--- a/Include/internal/pycore_uniqueid.h
+++ b/Include/internal/pycore_uniqueid.h
@@ -16,7 +16,7 @@ extern "C" {
// Per-thread reference counting is used along with deferred reference
// counting to avoid scaling bottlenecks due to reference count contention.
//
-// An id of -1 is used to indicate that an object doesn't use per-thread
+// An id of 0 is used to indicate that an object doesn't use per-thread
// refcounting. This value is used when the object is finalized by the GC
// and during interpreter shutdown to allow the object to be
// deallocated promptly when the object's refcount reaches zero.
@@ -45,6 +45,8 @@ struct _Py_unique_id_pool {
Py_ssize_t size;
};
+#define _Py_INVALID_UNIQUE_ID 0
+
// Assigns the next id from the pool of ids.
extern Py_ssize_t _PyObject_AssignUniqueId(PyObject *obj);
@@ -65,7 +67,7 @@ extern void _PyObject_FinalizePerThreadRefcounts(_PyThreadStateImpl *tstate);
extern void _PyObject_FinalizeUniqueIdPool(PyInterpreterState *interp);
// Increfs the object, resizing the thread-local refcount array if necessary.
-PyAPI_FUNC(void) _PyObject_ThreadIncrefSlow(PyObject *obj, Py_ssize_t unique_id);
+PyAPI_FUNC(void) _PyObject_ThreadIncrefSlow(PyObject *obj, size_t idx);
#endif /* Py_GIL_DISABLED */
diff --git a/Lib/test/test_capi/test_type.py b/Lib/test/test_capi/test_type.py
index 92d056e802eeed..ffcaae73bca236 100644
--- a/Lib/test/test_capi/test_type.py
+++ b/Lib/test/test_capi/test_type.py
@@ -67,3 +67,10 @@ class FreezeThis(metaclass=Meta):
Base.value = 3
type_freeze(FreezeThis)
self.assertEqual(FreezeThis.value, 2)
+
+ def test_manual_heap_type(self):
+ # gh-128923: test that a manually allocated and initailized heap type
+ # works correctly
+ ManualHeapType = _testcapi.ManualHeapType
+ for i in range(100):
+ self.assertIsInstance(ManualHeapType(), ManualHeapType)
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 7d304add5999d1..b657bb665bd5c5 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -4149,6 +4149,61 @@ static PyTypeObject ContainerNoGC_type = {
.tp_new = ContainerNoGC_new,
};
+/* Manually allocated heap type */
+
+typedef struct {
+ PyObject_HEAD
+ PyObject *dict;
+} ManualHeapType;
+
+static int
+ManualHeapType_traverse(PyObject *self, visitproc visit, void *arg)
+{
+ ManualHeapType *mht = (ManualHeapType *)self;
+ Py_VISIT(mht->dict);
+ return 0;
+}
+
+static void
+ManualHeapType_dealloc(PyObject *self)
+{
+ ManualHeapType *mht = (ManualHeapType *)self;
+ PyObject_GC_UnTrack(self);
+ Py_XDECREF(mht->dict);
+ PyTypeObject *type = Py_TYPE(self);
+ Py_TYPE(self)->tp_free(self);
+ Py_DECREF(type);
+}
+
+static PyObject *
+create_manual_heap_type(void)
+{
+ // gh-128923: Ensure that a heap type allocated through PyType_Type.tp_alloc
+ // with minimal initialization works correctly.
+ PyHeapTypeObject *heap_type = (PyHeapTypeObject *)PyType_Type.tp_alloc(&PyType_Type, 0);
+ if (heap_type == NULL) {
+ return NULL;
+ }
+ PyTypeObject* type = &heap_type->ht_type;
+ type->tp_basicsize = sizeof(ManualHeapType);
+ type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_HAVE_GC;
+ type->tp_new = PyType_GenericNew;
+ type->tp_name = "ManualHeapType";
+ type->tp_dictoffset = offsetof(ManualHeapType, dict);
+ type->tp_traverse = ManualHeapType_traverse;
+ type->tp_dealloc = ManualHeapType_dealloc;
+ heap_type->ht_name = PyUnicode_FromString(type->tp_name);
+ if (!heap_type->ht_name) {
+ Py_DECREF(type);
+ return NULL;
+ }
+ heap_type->ht_qualname = Py_NewRef(heap_type->ht_name);
+ if (PyType_Ready(type) < 0) {
+ Py_DECREF(type);
+ return NULL;
+ }
+ return (PyObject *)type;
+}
static struct PyModuleDef _testcapimodule = {
PyModuleDef_HEAD_INIT,
@@ -4283,6 +4338,15 @@ PyInit__testcapi(void)
(PyObject *) &ContainerNoGC_type) < 0)
return NULL;
+ PyObject *manual_heap_type = create_manual_heap_type();
+ if (manual_heap_type == NULL) {
+ return NULL;
+ }
+ if (PyModule_Add(m, "ManualHeapType", manual_heap_type) < 0) {
+ return NULL;
+ }
+
+
/* Include tests from the _testcapi/ directory */
if (_PyTestCapi_Init_Vectorcall(m) < 0) {
return NULL;
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index 15b36a868a47df..539200c97c1206 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -1911,7 +1911,7 @@ code_dealloc(PyObject *self)
Py_XDECREF(co->co_linetable);
Py_XDECREF(co->co_exceptiontable);
#ifdef Py_GIL_DISABLED
- assert(co->_co_unique_id == -1);
+ assert(co->_co_unique_id == _Py_INVALID_UNIQUE_ID);
#endif
if (co->_co_cached != NULL) {
Py_XDECREF(co->_co_cached->_co_code);
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 82789d5e56f523..504e65b01ca959 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -1659,6 +1659,9 @@ _PyDict_EnablePerThreadRefcounting(PyObject *op)
assert(PyDict_Check(op));
#ifdef Py_GIL_DISABLED
Py_ssize_t id = _PyObject_AssignUniqueId(op);
+ if (id == _Py_INVALID_UNIQUE_ID) {
+ return;
+ }
if ((uint64_t)id >= (uint64_t)DICT_UNIQUE_ID_MAX) {
_PyObject_ReleaseUniqueId(id);
return;
@@ -1666,8 +1669,7 @@ _PyDict_EnablePerThreadRefcounting(PyObject *op)
PyDictObject *mp = (PyDictObject *)op;
assert((mp->_ma_watcher_tag >> DICT_UNIQUE_ID_SHIFT) == 0);
- // Plus 1 so that _ma_watcher_tag=0 represents an unassigned id
- mp->_ma_watcher_tag += ((uint64_t)id + 1) << DICT_UNIQUE_ID_SHIFT;
+ mp->_ma_watcher_tag += (uint64_t)id << DICT_UNIQUE_ID_SHIFT;
#endif
}
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index d8f5f6d9cb2366..93920341a179e8 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -6160,7 +6160,7 @@ type_dealloc(PyObject *self)
Py_XDECREF(et->ht_module);
PyMem_Free(et->_ht_tpname);
#ifdef Py_GIL_DISABLED
- assert(et->unique_id == -1);
+ assert(et->unique_id == _Py_INVALID_UNIQUE_ID);
#endif
et->ht_token = NULL;
Py_TYPE(type)->tp_free((PyObject *)type);
diff --git a/Python/uniqueid.c b/Python/uniqueid.c
index b9f30713feeb57..64c3e6cfbbe825 100644
--- a/Python/uniqueid.c
+++ b/Python/uniqueid.c
@@ -86,7 +86,7 @@ _PyObject_AssignUniqueId(PyObject *obj)
if (pool->freelist == NULL) {
if (resize_interp_type_id_pool(pool) < 0) {
UNLOCK_POOL(pool);
- return -1;
+ return _Py_INVALID_UNIQUE_ID;
}
}
@@ -94,7 +94,9 @@ _PyObject_AssignUniqueId(PyObject *obj)
pool->freelist = entry->next;
entry->obj = obj;
_PyObject_SetDeferredRefcount(obj);
- Py_ssize_t unique_id = (entry - pool->table);
+ // The unique id is one plus the index of the entry in the table.
+ Py_ssize_t unique_id = (entry - pool->table) + 1;
+ assert(unique_id > 0);
UNLOCK_POOL(pool);
return unique_id;
}
@@ -106,8 +108,9 @@ _PyObject_ReleaseUniqueId(Py_ssize_t unique_id)
struct _Py_unique_id_pool *pool = &interp->unique_ids;
LOCK_POOL(pool);
- assert(unique_id >= 0 && unique_id < pool->size);
- _Py_unique_id_entry *entry = &pool->table[unique_id];
+ assert(unique_id > 0 && unique_id <= pool->size);
+ Py_ssize_t idx = unique_id - 1;
+ _Py_unique_id_entry *entry = &pool->table[idx];
entry->next = pool->freelist;
pool->freelist = entry;
UNLOCK_POOL(pool);
@@ -116,18 +119,18 @@ _PyObject_ReleaseUniqueId(Py_ssize_t unique_id)
static Py_ssize_t
clear_unique_id(PyObject *obj)
{
- Py_ssize_t id = -1;
+ Py_ssize_t id = _Py_INVALID_UNIQUE_ID;
if (PyType_Check(obj)) {
if (PyType_HasFeature((PyTypeObject *)obj, Py_TPFLAGS_HEAPTYPE)) {
PyHeapTypeObject *ht = (PyHeapTypeObject *)obj;
id = ht->unique_id;
- ht->unique_id = -1;
+ ht->unique_id = _Py_INVALID_UNIQUE_ID;
}
}
else if (PyCode_Check(obj)) {
PyCodeObject *co = (PyCodeObject *)obj;
id = co->_co_unique_id;
- co->_co_unique_id = -1;
+ co->_co_unique_id = _Py_INVALID_UNIQUE_ID;
}
else if (PyDict_Check(obj)) {
PyDictObject *mp = (PyDictObject *)obj;
@@ -141,23 +144,23 @@ void
_PyObject_DisablePerThreadRefcounting(PyObject *obj)
{
Py_ssize_t id = clear_unique_id(obj);
- if (id >= 0) {
+ if (id != _Py_INVALID_UNIQUE_ID) {
_PyObject_ReleaseUniqueId(id);
}
}
void
-_PyObject_ThreadIncrefSlow(PyObject *obj, Py_ssize_t unique_id)
+_PyObject_ThreadIncrefSlow(PyObject *obj, size_t idx)
{
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
- if (unique_id < 0 || resize_local_refcounts(tstate) < 0) {
+ if (((Py_ssize_t)idx) < 0 || resize_local_refcounts(tstate) < 0) {
// just incref the object directly.
Py_INCREF(obj);
return;
}
- assert(unique_id < tstate->refcounts.size);
- tstate->refcounts.values[unique_id]++;
+ assert(idx < (size_t)tstate->refcounts.size);
+ tstate->refcounts.values[idx]++;
#ifdef Py_REF_DEBUG
_Py_IncRefTotal((PyThreadState *)tstate);
#endif
@@ -217,7 +220,7 @@ _PyObject_FinalizeUniqueIdPool(PyInterpreterState *interp)
if (obj != NULL) {
Py_ssize_t id = clear_unique_id(obj);
(void)id;
- assert(id == i);
+ assert(id == i + 1);
}
}
PyMem_Free(pool->table);
1
0
https://github.com/python/cpython/commit/767c89ba7c5a70626df6e75eb56b546bf9…
commit: 767c89ba7c5a70626df6e75eb56b546bf911b997
branch: main
author: Tian Gao <gaogaotiantian(a)hotmail.com>
committer: gaogaotiantian <gaogaotiantian(a)hotmail.com>
date: 2025-01-17T10:33:04-05:00
summary:
gh-58956: Fix a frame refleak in bdb (#128190)
files:
A Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst
M Lib/bdb.py
M Lib/test/test_pdb.py
diff --git a/Lib/bdb.py b/Lib/bdb.py
index 81bba8a130f97c..73e249621a053b 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -404,6 +404,7 @@ def set_trace(self, frame=None):
frame.f_trace_lines = True
frame = frame.f_back
self.set_stepinstr()
+ self.enterframe = None
sys.settrace(self.trace_dispatch)
def set_continue(self):
@@ -423,6 +424,7 @@ def set_continue(self):
for frame, (trace_lines, trace_opcodes) in self.frame_trace_lines_opcodes.items():
frame.f_trace_lines, frame.f_trace_opcodes = trace_lines, trace_opcodes
self.frame_trace_lines_opcodes = {}
+ self.enterframe = None
def set_quit(self):
"""Set quitting attribute to True.
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index ace5544f4106d0..09601623b29ac1 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -3009,6 +3009,57 @@ def test_pdb_f_trace_lines():
(Pdb) continue
"""
+def test_pdb_frame_refleak():
+ """
+ pdb should not leak reference to frames
+
+ >>> def frame_leaker(container):
+ ... import sys
+ ... container.append(sys._getframe())
+ ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ ... pass
+
+ >>> def test_function():
+ ... import gc
+ ... container = []
+ ... frame_leaker(container) # c
+ ... print(len(gc.get_referrers(container[0])))
+ ... container = []
+ ... frame_leaker(container) # n c
+ ... print(len(gc.get_referrers(container[0])))
+ ... container = []
+ ... frame_leaker(container) # r c
+ ... print(len(gc.get_referrers(container[0])))
+
+ >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
+ ... 'continue',
+ ... 'next',
+ ... 'continue',
+ ... 'return',
+ ... 'continue',
+ ... ]):
+ ... test_function()
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(4)frame_leaker()
+ -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ (Pdb) continue
+ 1
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(4)frame_leaker()
+ -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ (Pdb) next
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(5)frame_leaker()
+ -> pass
+ (Pdb) continue
+ 1
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(4)frame_leaker()
+ -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ (Pdb) return
+ --Return--
+ > <doctest test.test_pdb.test_pdb_frame_refleak[0]>(5)frame_leaker()->None
+ -> pass
+ (Pdb) continue
+ 1
+ """
+
def test_pdb_function_break():
"""Testing the line number of break on function
diff --git a/Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst b/Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst
new file mode 100644
index 00000000000000..b78bc5aaf44217
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst
@@ -0,0 +1 @@
+Fixed a frame reference leak in :mod:`bdb`.
1
0
https://github.com/python/cpython/commit/d7f703d54d64c5a380f43e2363c439f7da…
commit: d7f703d54d64c5a380f43e2363c439f7dab0d09d
branch: main
author: Victor Stinner <vstinner(a)python.org>
committer: vstinner <vstinner(a)python.org>
date: 2025-01-17T14:55:43+01:00
summary:
gh-59705: Implement _thread.set_name() on Windows (#128675)
Implement set_name() with SetThreadDescription() and _get_name() with
GetThreadDescription(). If SetThreadDescription() or
GetThreadDescription() is not available in kernelbase.dll, delete the
method when the _thread module is imported.
Truncate the thread name to 32766 characters.
Co-authored-by: Eryk Sun <eryksun(a)gmail.com>
files:
M Lib/test/test_threading.py
M Modules/_threadmodule.c
M Modules/clinic/_threadmodule.c.h
M PC/pyconfig.h.in
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index 3e164a12581dd1..214e1ba0b53dd2 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -2130,6 +2130,15 @@ def test_set_name(self):
# Test long non-ASCII name (truncated)
"x" * (limit - 1) + "é€",
+
+ # Test long non-BMP names (truncated) creating surrogate pairs
+ # on Windows
+ "x" * (limit - 1) + "\U0010FFFF",
+ "x" * (limit - 2) + "\U0010FFFF" * 2,
+ "x" + "\U0001f40d" * limit,
+ "xx" + "\U0001f40d" * limit,
+ "xxx" + "\U0001f40d" * limit,
+ "xxxx" + "\U0001f40d" * limit,
]
if os_helper.FS_NONASCII:
tests.append(f"nonascii:{os_helper.FS_NONASCII}")
@@ -2146,15 +2155,31 @@ def work():
work_name = _thread._get_name()
for name in tests:
- encoded = name.encode(encoding, "replace")
- if b'\0' in encoded:
- encoded = encoded.split(b'\0', 1)[0]
- if truncate is not None:
- encoded = encoded[:truncate]
- if sys.platform.startswith("solaris"):
- expected = encoded.decode("utf-8", "surrogateescape")
+ if not support.MS_WINDOWS:
+ encoded = name.encode(encoding, "replace")
+ if b'\0' in encoded:
+ encoded = encoded.split(b'\0', 1)[0]
+ if truncate is not None:
+ encoded = encoded[:truncate]
+ if sys.platform.startswith("solaris"):
+ expected = encoded.decode("utf-8", "surrogateescape")
+ else:
+ expected = os.fsdecode(encoded)
else:
- expected = os.fsdecode(encoded)
+ size = 0
+ chars = []
+ for ch in name:
+ if ord(ch) > 0xFFFF:
+ size += 2
+ else:
+ size += 1
+ if size > truncate:
+ break
+ chars.append(ch)
+ expected = ''.join(chars)
+
+ if '\0' in expected:
+ expected = expected.split('\0', 1)[0]
with self.subTest(name=name, expected=expected):
work_name = None
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c
index d19ae326bd6b48..586ed368024fb1 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -47,6 +47,14 @@ get_thread_state(PyObject *module)
}
+#ifdef MS_WINDOWS
+typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*);
+typedef HRESULT (WINAPI *PF_SET_THREAD_DESCRIPTION)(HANDLE, PCWSTR);
+static PF_GET_THREAD_DESCRIPTION pGetThreadDescription = NULL;
+static PF_SET_THREAD_DESCRIPTION pSetThreadDescription = NULL;
+#endif
+
+
/*[clinic input]
module _thread
[clinic start generated code]*/
@@ -2368,7 +2376,7 @@ Internal only. Return a non-zero integer that uniquely identifies the main threa
of the main interpreter.");
-#ifdef HAVE_PTHREAD_GETNAME_NP
+#if defined(HAVE_PTHREAD_GETNAME_NP) || defined(MS_WINDOWS)
/*[clinic input]
_thread._get_name
@@ -2379,6 +2387,7 @@ static PyObject *
_thread__get_name_impl(PyObject *module)
/*[clinic end generated code: output=20026e7ee3da3dd7 input=35cec676833d04c8]*/
{
+#ifndef MS_WINDOWS
// Linux and macOS are limited to respectively 16 and 64 bytes
char name[100];
pthread_t thread = pthread_self();
@@ -2393,11 +2402,26 @@ _thread__get_name_impl(PyObject *module)
#else
return PyUnicode_DecodeFSDefault(name);
#endif
+#else
+ // Windows implementation
+ assert(pGetThreadDescription != NULL);
+
+ wchar_t *name;
+ HRESULT hr = pGetThreadDescription(GetCurrentThread(), &name);
+ if (FAILED(hr)) {
+ PyErr_SetFromWindowsErr(0);
+ return NULL;
+ }
+
+ PyObject *name_obj = PyUnicode_FromWideChar(name, -1);
+ LocalFree(name);
+ return name_obj;
+#endif
}
#endif // HAVE_PTHREAD_GETNAME_NP
-#ifdef HAVE_PTHREAD_SETNAME_NP
+#if defined(HAVE_PTHREAD_SETNAME_NP) || defined(MS_WINDOWS)
/*[clinic input]
_thread.set_name
@@ -2410,6 +2434,7 @@ static PyObject *
_thread_set_name_impl(PyObject *module, PyObject *name_obj)
/*[clinic end generated code: output=402b0c68e0c0daed input=7e7acd98261be82f]*/
{
+#ifndef MS_WINDOWS
#ifdef __sun
// Solaris always uses UTF-8
const char *encoding = "utf-8";
@@ -2455,6 +2480,35 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj)
return PyErr_SetFromErrno(PyExc_OSError);
}
Py_RETURN_NONE;
+#else
+ // Windows implementation
+ assert(pSetThreadDescription != NULL);
+
+ Py_ssize_t len;
+ wchar_t *name = PyUnicode_AsWideCharString(name_obj, &len);
+ if (name == NULL) {
+ return NULL;
+ }
+
+ if (len > PYTHREAD_NAME_MAXLEN) {
+ // Truncate the name
+ Py_UCS4 ch = name[PYTHREAD_NAME_MAXLEN-1];
+ if (Py_UNICODE_IS_HIGH_SURROGATE(ch)) {
+ name[PYTHREAD_NAME_MAXLEN-1] = 0;
+ }
+ else {
+ name[PYTHREAD_NAME_MAXLEN] = 0;
+ }
+ }
+
+ HRESULT hr = pSetThreadDescription(GetCurrentThread(), name);
+ PyMem_Free(name);
+ if (FAILED(hr)) {
+ PyErr_SetFromWindowsErr((int)hr);
+ return NULL;
+ }
+ Py_RETURN_NONE;
+#endif
}
#endif // HAVE_PTHREAD_SETNAME_NP
@@ -2598,6 +2652,31 @@ thread_module_exec(PyObject *module)
}
#endif
+#ifdef MS_WINDOWS
+ HMODULE kernelbase = GetModuleHandleW(L"kernelbase.dll");
+ if (kernelbase != NULL) {
+ if (pGetThreadDescription == NULL) {
+ pGetThreadDescription = (PF_GET_THREAD_DESCRIPTION)GetProcAddress(
+ kernelbase, "GetThreadDescription");
+ }
+ if (pSetThreadDescription == NULL) {
+ pSetThreadDescription = (PF_SET_THREAD_DESCRIPTION)GetProcAddress(
+ kernelbase, "SetThreadDescription");
+ }
+ }
+
+ if (pGetThreadDescription == NULL) {
+ if (PyObject_DelAttrString(module, "_get_name") < 0) {
+ return -1;
+ }
+ }
+ if (pSetThreadDescription == NULL) {
+ if (PyObject_DelAttrString(module, "set_name") < 0) {
+ return -1;
+ }
+ }
+#endif
+
return 0;
}
diff --git a/Modules/clinic/_threadmodule.c.h b/Modules/clinic/_threadmodule.c.h
index 8f0507d40285b3..09b7afebd6d8d9 100644
--- a/Modules/clinic/_threadmodule.c.h
+++ b/Modules/clinic/_threadmodule.c.h
@@ -8,7 +8,7 @@ preserve
#endif
#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
-#if defined(HAVE_PTHREAD_GETNAME_NP)
+#if (defined(HAVE_PTHREAD_GETNAME_NP) || defined(MS_WINDOWS))
PyDoc_STRVAR(_thread__get_name__doc__,
"_get_name($module, /)\n"
@@ -28,9 +28,9 @@ _thread__get_name(PyObject *module, PyObject *Py_UNUSED(ignored))
return _thread__get_name_impl(module);
}
-#endif /* defined(HAVE_PTHREAD_GETNAME_NP) */
+#endif /* (defined(HAVE_PTHREAD_GETNAME_NP) || defined(MS_WINDOWS)) */
-#if defined(HAVE_PTHREAD_SETNAME_NP)
+#if (defined(HAVE_PTHREAD_SETNAME_NP) || defined(MS_WINDOWS))
PyDoc_STRVAR(_thread_set_name__doc__,
"set_name($module, /, name)\n"
@@ -92,7 +92,7 @@ _thread_set_name(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb
return return_value;
}
-#endif /* defined(HAVE_PTHREAD_SETNAME_NP) */
+#endif /* (defined(HAVE_PTHREAD_SETNAME_NP) || defined(MS_WINDOWS)) */
#ifndef _THREAD__GET_NAME_METHODDEF
#define _THREAD__GET_NAME_METHODDEF
@@ -101,4 +101,4 @@ _thread_set_name(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb
#ifndef _THREAD_SET_NAME_METHODDEF
#define _THREAD_SET_NAME_METHODDEF
#endif /* !defined(_THREAD_SET_NAME_METHODDEF) */
-/*[clinic end generated code: output=b5cb85aaccc45bf6 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=6e88ef6b126cece8 input=a9049054013a1b77]*/
diff --git a/PC/pyconfig.h.in b/PC/pyconfig.h.in
index 010f5fe5646630..837461d0e884bc 100644
--- a/PC/pyconfig.h.in
+++ b/PC/pyconfig.h.in
@@ -753,4 +753,8 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */
/* Define if libssl has X509_VERIFY_PARAM_set1_host and related function */
#define HAVE_X509_VERIFY_PARAM_SET1_HOST 1
+// Truncate the thread name to 64 characters. The OS limit is 32766 wide
+// characters, but long names aren't of practical use.
+#define PYTHREAD_NAME_MAXLEN 32766
+
#endif /* !Py_CONFIG_H */
1
0