Skip to content

[3.12] gh-101830: Fix Tcl_Obj to string conversion (GH-120884) #120913

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
52 changes: 51 additions & 1 deletion Lib/test/test_tcl.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_eval_null_in_result(self):

def test_eval_surrogates_in_result(self):
tcl = self.interp
self.assertIn(tcl.eval(r'set a "<\ud83d\udcbb>"'), '<\U0001f4bb>')
self.assertEqual(tcl.eval(r'set a "<\ud83d\udcbb>"'), '<\U0001f4bb>')

def testEvalException(self):
tcl = self.interp
Expand All @@ -61,6 +61,13 @@ def testEvalException2(self):
tcl = self.interp
self.assertRaises(TclError,tcl.eval,'this is wrong')

def test_eval_returns_tcl_obj(self):
tcl = self.interp.tk
tcl.eval(r'set a "\u20ac \ud83d\udcbb \0 \udcab"; regexp -about $a')
a = tcl.eval('set a')
expected = '\u20ac \U0001f4bb \0 \udced\udcb2\udcab'
self.assertEqual(a, expected)

def testCall(self):
tcl = self.interp
tcl.call('set','a','1')
Expand All @@ -74,6 +81,18 @@ def testCallException2(self):
tcl = self.interp
self.assertRaises(TclError,tcl.call,'this','is','wrong')

def test_call_returns_tcl_obj(self):
tcl = self.interp.tk
tcl.eval(r'set a "\u20ac \ud83d\udcbb \0 \udcab"; regexp -about $a')
a = tcl.call('set', 'a')
expected = '\u20ac \U0001f4bb \0 \udced\udcb2\udcab'
if self.wantobjects:
self.assertEqual(str(a), expected)
self.assertEqual(a.string, expected)
self.assertEqual(a.typename, 'regexp')
else:
self.assertEqual(a, expected)

def testSetVar(self):
tcl = self.interp
tcl.setvar('a','1')
Expand Down Expand Up @@ -102,6 +121,18 @@ def testGetVarArrayException(self):
tcl = self.interp
self.assertRaises(TclError,tcl.getvar,'a(1)')

def test_getvar_returns_tcl_obj(self):
tcl = self.interp.tk
tcl.eval(r'set a "\u20ac \ud83d\udcbb \0 \udcab"; regexp -about $a')
a = tcl.getvar('a')
expected = '\u20ac \U0001f4bb \0 \udced\udcb2\udcab'
if self.wantobjects:
self.assertEqual(str(a), expected)
self.assertEqual(a.string, expected)
self.assertEqual(a.typename, 'regexp')
else:
self.assertEqual(a, expected)

def testUnsetVar(self):
tcl = self.interp
tcl.setvar('a',1)
Expand Down Expand Up @@ -538,6 +569,24 @@ def float_eq(actual, expected):
check((1, (2,), (3, 4), '5 6', ()), '1 2 {3 4} {5 6} {}')
check([1, [2,], [3, 4], '5 6', []], '1 2 {3 4} {5 6} {}')

def test_passing_tcl_obj(self):
tcl = self.interp.tk
a = None
def testfunc(arg):
nonlocal a
a = arg
self.interp.createcommand('testfunc', testfunc)
self.addCleanup(self.interp.tk.deletecommand, 'testfunc')
tcl.eval(r'set a "\u20ac \ud83d\udcbb \0 \udcab"; regexp -about $a')
tcl.eval(r'testfunc $a')
expected = '\u20ac \U0001f4bb \0 \udced\udcb2\udcab'
if self.wantobjects >= 2:
self.assertEqual(str(a), expected)
self.assertEqual(a.string, expected)
self.assertEqual(a.typename, 'regexp')
else:
self.assertEqual(a, expected)

def test_splitlist(self):
splitlist = self.interp.tk.splitlist
call = self.interp.tk.call
Expand Down Expand Up @@ -662,6 +711,7 @@ def test_new_tcl_obj(self):
support.check_disallow_instantiation(self, _tkinter.TkttType)
support.check_disallow_instantiation(self, _tkinter.TkappType)


class BigmemTclTest(unittest.TestCase):

def setUp(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Accessing the :mod:`tkinter` object's string representation no longer converts
the underlying Tcl object to a string on Windows.
46 changes: 25 additions & 21 deletions Modules/_tkinter.c
Original file line number Diff line number Diff line change
Expand Up @@ -497,24 +497,28 @@ unicodeFromTclString(const char *s)
}

static PyObject *
unicodeFromTclObj(Tcl_Obj *value)
unicodeFromTclObj(TkappObject *tkapp, Tcl_Obj *value)
{
Tcl_Size len;
#if USE_TCL_UNICODE
int byteorder = NATIVE_BYTEORDER;
const Tcl_UniChar *u = Tcl_GetUnicodeFromObj(value, &len);
if (sizeof(Tcl_UniChar) == 2)
return PyUnicode_DecodeUTF16((const char *)u, len * 2,
"surrogatepass", &byteorder);
else if (sizeof(Tcl_UniChar) == 4)
return PyUnicode_DecodeUTF32((const char *)u, len * 4,
"surrogatepass", &byteorder);
else
Py_UNREACHABLE();
#else
if (value->typePtr != NULL && tkapp != NULL &&
(value->typePtr == tkapp->StringType ||
value->typePtr == tkapp->UTF32StringType))
{
int byteorder = NATIVE_BYTEORDER;
const Tcl_UniChar *u = Tcl_GetUnicodeFromObj(value, &len);
if (sizeof(Tcl_UniChar) == 2)
return PyUnicode_DecodeUTF16((const char *)u, len * 2,
"surrogatepass", &byteorder);
else if (sizeof(Tcl_UniChar) == 4)
return PyUnicode_DecodeUTF32((const char *)u, len * 4,
"surrogatepass", &byteorder);
else
Py_UNREACHABLE();
}
#endif
const char *s = Tcl_GetStringFromObj(value, &len);
return unicodeFromTclStringAndSize(s, len);
#endif
}

/*[clinic input]
Expand Down Expand Up @@ -796,7 +800,7 @@ static PyObject *
PyTclObject_string(PyTclObject *self, void *ignored)
{
if (!self->string) {
self->string = unicodeFromTclObj(self->value);
self->string = unicodeFromTclObj(NULL, self->value);
if (!self->string)
return NULL;
}
Expand All @@ -810,7 +814,7 @@ PyTclObject_str(PyTclObject *self)
return Py_NewRef(self->string);
}
/* XXX Could cache result if it is non-ASCII. */
return unicodeFromTclObj(self->value);
return unicodeFromTclObj(NULL, self->value);
}

static PyObject *
Expand Down Expand Up @@ -1149,7 +1153,7 @@ FromObj(TkappObject *tkapp, Tcl_Obj *value)
Tcl_Interp *interp = Tkapp_Interp(tkapp);

if (value->typePtr == NULL) {
return unicodeFromTclObj(value);
return unicodeFromTclObj(tkapp, value);
}

if (value->typePtr == tkapp->BooleanType ||
Expand Down Expand Up @@ -1214,7 +1218,7 @@ FromObj(TkappObject *tkapp, Tcl_Obj *value)
if (value->typePtr == tkapp->StringType ||
value->typePtr == tkapp->UTF32StringType)
{
return unicodeFromTclObj(value);
return unicodeFromTclObj(tkapp, value);
}

if (tkapp->BignumType == NULL &&
Expand Down Expand Up @@ -1314,7 +1318,7 @@ Tkapp_CallArgs(PyObject *args, Tcl_Obj** objStore, Tcl_Size *pobjc)
static PyObject *
Tkapp_UnicodeResult(TkappObject *self)
{
return unicodeFromTclObj(Tcl_GetObjResult(self->interp));
return unicodeFromTclObj(self, Tcl_GetObjResult(self->interp));
}


Expand All @@ -1333,7 +1337,7 @@ Tkapp_ObjectResult(TkappObject *self)
res = FromObj(self, value);
Tcl_DecrRefCount(value);
} else {
res = unicodeFromTclObj(value);
res = unicodeFromTclObj(self, value);
}
return res;
}
Expand Down Expand Up @@ -1863,7 +1867,7 @@ GetVar(TkappObject *self, PyObject *args, int flags)
res = FromObj(self, tres);
}
else {
res = unicodeFromTclObj(tres);
res = unicodeFromTclObj(self, tres);
}
}
LEAVE_OVERLAP_TCL
Expand Down Expand Up @@ -2308,7 +2312,7 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp,
return PythonCmd_Error(interp);

for (i = 0; i < (objc - 1); i++) {
PyObject *s = unicodeFromTclObj(objv[i + 1]);
PyObject *s = unicodeFromTclObj((TkappObject *)data->self, objv[i + 1]);
if (!s) {
Py_DECREF(args);
return PythonCmd_Error(interp);
Expand Down
Loading