Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions Lib/test/test_tcl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
63 changes: 43 additions & 20 deletions Modules/_tkinter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
}

Expand Down
Loading