From 714274cd68314df1fb29ebc426a9283c732dec99 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 13:43:21 +0200 Subject: [PATCH 1/2] fix crash when visiting a non-consumed timer handler --- Modules/_tkinter.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index f094286063e9f5..935ba67984ed09 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -2764,7 +2764,14 @@ static int Tktt_Clear(PyObject *op) { TkttObject *self = TkttObject_CAST(op); - Py_CLEAR(self->func); + if (self->token != NULL) { + Tcl_DeleteTimerHandler(self->token); + self->token = NULL; + } + if (self->func != NULL) { + Py_CLEAR(self->func); + Py_DECREF(op); /* See Tktt_New() */ + } return 0; } @@ -2783,6 +2790,9 @@ Tktt_Traverse(PyObject *op, visitproc visit, void *arg) { TkttObject *self = TkttObject_CAST(op); Py_VISIT(Py_TYPE(op)); + if (self->token != NULL) { + Py_VISIT(op); /* See Tktt_New() */ + } Py_VISIT(self->func); return 0; } From 3873fd5431c03c45c2c7cd33ee3617cae85c3c6b 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 14:21:26 +0200 Subject: [PATCH 2/2] add regression test --- Lib/test/test_tkinter/test_misc.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 0c76e07066f8a8..365d8b04395e3f 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -508,6 +508,23 @@ def test_embedded_null(self): widget.selection_range(0, 'end') self.assertEqual(widget.selection_get(), '\u20ac\0abc\x00def') + def test_settrace_gc(self): + # Regression test for https://github.com/python/cpython/issues/138791. + root = tkinter.Tk() + trace = lambda *_, **__: None + trace.evil = type(root.tk) + root.tk.settrace(trace) + root.destroy() + + def test_createtimerhandler_gc(self): + # Regression test for https://github.com/python/cpython/issues/138791. + root = tkinter.Tk() + func = lambda *_, **__: None + func.evil = type(root.tk.createtimerhandler(0, print)) + # Large timeout (in ms) so that the object is destroyed before. + root.tk.createtimerhandler(1234567, func) + root.destroy() + class WmTest(AbstractTkTest, unittest.TestCase):