https://github.com/python/cpython/commit/8beec4a2e47ef01525486294399f7f363b0c446d
commit: 8beec4a2e47ef01525486294399f7f363b0c446d
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: picnixz <[email protected]>
date: 2026-01-02T10:17:13Z
summary:

[3.14] gh-143310: fix crash in Tcl object conversion with concurrent mutations 
(GH-143321) (#143343)

gh-143310: fix crash in Tcl object conversion with concurrent mutations 
(GH-143321)
(cherry picked from commit 9712dc1d9eb03ffa96ed746d20bd43239d251ba7)

Co-authored-by: Bénédikt Tran <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-01-01-11-21-57.gh-issue-143310.8rxtH3.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 d479f7d7515d9b..ef281f6d1fe53a 100644
--- a/Lib/test/test_tcl.py
+++ b/Lib/test/test_tcl.py
@@ -40,6 +40,9 @@ def setUp(self):
         self.interp = Tcl()
         self.wantobjects = self.interp.tk.wantobjects()
 
+    def passValue(self, value):
+        return self.interp.call('set', '_', value)
+
     def testEval(self):
         tcl = self.interp
         tcl.eval('set a 1')
@@ -490,8 +493,7 @@ def test_expr_bignum(self):
                 self.assertIsInstance(result, str)
 
     def test_passing_values(self):
-        def passValue(value):
-            return self.interp.call('set', '_', value)
+        passValue = self.passValue
 
         self.assertEqual(passValue(True), True if self.wantobjects else '1')
         self.assertEqual(passValue(False), False if self.wantobjects else '0')
@@ -537,6 +539,24 @@ def passValue(value):
         self.assertEqual(passValue(['a', ['b', 'c']]),
                          ('a', ('b', 'c')) if self.wantobjects else 'a {b c}')
 
+    def test_set_object_concurrent_mutation_in_sequence_conversion(self):
+        # Prevent SIGSEV when the object to convert is concurrently mutated.
+        # See: https://github.com/python/cpython/issues/143310.
+
+        string = "value"
+
+        class Value:
+            def __str__(self):
+                values.clear()
+                return string
+
+        class List(list):
+            pass
+
+        expect = (string, "pad") if self.wantobjects else f"{string} pad"
+        self.assertEqual(self.passValue(values := [Value(), "pad"]), expect)
+        self.assertEqual(self.passValue(values := List([Value(), "pad"])), 
expect)
+
     def test_user_command(self):
         result = None
         def testfunc(arg):
diff --git 
a/Misc/NEWS.d/next/Library/2026-01-01-11-21-57.gh-issue-143310.8rxtH3.rst 
b/Misc/NEWS.d/next/Library/2026-01-01-11-21-57.gh-issue-143310.8rxtH3.rst
new file mode 100644
index 00000000000000..32d4862179d587
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-01-01-11-21-57.gh-issue-143310.8rxtH3.rst
@@ -0,0 +1,3 @@
+:mod:`tkinter`: fix a crash when a Python :class:`list` is mutated during
+the conversion to a Tcl object (e.g., when setting a Tcl variable).
+Patch by Bénédikt Tran.
diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c
index 08fb96169da6c3..2216de509e939c 100644
--- a/Modules/_tkinter.c
+++ b/Modules/_tkinter.c
@@ -968,6 +968,40 @@ asBignumObj(PyObject *value)
     return result;
 }
 
+static Tcl_Obj* AsObj(PyObject *value);
+
+static Tcl_Obj*
+TupleAsObj(PyObject *value, int wrapped)
+{
+    Tcl_Obj *result = NULL;
+    Py_ssize_t size = PyTuple_GET_SIZE(value);
+    if (size == 0) {
+        return Tcl_NewListObj(0, NULL);
+    }
+    if (!CHECK_SIZE(size, sizeof(Tcl_Obj *))) {
+        PyErr_SetString(PyExc_OverflowError,
+                        wrapped ? "list is too long" : "tuple is too long");
+        return NULL;
+    }
+    Tcl_Obj **argv = (Tcl_Obj **)PyMem_Malloc(((size_t)size) * sizeof(Tcl_Obj 
*));
+    if (argv == NULL) {
+      PyErr_NoMemory();
+      return NULL;
+    }
+    for (Py_ssize_t i = 0; i < size; i++) {
+        Tcl_Obj *item = AsObj(PyTuple_GET_ITEM(value, i));
+        if (item == NULL) {
+            goto exit;
+        }
+        argv[i] = item;
+    }
+    result = Tcl_NewListObj((int)size, argv);
+
+exit:
+    PyMem_Free(argv);
+    return result;
+}
+
 static Tcl_Obj*
 AsObj(PyObject *value)
 {
@@ -1014,28 +1048,17 @@ AsObj(PyObject *value)
     if (PyFloat_Check(value))
         return Tcl_NewDoubleObj(PyFloat_AS_DOUBLE(value));
 
-    if (PyTuple_Check(value) || PyList_Check(value)) {
-        Tcl_Obj **argv;
-        Py_ssize_t size, i;
-
-        size = PySequence_Fast_GET_SIZE(value);
-        if (size == 0)
-            return Tcl_NewListObj(0, NULL);
-        if (!CHECK_SIZE(size, sizeof(Tcl_Obj *))) {
-            PyErr_SetString(PyExc_OverflowError,
-                            PyTuple_Check(value) ? "tuple is too long" :
-                                                   "list is too long");
+    if (PyTuple_Check(value)) {
+        return TupleAsObj(value, false);
+    }
+
+    if (PyList_Check(value)) {
+        PyObject *value_as_tuple = PyList_AsTuple(value);
+        if (value_as_tuple == NULL) {
             return NULL;
         }
-        argv = (Tcl_Obj **) PyMem_Malloc(((size_t)size) * sizeof(Tcl_Obj *));
-        if (!argv) {
-          PyErr_NoMemory();
-          return NULL;
-        }
-        for (i = 0; i < size; i++)
-          argv[i] = AsObj(PySequence_Fast_GET_ITEM(value,i));
-        result = Tcl_NewListObj((int)size, argv);
-        PyMem_Free(argv);
+        result = TupleAsObj(value_as_tuple, true);
+        Py_DECREF(value_as_tuple);
         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]

Reply via email to