https://github.com/python/cpython/commit/9712dc1d9eb03ffa96ed746d20bd43239d251ba7
commit: 9712dc1d9eb03ffa96ed746d20bd43239d251ba7
branch: main
author: Bénédikt Tran <[email protected]>
committer: picnixz <[email protected]>
date: 2026-01-02T10:51:05+01:00
summary:
gh-143310: fix crash in Tcl object conversion with concurrent mutations
(#143321)
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 8cea7b59fe730e..e6c0c1ff46ce52 100644
--- a/Modules/_tkinter.c
+++ b/Modules/_tkinter.c
@@ -947,6 +947,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)
{
@@ -993,28 +1027,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]