From 859aecc33b82f45e5b7ae30138d28f2a2f33a575 Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Thu, 11 Sep 2025 00:08:53 -0700 Subject: [PATCH 1/8] gh-138432: Improved invalid path checking in zoneinfo.reset_tzpath() (GH-138433) * Improve error messages for path-like relative paths and path-like bytes paths. * TZPATH is now always a tuple of strings. --- Lib/test/test_zoneinfo/test_zoneinfo.py | 11 ++++++++++- Lib/zoneinfo/_tzpath.py | 7 +++++++ .../2025-09-03-15-20-10.gh-issue-138432.RMc7UX.rst | 6 ++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-09-03-15-20-10.gh-issue-138432.RMc7UX.rst diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index addc905af5ede9..2f9a5dfc1a89a0 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -18,7 +18,7 @@ from functools import cached_property from test.support import MISSING_C_DOCSTRINGS -from test.support.os_helper import EnvironmentVarGuard +from test.support.os_helper import EnvironmentVarGuard, FakePath from test.test_zoneinfo import _support as test_support from test.test_zoneinfo._support import TZPATH_TEST_LOCK, ZoneInfoTestBase from test.support.import_helper import import_module, CleanImport @@ -1784,6 +1784,7 @@ def test_reset_tzpath_relative_paths(self): ("/usr/share/zoneinfo", "../relative/path",), ("path/to/somewhere", "../relative/path",), ("/usr/share/zoneinfo", "path/to/somewhere", "../relative/path",), + (FakePath("path/to/somewhere"),) ] for input_paths in bad_values: with self.subTest(input_paths=input_paths): @@ -1795,6 +1796,9 @@ def test_tzpath_type_error(self): "/etc/zoneinfo:/usr/share/zoneinfo", b"/etc/zoneinfo:/usr/share/zoneinfo", 0, + (b"/bytes/path", "/valid/path"), + (FakePath(b"/bytes/path"),), + (0,), ] for bad_value in bad_values: @@ -1805,6 +1809,7 @@ def test_tzpath_type_error(self): def test_tzpath_attribute(self): tzpath_0 = [f"{DRIVE}/one", f"{DRIVE}/two"] tzpath_1 = [f"{DRIVE}/three"] + tzpath_pathlike = (FakePath(f"{DRIVE}/usr/share/zoneinfo"),) with self.tzpath_context(tzpath_0): query_0 = self.module.TZPATH @@ -1812,8 +1817,12 @@ def test_tzpath_attribute(self): with self.tzpath_context(tzpath_1): query_1 = self.module.TZPATH + with self.tzpath_context(tzpath_pathlike): + query_pathlike = self.module.TZPATH + self.assertSequenceEqual(tzpath_0, query_0) self.assertSequenceEqual(tzpath_1, query_1) + self.assertSequenceEqual(tuple([os.fspath(p) for p in tzpath_pathlike]), query_pathlike) class CTzPathTest(TzPathTest): diff --git a/Lib/zoneinfo/_tzpath.py b/Lib/zoneinfo/_tzpath.py index 78fa6f00a8590a..177d32c35eff29 100644 --- a/Lib/zoneinfo/_tzpath.py +++ b/Lib/zoneinfo/_tzpath.py @@ -13,6 +13,13 @@ def _reset_tzpath(to=None, stacklevel=4): + f"not {type(tzpaths)}: {tzpaths!r}" ) + tzpaths = [os.fspath(p) for p in tzpaths] + if not all(isinstance(p, str) for p in tzpaths): + raise TypeError( + "All elements of a tzpath sequence must be strings or " + "os.PathLike objects which convert to strings." + ) + if not all(map(os.path.isabs, tzpaths)): raise ValueError(_get_invalid_paths_message(tzpaths)) base_tzpath = tzpaths diff --git a/Misc/NEWS.d/next/Library/2025-09-03-15-20-10.gh-issue-138432.RMc7UX.rst b/Misc/NEWS.d/next/Library/2025-09-03-15-20-10.gh-issue-138432.RMc7UX.rst new file mode 100644 index 00000000000000..b48b978a6d5c75 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-03-15-20-10.gh-issue-138432.RMc7UX.rst @@ -0,0 +1,6 @@ +:meth:`zoneinfo.reset_tzpath` will now convert any :class:`os.PathLike` objects +it receives into strings before adding them to ``TZPATH``. It will raise +``TypeError`` if anything other than a string is found after this conversion. +If given an :class:`os.PathLike` object that represents a relative path, it +will now raise ``ValueError`` instead of ``TypeError``, and present a more +informative error message. From 4978bfca1033358504c7a4ebbe33f0c28bededf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:56:20 +0200 Subject: [PATCH 2/8] gh-116946: add `Py_TPFLAGS_IMMUTABLETYPE` to several internal types (#138582) The following types are now immutable: * `_curses_panel.panel`, * `[posix,nt].ScandirIterator`, `[posix,nt].DirEntry` (exposed in `os.py`), * `_remote_debugging.RemoteUnwinder`, * `_tkinter.Tcl_Obj`, `_tkinter.tkapp`, `_tkinter.tktimertoken`, * `zlib.Compress`, and `zlib.Decompress`. --- ...-09-06-14-47-23.gh-issue-116946.hj_u1t.rst | 5 +++ ...-09-06-14-53-19.gh-issue-116946.c-npxd.rst | 2 + ...-09-06-14-54-01.gh-issue-116946.hzQEWI.rst | 3 ++ ...-09-06-14-56-40.gh-issue-116946.GGIeyO.rst | 2 + Modules/_curses_panel.c | 1 + Modules/_remote_debugging_module.c | 5 ++- Modules/_tkinter.c | 39 ++++++++++++------- Modules/posixmodule.c | 28 +++++++------ Modules/zlibmodule.c | 2 + 9 files changed, 60 insertions(+), 27 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-09-06-14-47-23.gh-issue-116946.hj_u1t.rst create mode 100644 Misc/NEWS.d/next/Library/2025-09-06-14-53-19.gh-issue-116946.c-npxd.rst create mode 100644 Misc/NEWS.d/next/Library/2025-09-06-14-54-01.gh-issue-116946.hzQEWI.rst create mode 100644 Misc/NEWS.d/next/Library/2025-09-06-14-56-40.gh-issue-116946.GGIeyO.rst diff --git a/Misc/NEWS.d/next/Library/2025-09-06-14-47-23.gh-issue-116946.hj_u1t.rst b/Misc/NEWS.d/next/Library/2025-09-06-14-47-23.gh-issue-116946.hj_u1t.rst new file mode 100644 index 00000000000000..b078070166c26d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-06-14-47-23.gh-issue-116946.hj_u1t.rst @@ -0,0 +1,5 @@ +:mod:`tkinter`: the types :class:`!_tkinter.Tcl_Obj` (wrapper for Tcl objects), +:class:`!_tkinter.tktimertoken` (obtained by calling ``createtimerhandler()`` +on a :attr:`Tk ` application) and :class:`!_tkinter.tkapp` +(the runtime type of Tk applications) are now immutable. +Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Library/2025-09-06-14-53-19.gh-issue-116946.c-npxd.rst b/Misc/NEWS.d/next/Library/2025-09-06-14-53-19.gh-issue-116946.c-npxd.rst new file mode 100644 index 00000000000000..7d7d7ac5b289f3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-06-14-53-19.gh-issue-116946.c-npxd.rst @@ -0,0 +1,2 @@ +:mod:`os`: the :class:`os.DirEntry` type and the type of :func:`os.scandir` +are now immutable. Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Library/2025-09-06-14-54-01.gh-issue-116946.hzQEWI.rst b/Misc/NEWS.d/next/Library/2025-09-06-14-54-01.gh-issue-116946.hzQEWI.rst new file mode 100644 index 00000000000000..91c03fc740463f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-06-14-54-01.gh-issue-116946.hzQEWI.rst @@ -0,0 +1,3 @@ +:mod:`zlib`: the types of :func:`zlib.compressobj` +and :func:`zlib.decompressobj` are now immutable. +Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Library/2025-09-06-14-56-40.gh-issue-116946.GGIeyO.rst b/Misc/NEWS.d/next/Library/2025-09-06-14-56-40.gh-issue-116946.GGIeyO.rst new file mode 100644 index 00000000000000..90cf43b788137f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-06-14-56-40.gh-issue-116946.GGIeyO.rst @@ -0,0 +1,2 @@ +:mod:`curses.panel`: the type of :func:`curses.panel.new_panel` is now +immutable. Patch by Bénédikt Tran. diff --git a/Modules/_curses_panel.c b/Modules/_curses_panel.c index 66a8c40953da8c..3b46fdf838b16f 100644 --- a/Modules/_curses_panel.c +++ b/Modules/_curses_panel.c @@ -684,6 +684,7 @@ static PyType_Spec PyCursesPanel_Type_spec = { .flags = ( Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC ), .slots = PyCursesPanel_Type_slots diff --git a/Modules/_remote_debugging_module.c b/Modules/_remote_debugging_module.c index 827dcf4dcc4c14..c306143ee73b18 100644 --- a/Modules/_remote_debugging_module.c +++ b/Modules/_remote_debugging_module.c @@ -3062,7 +3062,10 @@ static PyType_Slot RemoteUnwinder_slots[] = { static PyType_Spec RemoteUnwinder_spec = { .name = "_remote_debugging.RemoteUnwinder", .basicsize = sizeof(RemoteUnwinderObject), - .flags = Py_TPFLAGS_DEFAULT, + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_IMMUTABLETYPE + ), .slots = RemoteUnwinder_slots, }; diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index d921c46d645ac0..f0882191d3c3e8 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -906,11 +906,14 @@ static PyType_Slot PyTclObject_Type_slots[] = { }; static PyType_Spec PyTclObject_Type_spec = { - "_tkinter.Tcl_Obj", - sizeof(PyTclObject), - 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, - PyTclObject_Type_slots, + .name = "_tkinter.Tcl_Obj", + .basicsize = sizeof(PyTclObject), + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE + ), + .slots = PyTclObject_Type_slots, }; @@ -3267,11 +3270,14 @@ static PyType_Slot Tktt_Type_slots[] = { }; static PyType_Spec Tktt_Type_spec = { - "_tkinter.tktimertoken", - sizeof(TkttObject), - 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, - Tktt_Type_slots, + .name = "_tkinter.tktimertoken", + .basicsize = sizeof(TkttObject), + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE + ), + .slots = Tktt_Type_slots, }; @@ -3323,11 +3329,14 @@ static PyType_Slot Tkapp_Type_slots[] = { static PyType_Spec Tkapp_Type_spec = { - "_tkinter.tkapp", - sizeof(TkappObject), - 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, - Tkapp_Type_slots, + .name = "_tkinter.tkapp", + .basicsize = sizeof(TkappObject), + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE + ), + .slots = Tkapp_Type_slots, }; static PyMethodDef moduleMethods[] = diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 50d0ff1dc2127c..f229c4e8dd9322 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -16050,11 +16050,14 @@ static PyType_Slot DirEntryType_slots[] = { }; static PyType_Spec DirEntryType_spec = { - MODNAME ".DirEntry", - sizeof(DirEntry), - 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, - DirEntryType_slots + .name = MODNAME ".DirEntry", + .basicsize = sizeof(DirEntry), + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE + ), + .slots = DirEntryType_slots }; @@ -16492,14 +16495,17 @@ static PyType_Slot ScandirIteratorType_slots[] = { }; static PyType_Spec ScandirIteratorType_spec = { - MODNAME ".ScandirIterator", - sizeof(ScandirIterator), - 0, + .name = MODNAME ".ScandirIterator", + .basicsize = sizeof(ScandirIterator), // bpo-40549: Py_TPFLAGS_BASETYPE should not be used, since // PyType_GetModule(Py_TYPE(self)) doesn't work on a subclass instance. - (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_FINALIZE - | Py_TPFLAGS_DISALLOW_INSTANTIATION), - ScandirIteratorType_slots + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_HAVE_FINALIZE + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE + ), + .slots = ScandirIteratorType_slots }; /*[clinic input] diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index 0625a6f8052b6c..1ee14e31612860 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -2043,6 +2043,7 @@ static PyType_Spec Comptype_spec = { .flags = ( Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC ), .slots= Comptype_slots, @@ -2062,6 +2063,7 @@ static PyType_Spec Decomptype_spec = { .flags = ( Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC ), .slots = Decomptype_slots, From a1707e4516c4861efbd43068da6e0db2d38ef87d Mon Sep 17 00:00:00 2001 From: yihong Date: Thu, 11 Sep 2025 16:54:16 +0800 Subject: [PATCH 3/8] gh-138081: fix some dead links in InternalDocs (#138082) --- InternalDocs/compiler.md | 2 +- InternalDocs/generators.md | 2 +- InternalDocs/jit.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/InternalDocs/compiler.md b/InternalDocs/compiler.md index 02bbdf6071fa12..742af5efcf5ce5 100644 --- a/InternalDocs/compiler.md +++ b/InternalDocs/compiler.md @@ -604,4 +604,4 @@ References pp. 213--227, 1997. [^2]: The Zephyr Abstract Syntax Description Language.: - https://www.cs.princeton.edu/research/techreps/TR-554-97 + https://www.cs.princeton.edu/research/techreps/254 diff --git a/InternalDocs/generators.md b/InternalDocs/generators.md index 979a5b51521e4e..fa652cd231cfa9 100644 --- a/InternalDocs/generators.md +++ b/InternalDocs/generators.md @@ -64,7 +64,7 @@ Iteration The [`FOR_ITER`](https://docs.python.org/dev/library/dis.html#opcode-FOR_ITER) instruction calls `__next__` on the iterator which is on the top of the stack, -and pushes the result to the stack. It has [`specializations`](adaptive.md) +and pushes the result to the stack. It has [`specializations`](interpreter.md) for a few common iterator types, including `FOR_ITER_GEN`, for iterating over a generator. `FOR_ITER_GEN` bypasses the call to `__next__`, and instead directly pushes the generator stack and resumes its execution from the diff --git a/InternalDocs/jit.md b/InternalDocs/jit.md index c98ccb3ace8d9e..095853807377a4 100644 --- a/InternalDocs/jit.md +++ b/InternalDocs/jit.md @@ -89,7 +89,7 @@ When the full jit is enabled (python was configured with [`--enable-experimental-jit`](https://docs.python.org/dev/using/configure.html#cmdoption-enable-experimental-jit), the uop executor's `jit_code` field is populated with a pointer to a compiled C function that implements the executor logic. This function's signature is -defined by `jit_func` in [`pycore_jit.h`](Include/internal/pycore_jit.h). +defined by `jit_func` in [`pycore_jit.h`](../Include/internal/pycore_jit.h). When the executor is invoked by `ENTER_EXECUTOR`, instead of jumping to the uop interpreter at `tier2_dispatch`, the executor runs the function that `jit_code` points to. This function returns the instruction pointer From 011179a79a0d7b93ce074b25b0819e96b6dd3315 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 11 Sep 2025 13:30:53 +0300 Subject: [PATCH 4/8] gh-71810: Fix corner case (length==0) for int.to_bytes() (#138739) ```pycon >>> (0).to_bytes(0, 'big', signed=True) b'' >>> (-1).to_bytes(0, 'big', signed=True) # was b'' Traceback (most recent call last): File "", line 1, in (-1).to_bytes(0, 'big', signed=True) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^ OverflowError: int too big to convert ``` Co-authored-by: Serhiy Storchaka --- Lib/test/test_long.py | 9 +++++++-- .../2025-09-10-14-53-59.gh-issue-71810.ppf0J-.rst | 2 ++ Objects/longobject.c | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-09-10-14-53-59.gh-issue-71810.ppf0J-.rst diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index d63bc19ed9c9a2..b48a8812a1a2d1 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -1374,17 +1374,22 @@ def equivalent_python(n, length, byteorder, signed=False): check(tests4, 'little', signed=False) self.assertRaises(OverflowError, (256).to_bytes, 1, 'big', signed=False) - self.assertRaises(OverflowError, (256).to_bytes, 1, 'big', signed=True) self.assertRaises(OverflowError, (256).to_bytes, 1, 'little', signed=False) - self.assertRaises(OverflowError, (256).to_bytes, 1, 'little', signed=True) + self.assertRaises(OverflowError, (128).to_bytes, 1, 'big', signed=True) + self.assertRaises(OverflowError, (128).to_bytes, 1, 'little', signed=True) + self.assertRaises(OverflowError, (-129).to_bytes, 1, 'big', signed=True) + self.assertRaises(OverflowError, (-129).to_bytes, 1, 'little', signed=True) self.assertRaises(OverflowError, (-1).to_bytes, 2, 'big', signed=False) self.assertRaises(OverflowError, (-1).to_bytes, 2, 'little', signed=False) self.assertEqual((0).to_bytes(0, 'big'), b'') + self.assertEqual((0).to_bytes(0, 'big', signed=True), b'') self.assertEqual((1).to_bytes(5, 'big'), b'\x00\x00\x00\x00\x01') self.assertEqual((0).to_bytes(5, 'big'), b'\x00\x00\x00\x00\x00') self.assertEqual((-1).to_bytes(5, 'big', signed=True), b'\xff\xff\xff\xff\xff') self.assertRaises(OverflowError, (1).to_bytes, 0, 'big') + self.assertRaises(OverflowError, (-1).to_bytes, 0, 'big', signed=True) + self.assertRaises(OverflowError, (-1).to_bytes, 0, 'little', signed=True) # gh-98783 class SubStr(str): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-10-14-53-59.gh-issue-71810.ppf0J-.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-10-14-53-59.gh-issue-71810.ppf0J-.rst new file mode 100644 index 00000000000000..a87db44225e825 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-10-14-53-59.gh-issue-71810.ppf0J-.rst @@ -0,0 +1,2 @@ +Raise :exc:`OverflowError` for ``(-1).to_bytes()`` for signed conversions +when bytes count is zero. Patch by Sergey B Kirpichev. diff --git a/Objects/longobject.c b/Objects/longobject.c index 287458ba2da62a..63b48572ff20d3 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -1209,7 +1209,7 @@ _PyLong_AsByteArray(PyLongObject* v, *p = (unsigned char)(accum & 0xff); p += pincr; } - else if (j == n && n > 0 && is_signed) { + else if (j == n && is_signed) { /* The main loop filled the byte array exactly, so the code just above didn't get to ensure there's a sign bit, and the loop below wouldn't add one either. Make sure a sign bit From 1da989be74eaa94590ec28e750e5352de1ead227 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 11 Sep 2025 06:39:09 -0400 Subject: [PATCH 5/8] gh-138479: Ensure that `__typing_subst__` returns a tuple (GH-138482) Raise an exception if __typing_subst__ returns a non-tuple object. --------- Co-authored-by: Serhiy Storchaka --- Lib/test/test_typing.py | 17 +++++++++++++++++ ...25-09-03-17-00-30.gh-issue-138479.qUxgWs.rst | 2 ++ Objects/genericaliasobject.c | 12 ++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-09-03-17-00-30.gh-issue-138479.qUxgWs.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 0fc5415c390145..8238c62f0715f8 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -5844,6 +5844,23 @@ class A: with self.assertRaises(TypeError): a[int] + def test_return_non_tuple_while_unpacking(self): + # GH-138497: GenericAlias objects didn't ensure that __typing_subst__ actually + # returned a tuple + class EvilTypeVar: + __typing_is_unpacked_typevartuple__ = True + def __typing_prepare_subst__(*_): + return None # any value + def __typing_subst__(*_): + return 42 # not tuple + + evil = EvilTypeVar() + # Create a dummy TypeAlias that will be given the evil generic from + # above. + type type_alias[*_] = 0 + with self.assertRaisesRegex(TypeError, ".+__typing_subst__.+tuple.+int.*"): + type_alias[evil][0] + class ClassVarTests(BaseTestCase): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-03-17-00-30.gh-issue-138479.qUxgWs.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-03-17-00-30.gh-issue-138479.qUxgWs.rst new file mode 100644 index 00000000000000..c94640af3b053c --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-03-17-00-30.gh-issue-138479.qUxgWs.rst @@ -0,0 +1,2 @@ +Fix a crash when a generic object's ``__typing_subst__`` returns an object +that isn't a :class:`tuple`. diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index b3ff933c9b584e..8b526f43f1e053 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -525,12 +525,24 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje return NULL; } if (unpack) { + if (!PyTuple_Check(arg)) { + Py_DECREF(newargs); + Py_DECREF(item); + Py_XDECREF(tuple_args); + PyObject *original = PyTuple_GET_ITEM(args, iarg); + PyErr_Format(PyExc_TypeError, + "expected __typing_subst__ of %T objects to return a tuple, not %T", + original, arg); + Py_DECREF(arg); + return NULL; + } jarg = tuple_extend(&newargs, jarg, &PyTuple_GET_ITEM(arg, 0), PyTuple_GET_SIZE(arg)); Py_DECREF(arg); if (jarg < 0) { Py_DECREF(item); Py_XDECREF(tuple_args); + assert(newargs == NULL); return NULL; } } From 101172486f81d00092ffea09f75d65770362e76b Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 11 Sep 2025 03:45:30 -0700 Subject: [PATCH 6/8] gh-138013: Move I/O test infrastructre to test_io.utils (#138475) This moves test support code to `test_io.utils` via copy/paste code movement then adjusts imports as needed (remove unneded + add all required). --- Lib/test/test_io/test_general.py | 309 +----------------------------- Lib/test/test_io/utils.py | 318 +++++++++++++++++++++++++++++++ 2 files changed, 319 insertions(+), 308 deletions(-) create mode 100644 Lib/test/test_io/utils.py diff --git a/Lib/test/test_io/test_general.py b/Lib/test/test_io/test_general.py index 604b56cea21fac..c450f957ab69b7 100644 --- a/Lib/test/test_io/test_general.py +++ b/Lib/test/test_io/test_general.py @@ -27,27 +27,12 @@ import_helper, is_apple, os_helper, threading_helper, warnings_helper, ) from test.support.os_helper import FakePath +from .utils import byteslike, CTestCase, PyTestCase import codecs import io # C implementation of io import _pyio as pyio # Python implementation of io -try: - import ctypes -except ImportError: - def byteslike(*pos, **kw): - return array.array("b", bytes(*pos, **kw)) -else: - def byteslike(*pos, **kw): - """Create a bytes-like object having no string or sequence methods""" - data = bytes(*pos, **kw) - obj = EmptyStruct() - ctypes.resize(obj, len(data)) - memoryview(obj).cast("B")[:] = data - return obj - class EmptyStruct(ctypes.Structure): - pass - def _default_chunk_size(): """Get the default TextIOWrapper chunk size""" @@ -63,298 +48,6 @@ class BadIndex: def __index__(self): 1/0 -class MockRawIOWithoutRead: - """A RawIO implementation without read(), so as to exercise the default - RawIO.read() which calls readinto().""" - - def __init__(self, read_stack=()): - self._read_stack = list(read_stack) - self._write_stack = [] - self._reads = 0 - self._extraneous_reads = 0 - - def write(self, b): - self._write_stack.append(bytes(b)) - return len(b) - - def writable(self): - return True - - def fileno(self): - return 42 - - def readable(self): - return True - - def seekable(self): - return True - - def seek(self, pos, whence): - return 0 # wrong but we gotta return something - - def tell(self): - return 0 # same comment as above - - def readinto(self, buf): - self._reads += 1 - max_len = len(buf) - try: - data = self._read_stack[0] - except IndexError: - self._extraneous_reads += 1 - return 0 - if data is None: - del self._read_stack[0] - return None - n = len(data) - if len(data) <= max_len: - del self._read_stack[0] - buf[:n] = data - return n - else: - buf[:] = data[:max_len] - self._read_stack[0] = data[max_len:] - return max_len - - def truncate(self, pos=None): - return pos - -class CMockRawIOWithoutRead(MockRawIOWithoutRead, io.RawIOBase): - pass - -class PyMockRawIOWithoutRead(MockRawIOWithoutRead, pyio.RawIOBase): - pass - - -class MockRawIO(MockRawIOWithoutRead): - - def read(self, n=None): - self._reads += 1 - try: - return self._read_stack.pop(0) - except: - self._extraneous_reads += 1 - return b"" - -class CMockRawIO(MockRawIO, io.RawIOBase): - pass - -class PyMockRawIO(MockRawIO, pyio.RawIOBase): - pass - - -class MisbehavedRawIO(MockRawIO): - def write(self, b): - return super().write(b) * 2 - - def read(self, n=None): - return super().read(n) * 2 - - def seek(self, pos, whence): - return -123 - - def tell(self): - return -456 - - def readinto(self, buf): - super().readinto(buf) - return len(buf) * 5 - -class CMisbehavedRawIO(MisbehavedRawIO, io.RawIOBase): - pass - -class PyMisbehavedRawIO(MisbehavedRawIO, pyio.RawIOBase): - pass - - -class SlowFlushRawIO(MockRawIO): - def __init__(self): - super().__init__() - self.in_flush = threading.Event() - - def flush(self): - self.in_flush.set() - time.sleep(0.25) - -class CSlowFlushRawIO(SlowFlushRawIO, io.RawIOBase): - pass - -class PySlowFlushRawIO(SlowFlushRawIO, pyio.RawIOBase): - pass - - -class CloseFailureIO(MockRawIO): - closed = 0 - - def close(self): - if not self.closed: - self.closed = 1 - raise OSError - -class CCloseFailureIO(CloseFailureIO, io.RawIOBase): - pass - -class PyCloseFailureIO(CloseFailureIO, pyio.RawIOBase): - pass - - -class MockFileIO: - - def __init__(self, data): - self.read_history = [] - super().__init__(data) - - def read(self, n=None): - res = super().read(n) - self.read_history.append(None if res is None else len(res)) - return res - - def readinto(self, b): - res = super().readinto(b) - self.read_history.append(res) - return res - -class CMockFileIO(MockFileIO, io.BytesIO): - pass - -class PyMockFileIO(MockFileIO, pyio.BytesIO): - pass - - -class MockUnseekableIO: - def seekable(self): - return False - - def seek(self, *args): - raise self.UnsupportedOperation("not seekable") - - def tell(self, *args): - raise self.UnsupportedOperation("not seekable") - - def truncate(self, *args): - raise self.UnsupportedOperation("not seekable") - -class CMockUnseekableIO(MockUnseekableIO, io.BytesIO): - UnsupportedOperation = io.UnsupportedOperation - -class PyMockUnseekableIO(MockUnseekableIO, pyio.BytesIO): - UnsupportedOperation = pyio.UnsupportedOperation - - -class MockCharPseudoDevFileIO(MockFileIO): - # GH-95782 - # ftruncate() does not work on these special files (and CPython then raises - # appropriate exceptions), so truncate() does not have to be accounted for - # here. - def __init__(self, data): - super().__init__(data) - - def seek(self, *args): - return 0 - - def tell(self, *args): - return 0 - -class CMockCharPseudoDevFileIO(MockCharPseudoDevFileIO, io.BytesIO): - pass - -class PyMockCharPseudoDevFileIO(MockCharPseudoDevFileIO, pyio.BytesIO): - pass - - -class MockNonBlockWriterIO: - - def __init__(self): - self._write_stack = [] - self._blocker_char = None - - def pop_written(self): - s = b"".join(self._write_stack) - self._write_stack[:] = [] - return s - - def block_on(self, char): - """Block when a given char is encountered.""" - self._blocker_char = char - - def readable(self): - return True - - def seekable(self): - return True - - def seek(self, pos, whence=0): - # naive implementation, enough for tests - return 0 - - def writable(self): - return True - - def write(self, b): - b = bytes(b) - n = -1 - if self._blocker_char: - try: - n = b.index(self._blocker_char) - except ValueError: - pass - else: - if n > 0: - # write data up to the first blocker - self._write_stack.append(b[:n]) - return n - else: - # cancel blocker and indicate would block - self._blocker_char = None - return None - self._write_stack.append(b) - return len(b) - -class CMockNonBlockWriterIO(MockNonBlockWriterIO, io.RawIOBase): - BlockingIOError = io.BlockingIOError - -class PyMockNonBlockWriterIO(MockNonBlockWriterIO, pyio.RawIOBase): - BlockingIOError = pyio.BlockingIOError - - -# Build classes which point to all the right mocks per io implementation -class CTestCase(unittest.TestCase): - io = io - is_C = True - - MockRawIO = CMockRawIO - MisbehavedRawIO = CMisbehavedRawIO - MockFileIO = CMockFileIO - CloseFailureIO = CCloseFailureIO - MockNonBlockWriterIO = CMockNonBlockWriterIO - MockUnseekableIO = CMockUnseekableIO - MockRawIOWithoutRead = CMockRawIOWithoutRead - SlowFlushRawIO = CSlowFlushRawIO - MockCharPseudoDevFileIO = CMockCharPseudoDevFileIO - - # Use the class as a proxy to the io module members. - def __getattr__(self, name): - return getattr(io, name) - - -class PyTestCase(unittest.TestCase): - io = pyio - is_C = False - - MockRawIO = PyMockRawIO - MisbehavedRawIO = PyMisbehavedRawIO - MockFileIO = PyMockFileIO - CloseFailureIO = PyCloseFailureIO - MockNonBlockWriterIO = PyMockNonBlockWriterIO - MockUnseekableIO = PyMockUnseekableIO - MockRawIOWithoutRead = PyMockRawIOWithoutRead - SlowFlushRawIO = PySlowFlushRawIO - MockCharPseudoDevFileIO = PyMockCharPseudoDevFileIO - - # Use the class as a proxy to the _pyio module members. - def __getattr__(self, name): - return getattr(pyio, name) - class IOTest(unittest.TestCase): diff --git a/Lib/test/test_io/utils.py b/Lib/test/test_io/utils.py new file mode 100644 index 00000000000000..3b1faec2140fbc --- /dev/null +++ b/Lib/test/test_io/utils.py @@ -0,0 +1,318 @@ +import array +import threading +import time +import unittest + +import io # C implementation of io +import _pyio as pyio # Python implementation of io + + +try: + import ctypes +except ImportError: + def byteslike(*pos, **kw): + return array.array("b", bytes(*pos, **kw)) +else: + class EmptyStruct(ctypes.Structure): + pass + + def byteslike(*pos, **kw): + """Create a bytes-like object having no string or sequence methods""" + data = bytes(*pos, **kw) + obj = EmptyStruct() + ctypes.resize(obj, len(data)) + memoryview(obj).cast("B")[:] = data + return obj + + +class MockRawIOWithoutRead: + """A RawIO implementation without read(), so as to exercise the default + RawIO.read() which calls readinto().""" + + def __init__(self, read_stack=()): + self._read_stack = list(read_stack) + self._write_stack = [] + self._reads = 0 + self._extraneous_reads = 0 + + def write(self, b): + self._write_stack.append(bytes(b)) + return len(b) + + def writable(self): + return True + + def fileno(self): + return 42 + + def readable(self): + return True + + def seekable(self): + return True + + def seek(self, pos, whence): + return 0 # wrong but we gotta return something + + def tell(self): + return 0 # same comment as above + + def readinto(self, buf): + self._reads += 1 + max_len = len(buf) + try: + data = self._read_stack[0] + except IndexError: + self._extraneous_reads += 1 + return 0 + if data is None: + del self._read_stack[0] + return None + n = len(data) + if len(data) <= max_len: + del self._read_stack[0] + buf[:n] = data + return n + else: + buf[:] = data[:max_len] + self._read_stack[0] = data[max_len:] + return max_len + + def truncate(self, pos=None): + return pos + +class CMockRawIOWithoutRead(MockRawIOWithoutRead, io.RawIOBase): + pass + +class PyMockRawIOWithoutRead(MockRawIOWithoutRead, pyio.RawIOBase): + pass + + +class MockRawIO(MockRawIOWithoutRead): + + def read(self, n=None): + self._reads += 1 + try: + return self._read_stack.pop(0) + except: + self._extraneous_reads += 1 + return b"" + +class CMockRawIO(MockRawIO, io.RawIOBase): + pass + +class PyMockRawIO(MockRawIO, pyio.RawIOBase): + pass + + +class MisbehavedRawIO(MockRawIO): + def write(self, b): + return super().write(b) * 2 + + def read(self, n=None): + return super().read(n) * 2 + + def seek(self, pos, whence): + return -123 + + def tell(self): + return -456 + + def readinto(self, buf): + super().readinto(buf) + return len(buf) * 5 + +class CMisbehavedRawIO(MisbehavedRawIO, io.RawIOBase): + pass + +class PyMisbehavedRawIO(MisbehavedRawIO, pyio.RawIOBase): + pass + + +class SlowFlushRawIO(MockRawIO): + def __init__(self): + super().__init__() + self.in_flush = threading.Event() + + def flush(self): + self.in_flush.set() + time.sleep(0.25) + +class CSlowFlushRawIO(SlowFlushRawIO, io.RawIOBase): + pass + +class PySlowFlushRawIO(SlowFlushRawIO, pyio.RawIOBase): + pass + + +class CloseFailureIO(MockRawIO): + closed = 0 + + def close(self): + if not self.closed: + self.closed = 1 + raise OSError + +class CCloseFailureIO(CloseFailureIO, io.RawIOBase): + pass + +class PyCloseFailureIO(CloseFailureIO, pyio.RawIOBase): + pass + + +class MockFileIO: + + def __init__(self, data): + self.read_history = [] + super().__init__(data) + + def read(self, n=None): + res = super().read(n) + self.read_history.append(None if res is None else len(res)) + return res + + def readinto(self, b): + res = super().readinto(b) + self.read_history.append(res) + return res + +class CMockFileIO(MockFileIO, io.BytesIO): + pass + +class PyMockFileIO(MockFileIO, pyio.BytesIO): + pass + + +class MockUnseekableIO: + def seekable(self): + return False + + def seek(self, *args): + raise self.UnsupportedOperation("not seekable") + + def tell(self, *args): + raise self.UnsupportedOperation("not seekable") + + def truncate(self, *args): + raise self.UnsupportedOperation("not seekable") + +class CMockUnseekableIO(MockUnseekableIO, io.BytesIO): + UnsupportedOperation = io.UnsupportedOperation + +class PyMockUnseekableIO(MockUnseekableIO, pyio.BytesIO): + UnsupportedOperation = pyio.UnsupportedOperation + + +class MockCharPseudoDevFileIO(MockFileIO): + # GH-95782 + # ftruncate() does not work on these special files (and CPython then raises + # appropriate exceptions), so truncate() does not have to be accounted for + # here. + def __init__(self, data): + super().__init__(data) + + def seek(self, *args): + return 0 + + def tell(self, *args): + return 0 + +class CMockCharPseudoDevFileIO(MockCharPseudoDevFileIO, io.BytesIO): + pass + +class PyMockCharPseudoDevFileIO(MockCharPseudoDevFileIO, pyio.BytesIO): + pass + + +class MockNonBlockWriterIO: + + def __init__(self): + self._write_stack = [] + self._blocker_char = None + + def pop_written(self): + s = b"".join(self._write_stack) + self._write_stack[:] = [] + return s + + def block_on(self, char): + """Block when a given char is encountered.""" + self._blocker_char = char + + def readable(self): + return True + + def seekable(self): + return True + + def seek(self, pos, whence=0): + # naive implementation, enough for tests + return 0 + + def writable(self): + return True + + def write(self, b): + b = bytes(b) + n = -1 + if self._blocker_char: + try: + n = b.index(self._blocker_char) + except ValueError: + pass + else: + if n > 0: + # write data up to the first blocker + self._write_stack.append(b[:n]) + return n + else: + # cancel blocker and indicate would block + self._blocker_char = None + return None + self._write_stack.append(b) + return len(b) + +class CMockNonBlockWriterIO(MockNonBlockWriterIO, io.RawIOBase): + BlockingIOError = io.BlockingIOError + +class PyMockNonBlockWriterIO(MockNonBlockWriterIO, pyio.RawIOBase): + BlockingIOError = pyio.BlockingIOError + + +# Build classes which point to all the right mocks per io implementation +class CTestCase(unittest.TestCase): + io = io + is_C = True + + MockRawIO = CMockRawIO + MisbehavedRawIO = CMisbehavedRawIO + MockFileIO = CMockFileIO + CloseFailureIO = CCloseFailureIO + MockNonBlockWriterIO = CMockNonBlockWriterIO + MockUnseekableIO = CMockUnseekableIO + MockRawIOWithoutRead = CMockRawIOWithoutRead + SlowFlushRawIO = CSlowFlushRawIO + MockCharPseudoDevFileIO = CMockCharPseudoDevFileIO + + # Use the class as a proxy to the io module members. + def __getattr__(self, name): + return getattr(io, name) + + +class PyTestCase(unittest.TestCase): + io = pyio + is_C = False + + MockRawIO = PyMockRawIO + MisbehavedRawIO = PyMisbehavedRawIO + MockFileIO = PyMockFileIO + CloseFailureIO = PyCloseFailureIO + MockNonBlockWriterIO = PyMockNonBlockWriterIO + MockUnseekableIO = PyMockUnseekableIO + MockRawIOWithoutRead = PyMockRawIOWithoutRead + SlowFlushRawIO = PySlowFlushRawIO + MockCharPseudoDevFileIO = PyMockCharPseudoDevFileIO + + # Use the class as a proxy to the _pyio module members. + def __getattr__(self, name): + return getattr(pyio, name) From 96dee64c73531325daa9e048b3c18212f5eadd98 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 11 Sep 2025 12:46:19 +0200 Subject: [PATCH 7/8] gh-138756: Fix memory leak in PyInitConfig_Free() (#138759) Clear also memory of PyConfig members. --- Python/initconfig.c | 60 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/Python/initconfig.c b/Python/initconfig.c index cc0db19d416058..951730c9ea3eb5 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -239,9 +239,11 @@ static const PyConfigSpec PYPRECONFIG_SPEC[] = { // Forward declarations -static PyObject* -config_get(const PyConfig *config, const PyConfigSpec *spec, - int use_sys); +static PyObject* config_get(const PyConfig *config, const PyConfigSpec *spec, + int use_sys); +static void initconfig_free_wstr(wchar_t *member); +static void initconfig_free_wstr_list(PyWideStringList *list); +static void initconfig_free_config(const PyConfig *config); /* --- Command line options --------------------------------------- */ @@ -3725,6 +3727,8 @@ PyInitConfig_Free(PyInitConfig *config) if (config == NULL) { return; } + + initconfig_free_config(&config->config); free(config->err_msg); free(config); } @@ -4093,13 +4097,51 @@ PyInitConfig_SetStr(PyInitConfig *config, const char *name, const char* value) } +static void +initconfig_free_wstr(wchar_t *member) +{ + if (member) { + free(member); + } +} + + +static void +initconfig_free_wstr_list(PyWideStringList *list) +{ + for (Py_ssize_t i = 0; i < list->length; i++) { + free(list->items[i]); + } + free(list->items); +} + + +static void +initconfig_free_config(const PyConfig *config) +{ + const PyConfigSpec *spec = PYCONFIG_SPEC; + for (; spec->name != NULL; spec++) { + void *member = config_get_spec_member(config, spec); + if (spec->type == PyConfig_MEMBER_WSTR + || spec->type == PyConfig_MEMBER_WSTR_OPT) + { + wchar_t *wstr = *(wchar_t **)member; + initconfig_free_wstr(wstr); + } + else if (spec->type == PyConfig_MEMBER_WSTR_LIST) { + initconfig_free_wstr_list(member); + } + } +} + + static int -_PyWideStringList_FromUTF8(PyInitConfig *config, PyWideStringList *list, - Py_ssize_t length, char * const *items) +initconfig_set_str_list(PyInitConfig *config, PyWideStringList *list, + Py_ssize_t length, char * const *items) { PyWideStringList wlist = _PyWideStringList_INIT; size_t size = sizeof(wchar_t*) * length; - wlist.items = (wchar_t **)PyMem_RawMalloc(size); + wlist.items = (wchar_t **)malloc(size); if (wlist.items == NULL) { config->status = _PyStatus_NO_MEMORY(); return -1; @@ -4108,14 +4150,14 @@ _PyWideStringList_FromUTF8(PyInitConfig *config, PyWideStringList *list, for (Py_ssize_t i = 0; i < length; i++) { wchar_t *arg = utf8_to_wstr(config, items[i]); if (arg == NULL) { - _PyWideStringList_Clear(&wlist); + initconfig_free_wstr_list(&wlist); return -1; } wlist.items[i] = arg; wlist.length++; } - _PyWideStringList_Clear(list); + initconfig_free_wstr_list(list); *list = wlist; return 0; } @@ -4136,7 +4178,7 @@ PyInitConfig_SetStrList(PyInitConfig *config, const char *name, return -1; } PyWideStringList *list = raw_member; - if (_PyWideStringList_FromUTF8(config, list, length, items) < 0) { + if (initconfig_set_str_list(config, list, length, items) < 0) { return -1; } From 283380a9417eebf1bf9d3a1e941cef94149712bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 11 Sep 2025 12:59:30 +0200 Subject: [PATCH 8/8] gh-116946: fully implement GC protocol for `_tkinter.Tk{app,tt}Object` (#138331) --- Modules/_tkinter.c | 65 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index f0882191d3c3e8..f094286063e9f5 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -589,10 +589,11 @@ Tkapp_New(const char *screenName, const char *className, int interactive, int wantobjects, int wantTk, int sync, const char *use) { + PyTypeObject *type = (PyTypeObject *)Tkapp_Type; TkappObject *v; char *argv0; - v = PyObject_New(TkappObject, (PyTypeObject *) Tkapp_Type); + v = (TkappObject *)type->tp_alloc(type, 0); if (v == NULL) return NULL; @@ -2745,9 +2746,10 @@ _tkinter_tktimertoken_deletetimerhandler_impl(TkttObject *self) static TkttObject * Tktt_New(PyObject *func) { + PyTypeObject *type = (PyTypeObject *)Tktt_Type; TkttObject *v; - v = PyObject_New(TkttObject, (PyTypeObject *) Tktt_Type); + v = (TkttObject *)type->tp_alloc(type, 0); if (v == NULL) return NULL; @@ -2758,19 +2760,33 @@ Tktt_New(PyObject *func) return (TkttObject*)Py_NewRef(v); } -static void -Tktt_Dealloc(PyObject *self) +static int +Tktt_Clear(PyObject *op) { - TkttObject *v = TkttObject_CAST(self); - PyObject *func = v->func; - PyObject *tp = (PyObject *) Py_TYPE(self); - - Py_XDECREF(func); + TkttObject *self = TkttObject_CAST(op); + Py_CLEAR(self->func); + return 0; +} - PyObject_Free(self); +static void +Tktt_Dealloc(PyObject *op) +{ + PyTypeObject *tp = Py_TYPE(op); + PyObject_GC_UnTrack(op); + (void)Tktt_Clear(op); + tp->tp_free(op); Py_DECREF(tp); } +static int +Tktt_Traverse(PyObject *op, visitproc visit, void *arg) +{ + TkttObject *self = TkttObject_CAST(op); + Py_VISIT(Py_TYPE(op)); + Py_VISIT(self->func); + return 0; +} + static PyObject * Tktt_Repr(PyObject *self) { @@ -3061,21 +3077,38 @@ _tkinter_tkapp_willdispatch_impl(TkappObject *self) /**** Tkapp Type Methods ****/ +static int +Tkapp_Clear(PyObject *op) +{ + TkappObject *self = TkappObject_CAST(op); + Py_CLEAR(self->trace); + return 0; +} + static void Tkapp_Dealloc(PyObject *op) { + PyTypeObject *tp = Py_TYPE(op); + PyObject_GC_UnTrack(op); TkappObject *self = TkappObject_CAST(op); - PyTypeObject *tp = Py_TYPE(self); /*CHECK_TCL_APPARTMENT;*/ ENTER_TCL Tcl_DeleteInterp(Tkapp_Interp(self)); LEAVE_TCL - Py_XDECREF(self->trace); - PyObject_Free(self); + (void)Tkapp_Clear(op); + tp->tp_free(self); Py_DECREF(tp); DisableEventHook(); } +static int +Tkapp_Traverse(PyObject *op, visitproc visit, void *arg) +{ + TkappObject *self = TkappObject_CAST(op); + Py_VISIT(Py_TYPE(op)); + Py_VISIT(self->trace); + return 0; +} /**** Tkinter Module ****/ @@ -3263,7 +3296,9 @@ static PyMethodDef Tktt_methods[] = }; static PyType_Slot Tktt_Type_slots[] = { + {Py_tp_clear, Tktt_Clear}, {Py_tp_dealloc, Tktt_Dealloc}, + {Py_tp_traverse, Tktt_Traverse}, {Py_tp_repr, Tktt_Repr}, {Py_tp_methods, Tktt_methods}, {0, 0} @@ -3276,6 +3311,7 @@ static PyType_Spec Tktt_Type_spec = { Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE + | Py_TPFLAGS_HAVE_GC ), .slots = Tktt_Type_slots, }; @@ -3322,7 +3358,9 @@ static PyMethodDef Tkapp_methods[] = }; static PyType_Slot Tkapp_Type_slots[] = { + {Py_tp_clear, Tkapp_Clear}, {Py_tp_dealloc, Tkapp_Dealloc}, + {Py_tp_traverse, Tkapp_Traverse}, {Py_tp_methods, Tkapp_methods}, {0, 0} }; @@ -3335,6 +3373,7 @@ static PyType_Spec Tkapp_Type_spec = { Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE + | Py_TPFLAGS_HAVE_GC ), .slots = Tkapp_Type_slots, };