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')
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')
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):
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)
{
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;
}