Skip to content

Commit 9712dc1

Browse files
authored
gh-143310: fix crash in Tcl object conversion with concurrent mutations (#143321)
1 parent 61fc72a commit 9712dc1

File tree

3 files changed

+68
-22
lines changed

3 files changed

+68
-22
lines changed

Lib/test/test_tcl.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ def setUp(self):
4040
self.interp = Tcl()
4141
self.wantobjects = self.interp.tk.wantobjects()
4242

43+
def passValue(self, value):
44+
return self.interp.call('set', '_', value)
45+
4346
def testEval(self):
4447
tcl = self.interp
4548
tcl.eval('set a 1')
@@ -490,8 +493,7 @@ def test_expr_bignum(self):
490493
self.assertIsInstance(result, str)
491494

492495
def test_passing_values(self):
493-
def passValue(value):
494-
return self.interp.call('set', '_', value)
496+
passValue = self.passValue
495497

496498
self.assertEqual(passValue(True), True if self.wantobjects else '1')
497499
self.assertEqual(passValue(False), False if self.wantobjects else '0')
@@ -537,6 +539,24 @@ def passValue(value):
537539
self.assertEqual(passValue(['a', ['b', 'c']]),
538540
('a', ('b', 'c')) if self.wantobjects else 'a {b c}')
539541

542+
def test_set_object_concurrent_mutation_in_sequence_conversion(self):
543+
# Prevent SIGSEV when the object to convert is concurrently mutated.
544+
# See: https://github.com/python/cpython/issues/143310.
545+
546+
string = "value"
547+
548+
class Value:
549+
def __str__(self):
550+
values.clear()
551+
return string
552+
553+
class List(list):
554+
pass
555+
556+
expect = (string, "pad") if self.wantobjects else f"{string} pad"
557+
self.assertEqual(self.passValue(values := [Value(), "pad"]), expect)
558+
self.assertEqual(self.passValue(values := List([Value(), "pad"])), expect)
559+
540560
def test_user_command(self):
541561
result = None
542562
def testfunc(arg):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:mod:`tkinter`: fix a crash when a Python :class:`list` is mutated during
2+
the conversion to a Tcl object (e.g., when setting a Tcl variable).
3+
Patch by Bénédikt Tran.

Modules/_tkinter.c

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,40 @@ asBignumObj(PyObject *value)
947947
return result;
948948
}
949949

950+
static Tcl_Obj* AsObj(PyObject *value);
951+
952+
static Tcl_Obj*
953+
TupleAsObj(PyObject *value, int wrapped)
954+
{
955+
Tcl_Obj *result = NULL;
956+
Py_ssize_t size = PyTuple_GET_SIZE(value);
957+
if (size == 0) {
958+
return Tcl_NewListObj(0, NULL);
959+
}
960+
if (!CHECK_SIZE(size, sizeof(Tcl_Obj *))) {
961+
PyErr_SetString(PyExc_OverflowError,
962+
wrapped ? "list is too long" : "tuple is too long");
963+
return NULL;
964+
}
965+
Tcl_Obj **argv = (Tcl_Obj **)PyMem_Malloc(((size_t)size) * sizeof(Tcl_Obj *));
966+
if (argv == NULL) {
967+
PyErr_NoMemory();
968+
return NULL;
969+
}
970+
for (Py_ssize_t i = 0; i < size; i++) {
971+
Tcl_Obj *item = AsObj(PyTuple_GET_ITEM(value, i));
972+
if (item == NULL) {
973+
goto exit;
974+
}
975+
argv[i] = item;
976+
}
977+
result = Tcl_NewListObj((int)size, argv);
978+
979+
exit:
980+
PyMem_Free(argv);
981+
return result;
982+
}
983+
950984
static Tcl_Obj*
951985
AsObj(PyObject *value)
952986
{
@@ -993,28 +1027,17 @@ AsObj(PyObject *value)
9931027
if (PyFloat_Check(value))
9941028
return Tcl_NewDoubleObj(PyFloat_AS_DOUBLE(value));
9951029

996-
if (PyTuple_Check(value) || PyList_Check(value)) {
997-
Tcl_Obj **argv;
998-
Py_ssize_t size, i;
999-
1000-
size = PySequence_Fast_GET_SIZE(value);
1001-
if (size == 0)
1002-
return Tcl_NewListObj(0, NULL);
1003-
if (!CHECK_SIZE(size, sizeof(Tcl_Obj *))) {
1004-
PyErr_SetString(PyExc_OverflowError,
1005-
PyTuple_Check(value) ? "tuple is too long" :
1006-
"list is too long");
1030+
if (PyTuple_Check(value)) {
1031+
return TupleAsObj(value, false);
1032+
}
1033+
1034+
if (PyList_Check(value)) {
1035+
PyObject *value_as_tuple = PyList_AsTuple(value);
1036+
if (value_as_tuple == NULL) {
10071037
return NULL;
10081038
}
1009-
argv = (Tcl_Obj **) PyMem_Malloc(((size_t)size) * sizeof(Tcl_Obj *));
1010-
if (!argv) {
1011-
PyErr_NoMemory();
1012-
return NULL;
1013-
}
1014-
for (i = 0; i < size; i++)
1015-
argv[i] = AsObj(PySequence_Fast_GET_ITEM(value,i));
1016-
result = Tcl_NewListObj((int)size, argv);
1017-
PyMem_Free(argv);
1039+
result = TupleAsObj(value_as_tuple, true);
1040+
Py_DECREF(value_as_tuple);
10181041
return result;
10191042
}
10201043

0 commit comments

Comments
 (0)