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

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

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
This commit is contained in:
Miss Islington (bot) 2026-01-02 11:12:57 +01:00 committed by GitHub
parent afa24d8324
commit b40bf5ffc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 67 additions and 21 deletions

View file

@ -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):

View file

@ -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.

View file

@ -946,6 +946,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)
{
@ -992,28 +1026,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;
if (PyTuple_Check(value)) {
return TupleAsObj(value, false);
}
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 (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;
}