https://github.com/python/cpython/commit/9c0cfb141c236c902954841bb6cca407f86e1eeb
commit: 9c0cfb141c236c902954841bb6cca407f86e1eeb
branch: main
author: Gregory P. Smith <[email protected]>
committer: gpshead <[email protected]>
date: 2026-11-01T07:37:26Z
summary:
gh-160176: Fix crash in tkinter on deeply nested or recursive lists (GH-160177)
Converting a nested list or tuple to a Tcl object now raises ValueError
above 1000 levels, the same limit _flatten() uses. Converting a deeply
nested Tcl list back to Python now raises RecursionError.
ANT-2026-7UKC5QED
files:
A Misc/NEWS.d/next/Library/2026-10-22-00-29-50.gh-issue-160176.tkNst1.rst
M Lib/test/test_tcl.py
M Modules/_tkinter.c
diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py
index b0458e46c6bece..1d90ac10ad77ad 100644
--- a/Lib/test/test_tcl.py
+++ b/Lib/test/test_tcl.py
@@ -565,6 +565,37 @@ class List(list):
self.assertEqual(self.passValue(values := [Value(), "pad"]), expect)
self.assertEqual(self.passValue(values := List([Value(), "pad"])),
expect)
+ def test_passing_deeply_nested(self):
+ call = self.interp.tk.call
+ setvar = self.interp.tk.setvar
+ for wrap in (lambda x: [x]), (lambda x: (x,)):
+ value = 'a'
+ for i in range(1001):
+ value = wrap(value)
+ self.assertEqual(int(call('string', 'length', value)), 1)
+ value = wrap(value)
+ self.assertRaises(ValueError, call, 'set', '_', value)
+ self.assertRaises(ValueError, setvar, '_', value)
+ for i in range(support.exceeds_recursion_limit()):
+ value = wrap(value)
+ self.assertRaises(ValueError, call, 'set', '_', value)
+ self.assertRaises(ValueError, setvar, '_', value)
+ value = ['a', 1]
+ value.append(value)
+ self.assertRaises(ValueError, call, 'set', '_', value)
+ self.assertRaises(ValueError, setvar, '_', value)
+
+ @support.run_with_limited_c_stack()
+ def test_returning_deeply_nested(self):
+ if not self.wantobjects:
+ self.skipTest('requires wantobjects')
+ # run_with_limited_c_stack() runs the test in a new thread, and a
+ # Tcl interpreter can only be used in the thread that created it.
+ interp = Tcl()
+ interp.eval('set x a; for {set i 0} {$i < %d} {incr i} {set x [list
$x]}'
+ % support.exceeds_recursion_limit())
+ self.assertRaises(RecursionError, interp.getvar, 'x')
+
def test_user_command(self):
result = None
def testfunc(arg):
diff --git
a/Misc/NEWS.d/next/Library/2026-10-22-00-29-50.gh-issue-160176.tkNst1.rst
b/Misc/NEWS.d/next/Library/2026-10-22-00-29-50.gh-issue-160176.tkNst1.rst
new file mode 100644
index 00000000000000..5f11a8237ba81a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-22-00-29-50.gh-issue-160176.tkNst1.rst
@@ -0,0 +1,4 @@
+Fix a crash in :mod:`tkinter` when a deeply nested or recursive list or
+tuple is passed to Tcl, or a deeply nested Tcl list is converted to Python.
+These now raise :exc:`ValueError` (beyond 1000 levels) and
+:exc:`RecursionError` respectively.
diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c
index 07798dfaa42a0c..9a09687e55b981 100644
--- a/Modules/_tkinter.c
+++ b/Modules/_tkinter.c
@@ -1047,13 +1047,21 @@ asBignumObj(PyObject *value)
return result;
}
-static Tcl_Obj* AsObj(PyObject *value);
+static Tcl_Obj* AsObjNested(PyObject *value, int depth);
static Tcl_Obj*
-TupleAsObj(PyObject *value, int wrapped)
+TupleAsObj(PyObject *value, int wrapped, int depth)
{
Tcl_Obj *result = NULL;
Py_ssize_t size = PyTuple_GET_SIZE(value);
+ /* The same limit as in _flatten1(). It bounds not only the recursion
+ here, but also the recursion in Tcl when it generates the string
+ representation of the nested list, so a C stack check is not enough. */
+ if (depth > 1000) {
+ PyErr_SetString(PyExc_ValueError,
+ "nesting too deep in conversion to Tcl object");
+ return NULL;
+ }
if (size == 0) {
return Tcl_NewListObj(0, NULL);
}
@@ -1068,8 +1076,15 @@ TupleAsObj(PyObject *value, int wrapped)
return NULL;
}
for (Py_ssize_t i = 0; i < size; i++) {
- Tcl_Obj *item = AsObj(PyTuple_GET_ITEM(value, i));
+ Tcl_Obj *item = AsObjNested(PyTuple_GET_ITEM(value, i), depth + 1);
if (item == NULL) {
+ /* Free the already converted items. A new Tcl object has a
+ zero reference count, so it must be acquired before it can
+ be released. */
+ while (--i >= 0) {
+ Tcl_IncrRefCount(argv[i]);
+ Tcl_DecrRefCount(argv[i]);
+ }
goto exit;
}
argv[i] = item;
@@ -1082,7 +1097,7 @@ TupleAsObj(PyObject *value, int wrapped)
}
static Tcl_Obj*
-AsObj(PyObject *value)
+AsObjNested(PyObject *value, int depth)
{
Tcl_Obj *result;
@@ -1128,7 +1143,7 @@ AsObj(PyObject *value)
return Tcl_NewDoubleObj(PyFloat_AS_DOUBLE(value));
if (PyTuple_Check(value)) {
- return TupleAsObj(value, false);
+ return TupleAsObj(value, false, depth);
}
if (PyList_Check(value)) {
@@ -1136,7 +1151,7 @@ AsObj(PyObject *value)
if (value_as_tuple == NULL) {
return NULL;
}
- result = TupleAsObj(value_as_tuple, true);
+ result = TupleAsObj(value_as_tuple, true, depth);
Py_DECREF(value_as_tuple);
return result;
}
@@ -1221,12 +1236,18 @@ AsObj(PyObject *value)
PyObject *v = PyObject_Str(value);
if (!v)
return 0;
- result = AsObj(v);
+ result = AsObjNested(v, depth);
Py_DECREF(v);
return result;
}
}
+static Tcl_Obj*
+AsObj(PyObject *value)
+{
+ return AsObjNested(value, 0);
+}
+
static PyObject *
fromBoolean(TkappObject *tkapp, Tcl_Obj *value)
{
@@ -1384,19 +1405,25 @@ FromObj(TkappObject *tkapp, Tcl_Obj *value)
result = PyTuple_New(size);
if (!result)
return NULL;
+ if (Py_EnterRecursiveCall(" while converting a Tcl list")) {
+ Py_DECREF(result);
+ return NULL;
+ }
for (i = 0; i < size; i++) {
status = Tcl_ListObjIndex(interp, value, i, &tcl_elem);
if (status == TCL_ERROR) {
- Py_DECREF(result);
- return Tkinter_Error(tkapp);
+ Tkinter_Error(tkapp);
+ Py_CLEAR(result);
+ break;
}
elem = FromObj(tkapp, tcl_elem);
if (!elem) {
- Py_DECREF(result);
- return NULL;
+ Py_CLEAR(result);
+ break;
}
PyTuple_SET_ITEM(result, i, elem);
}
+ Py_LeaveRecursiveCall();
return result;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]