Skip to content

Commit 8beec4a

Browse files
[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 9712dc1) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent f6f3ddb commit 8beec4a

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
@@ -968,6 +968,40 @@ asBignumObj(PyObject *value)
968968
return result;
969969
}
970970

971+
static Tcl_Obj* AsObj(PyObject *value);
972+
973+
static Tcl_Obj*
974+
TupleAsObj(PyObject *value, int wrapped)
975+
{
976+
Tcl_Obj *result = NULL;
977+
Py_ssize_t size = PyTuple_GET_SIZE(value);
978+
if (size == 0) {
979+
return Tcl_NewListObj(0, NULL);
980+
}
981+
if (!CHECK_SIZE(size, sizeof(Tcl_Obj *))) {
982+
PyErr_SetString(PyExc_OverflowError,
983+
wrapped ? "list is too long" : "tuple is too long");
984+
return NULL;
985+
}
986+
Tcl_Obj **argv = (Tcl_Obj **)PyMem_Malloc(((size_t)size) * sizeof(Tcl_Obj *));
987+
if (argv == NULL) {
988+
PyErr_NoMemory();
989+
return NULL;
990+
}
991+
for (Py_ssize_t i = 0; i < size; i++) {
992+
Tcl_Obj *item = AsObj(PyTuple_GET_ITEM(value, i));
993+
if (item == NULL) {
994+
goto exit;
995+
}
996+
argv[i] = item;
997+
}
998+
result = Tcl_NewListObj((int)size, argv);
999+
1000+
exit:
1001+
PyMem_Free(argv);
1002+
return result;
1003+
}
1004+
9711005
static Tcl_Obj*
9721006
AsObj(PyObject *value)
9731007
{
@@ -1014,28 +1048,17 @@ AsObj(PyObject *value)
10141048
if (PyFloat_Check(value))
10151049
return Tcl_NewDoubleObj(PyFloat_AS_DOUBLE(value));
10161050

1017-
if (PyTuple_Check(value) || PyList_Check(value)) {
1018-
Tcl_Obj **argv;
1019-
Py_ssize_t size, i;
1020-
1021-
size = PySequence_Fast_GET_SIZE(value);
1022-
if (size == 0)
1023-
return Tcl_NewListObj(0, NULL);
1024-
if (!CHECK_SIZE(size, sizeof(Tcl_Obj *))) {
1025-
PyErr_SetString(PyExc_OverflowError,
1026-
PyTuple_Check(value) ? "tuple is too long" :
1027-
"list is too long");
1051+
if (PyTuple_Check(value)) {
1052+
return TupleAsObj(value, false);
1053+
}
1054+
1055+
if (PyList_Check(value)) {
1056+
PyObject *value_as_tuple = PyList_AsTuple(value);
1057+
if (value_as_tuple == NULL) {
10281058
return NULL;
10291059
}
1030-
argv = (Tcl_Obj **) PyMem_Malloc(((size_t)size) * sizeof(Tcl_Obj *));
1031-
if (!argv) {
1032-
PyErr_NoMemory();
1033-
return NULL;
1034-
}
1035-
for (i = 0; i < size; i++)
1036-
argv[i] = AsObj(PySequence_Fast_GET_ITEM(value,i));
1037-
result = Tcl_NewListObj((int)size, argv);
1038-
PyMem_Free(argv);
1060+
result = TupleAsObj(value_as_tuple, true);
1061+
Py_DECREF(value_as_tuple);
10391062
return result;
10401063
}
10411064

0 commit comments

Comments
 (0)