From efffb29d09f1ad3b1713461a52d6631668baf28c Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 5 Nov 2026 12:30:26 +0300 Subject: [PATCH 1/6] gh-161388: [tkinter] Fix crash on raising `__str__` method passed to `setvar` --- Lib/test/test_tcl.py | 11 +++++++++++ .../2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst | 2 ++ Modules/_tkinter.c | 5 ++++- 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index 1d90ac10ad77ade..85912fd7ebbb733 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -596,6 +596,17 @@ def test_returning_deeply_nested(self): % support.exceeds_recursion_limit()) self.assertRaises(RecursionError, interp.getvar, 'x') + def test_setvar_evil(self): + # gh-161388 + class StrRaises: + def __str__(self): + raise RuntimeError("setvar evil") + + setvar = self.interp.tk.setvar + default = StrRaises() + with self.assertRaisesRegex(RuntimeError, "setvar evil"): + setvar("name1", "name2", default) + def test_user_command(self): result = None def testfunc(arg): diff --git a/Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst b/Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst new file mode 100644 index 000000000000000..762c3dfe7d90516 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst @@ -0,0 +1,2 @@ +Fixes a crash when passing objects with raising :meth:`~object.__str__` +method to :meth:`~tkinter.Misc.setvar`. diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 9a09687e55b9817..191ef3f1bb0b7c0 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -1235,7 +1235,7 @@ AsObjNested(PyObject *value, int depth) { PyObject *v = PyObject_Str(value); if (!v) - return 0; + return NULL; result = AsObjNested(v, depth); Py_DECREF(v); return result; @@ -2101,6 +2101,9 @@ SetVar(TkappObject *self, PyObject *args, int flags) /* XXX must hold tcl lock already??? */ newval = AsObj(newValue); + if (newval == NULL) { + return NULL; + } if (self->trace) { if (flags & TCL_GLOBAL_ONLY) { TRACE(self, ("((sssNO))", "uplevel", "#0", "set", From f177872730f04ed8d46e7b61172eadaeb01909f8 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 5 Nov 2026 21:44:32 +0300 Subject: [PATCH 2/6] Apply suggestions from code review --- Modules/_tkinter.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 191ef3f1bb0b7c0..4ff1d211d068851 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -1234,8 +1234,9 @@ AsObjNested(PyObject *value, int depth) { PyObject *v = PyObject_Str(value); - if (!v) + if (!v) { return NULL; + } result = AsObjNested(v, depth); Py_DECREF(v); return result; From 90eb61270e7bbd263ba5f89990323aa9f2a17bb0 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 5 Nov 2026 21:54:41 +0300 Subject: [PATCH 3/6] Update Modules/_tkinter.c --- Modules/_tkinter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 4ff1d211d068851..70a10cea038348d 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -1236,7 +1236,7 @@ AsObjNested(PyObject *value, int depth) PyObject *v = PyObject_Str(value); if (!v) { return NULL; - } + } result = AsObjNested(v, depth); Py_DECREF(v); return result; From 09ad219152c16120b679a8a8ffc77da291db5f5b Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 5 Nov 2026 23:41:13 +0300 Subject: [PATCH 4/6] Address review --- Lib/test/test_tcl.py | 24 ++++++++++++++++--- ...-11-05-12-29-04.gh-issue-161388.s7ECT8.rst | 2 -- 2 files changed, 21 insertions(+), 5 deletions(-) delete mode 100644 Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index 85912fd7ebbb733..f1953e4a2371173 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -602,10 +602,28 @@ class StrRaises: def __str__(self): raise RuntimeError("setvar evil") - setvar = self.interp.tk.setvar default = StrRaises() - with self.assertRaisesRegex(RuntimeError, "setvar evil"): - setvar("name1", "name2", default) + + for getvar, setvar, unsetvar in [ + ( + self.interp.tk.getvar, + self.interp.tk.setvar, + self.interp.tk.unsetvar, + ), + ( + self.interp.tk.globalgetvar, + self.interp.tk.globalsetvar, + self.interp.tk.globalunsetvar, + ) + ]: + with self.subTest(getvar=getvar, setvar=setvar, unsetvar=unsetvar): + with self.assertRaisesRegex(RuntimeError, "setvar evil"): + setvar("name1", "name2", default) + + self.assertRaises(TclError, getvar, "name1") + self.assertIsNone(setvar("name1", "name2")) + self.assertEqual(getvar("name1"), "name2") + self.assertIsNone(unsetvar("name1")) def test_user_command(self): result = None diff --git a/Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst b/Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst deleted file mode 100644 index 762c3dfe7d90516..000000000000000 --- a/Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fixes a crash when passing objects with raising :meth:`~object.__str__` -method to :meth:`~tkinter.Misc.setvar`. From 81c1b2e790cf74b2aa86f0fb220eb84cd9a25837 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 7 Nov 2026 11:44:55 +0300 Subject: [PATCH 5/6] Address review --- Lib/test/test_tcl.py | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index f1953e4a2371173..3f139f7bd9e4966 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -602,28 +602,24 @@ class StrRaises: def __str__(self): raise RuntimeError("setvar evil") - default = StrRaises() - - for getvar, setvar, unsetvar in [ - ( - self.interp.tk.getvar, - self.interp.tk.setvar, - self.interp.tk.unsetvar, - ), - ( - self.interp.tk.globalgetvar, - self.interp.tk.globalsetvar, - self.interp.tk.globalunsetvar, - ) - ]: - with self.subTest(getvar=getvar, setvar=setvar, unsetvar=unsetvar): - with self.assertRaisesRegex(RuntimeError, "setvar evil"): - setvar("name1", "name2", default) - - self.assertRaises(TclError, getvar, "name1") - self.assertIsNone(setvar("name1", "name2")) - self.assertEqual(getvar("name1"), "name2") - self.assertIsNone(unsetvar("name1")) + value = StrRaises() + tk = self.interp.tk + + with self.assertRaisesRegex(RuntimeError, "setvar evil"): + tk.setvar("name1", value) + with self.assertRaisesRegex(RuntimeError, "setvar evil"): + tk.setvar("name1", "name2", value) + + self.assertRaises(TclError, tk.getvar, "name1") + self.assertRaises(TclError, tk.getvar, "name1", "name2") + + with self.assertRaisesRegex(RuntimeError, "setvar evil"): + tk.globalsetvar("name1", value) + with self.assertRaisesRegex(RuntimeError, "setvar evil"): + tk.globalsetvar("name1", "name2", value) + + self.assertRaises(TclError, tk.globalgetvar, "name1") + self.assertRaises(TclError, tk.globalgetvar, "name1", "name2") def test_user_command(self): result = None From 954283c8609add8527622a037d6bba2215835736 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 7 Nov 2026 13:04:39 +0300 Subject: [PATCH 6/6] Address review --- Lib/test/test_tcl.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index 3f139f7bd9e4966..004271fde2eac83 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -606,20 +606,18 @@ def __str__(self): tk = self.interp.tk with self.assertRaisesRegex(RuntimeError, "setvar evil"): - tk.setvar("name1", value) + tk.setvar("local_name1", value) + self.assertRaises(TclError, tk.getvar, "local_name1") with self.assertRaisesRegex(RuntimeError, "setvar evil"): - tk.setvar("name1", "name2", value) - - self.assertRaises(TclError, tk.getvar, "name1") - self.assertRaises(TclError, tk.getvar, "name1", "name2") + tk.setvar("local_name2", "local_name3", value) + self.assertRaises(TclError, tk.getvar, "local_name2", "local_name3") with self.assertRaisesRegex(RuntimeError, "setvar evil"): - tk.globalsetvar("name1", value) + tk.globalsetvar("global_name1", value) + self.assertRaises(TclError, tk.globalgetvar, "global_name1") with self.assertRaisesRegex(RuntimeError, "setvar evil"): - tk.globalsetvar("name1", "name2", value) - - self.assertRaises(TclError, tk.globalgetvar, "name1") - self.assertRaises(TclError, tk.globalgetvar, "name1", "name2") + tk.globalsetvar("global_name2", "global_name3", value) + self.assertRaises(TclError, tk.globalgetvar, "global_name2", "global_name3") def test_user_command(self): result = None