diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 14f741e802535f3..cfd3090e46b1abf 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -10,7 +10,8 @@ from test.support import os_helper from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import (AbstractTkTest, AbstractDefaultRootTest, - requires_tk, get_tk_patchlevel) + requires_tk, get_tk_patchlevel, + tcl_version) support.requires('gui') @@ -710,6 +711,30 @@ def test_iterable_protocol(self): widget in widget +class TkTest(AbstractTkTest, unittest.TestCase): + + def test_className(self): + # The className argument sets the class of the root window. Tk + # title-cases it: the first letter is upper-cased, the rest lower-cased. + cases = [ + ('fooBAR', 'Foobar'), + ('éÉ', 'Éé'), # small and capital E WITH ACUTE + ] + if tcl_version >= (9, 0): + # small and capital DESERET LETTER LONG I (a non-BMP script) + cases.append(('\U00010428\U00010400', '\U00010400\U00010428')) + for className, klass in cases: + root = tkinter.Tk(className=className) + try: + self.assertEqual(root.winfo_class(), klass) + finally: + root.destroy() + if tcl_version < (9, 0): + # gh-126219: title-casing a non-BMP first letter crashed Tcl 8.x; + # such a class name is now rejected. + self.assertRaises(ValueError, tkinter.Tk, className='\U00010428') + + class WinfoTest(AbstractTkTest, unittest.TestCase): def test_winfo_rgb(self): diff --git a/Misc/NEWS.d/next/Library/2026-06-23-13-27-14.gh-issue-126219.kOfv2g.rst b/Misc/NEWS.d/next/Library/2026-06-23-13-27-14.gh-issue-126219.kOfv2g.rst new file mode 100644 index 000000000000000..8cc8c64c719471d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-23-13-27-14.gh-issue-126219.kOfv2g.rst @@ -0,0 +1,3 @@ +Fixed a crash in :class:`tkinter.Tk` when *className* contains a non-BMP +character and tkinter is built against Tcl/Tk 8.x. Such a name is now +rejected with a :exc:`ValueError`. diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 1deff4ed44684cd..466e275c5cecf0e 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -3272,6 +3272,20 @@ _tkinter_create_impl(PyObject *module, const char *screenName, CHECK_STRING_LENGTH(className); CHECK_STRING_LENGTH(use); +#if TCL_MAJOR_VERSION < 9 + /* className is title-cased during Tk initialization. Tcl 8.x does not + * support non-BMP characters (encoded as 4-byte UTF-8 sequences) there + * and crashes in Tcl_UtfToTitle (see gh-126219). Reject them up front. */ + for (const unsigned char *p = (const unsigned char *)className; *p; p++) { + if (*p >= 0xF0) { + PyErr_SetString(PyExc_ValueError, + "className must not contain non-BMP characters " + "with this version of Tcl/Tk"); + return NULL; + } + } +#endif + return (PyObject *) Tkapp_New(screenName, className, interactive, wantobjects, wantTk, sync, use);