gh-101830: Fix Tcl_Obj to string conversion (GH-120884)

Accessing the Tkinter object's string representation no longer converts
the underlying Tcl object to a string on Windows.
This commit is contained in:
Serhiy Storchaka 2024-06-23 16:34:14 +03:00 committed by GitHub
parent 18b6ca9660
commit f4ddaa3967
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 78 additions and 22 deletions

View File

@ -51,7 +51,7 @@ class TclTest(unittest.TestCase):
def test_eval_surrogates_in_result(self): def test_eval_surrogates_in_result(self):
tcl = self.interp 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): def testEvalException(self):
tcl = self.interp tcl = self.interp
@ -61,6 +61,13 @@ class TclTest(unittest.TestCase):
tcl = self.interp tcl = self.interp
self.assertRaises(TclError,tcl.eval,'this is wrong') 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): def testCall(self):
tcl = self.interp tcl = self.interp
tcl.call('set','a','1') tcl.call('set','a','1')
@ -74,6 +81,18 @@ class TclTest(unittest.TestCase):
tcl = self.interp tcl = self.interp
self.assertRaises(TclError,tcl.call,'this','is','wrong') 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): def testSetVar(self):
tcl = self.interp tcl = self.interp
tcl.setvar('a','1') tcl.setvar('a','1')
@ -102,6 +121,18 @@ class TclTest(unittest.TestCase):
tcl = self.interp tcl = self.interp
self.assertRaises(TclError,tcl.getvar,'a(1)') 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): def testUnsetVar(self):
tcl = self.interp tcl = self.interp
tcl.setvar('a',1) tcl.setvar('a',1)
@ -549,6 +580,24 @@ class TclTest(unittest.TestCase):
'1 2 {3 4} {5 6} {}', '1 2 {3 4} {5 6} {}',
(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): def test_splitlist(self):
splitlist = self.interp.tk.splitlist splitlist = self.interp.tk.splitlist
call = self.interp.tk.call call = self.interp.tk.call
@ -673,6 +722,7 @@ class TclTest(unittest.TestCase):
support.check_disallow_instantiation(self, _tkinter.TkttType) support.check_disallow_instantiation(self, _tkinter.TkttType)
support.check_disallow_instantiation(self, _tkinter.TkappType) support.check_disallow_instantiation(self, _tkinter.TkappType)
class BigmemTclTest(unittest.TestCase): class BigmemTclTest(unittest.TestCase):
def setUp(self): def setUp(self):

View File

@ -0,0 +1,2 @@
Accessing the :mod:`tkinter` object's string representation no longer converts
the underlying Tcl object to a string on Windows.

View File

@ -493,24 +493,28 @@ unicodeFromTclString(const char *s)
} }
static PyObject * static PyObject *
unicodeFromTclObj(Tcl_Obj *value) unicodeFromTclObj(TkappObject *tkapp, Tcl_Obj *value)
{ {
Tcl_Size len; Tcl_Size len;
#if USE_TCL_UNICODE #if USE_TCL_UNICODE
int byteorder = NATIVE_BYTEORDER; if (value->typePtr != NULL && tkapp != NULL &&
const Tcl_UniChar *u = Tcl_GetUnicodeFromObj(value, &len); (value->typePtr == tkapp->StringType ||
if (sizeof(Tcl_UniChar) == 2) value->typePtr == tkapp->UTF32StringType))
return PyUnicode_DecodeUTF16((const char *)u, len * 2, {
"surrogatepass", &byteorder); int byteorder = NATIVE_BYTEORDER;
else if (sizeof(Tcl_UniChar) == 4) const Tcl_UniChar *u = Tcl_GetUnicodeFromObj(value, &len);
return PyUnicode_DecodeUTF32((const char *)u, len * 4, if (sizeof(Tcl_UniChar) == 2)
"surrogatepass", &byteorder); return PyUnicode_DecodeUTF16((const char *)u, len * 2,
else "surrogatepass", &byteorder);
Py_UNREACHABLE(); else if (sizeof(Tcl_UniChar) == 4)
#else return PyUnicode_DecodeUTF32((const char *)u, len * 4,
"surrogatepass", &byteorder);
else
Py_UNREACHABLE();
}
#endif
const char *s = Tcl_GetStringFromObj(value, &len); const char *s = Tcl_GetStringFromObj(value, &len);
return unicodeFromTclStringAndSize(s, len); return unicodeFromTclStringAndSize(s, len);
#endif
} }
/*[clinic input] /*[clinic input]
@ -793,7 +797,7 @@ PyTclObject_string(PyObject *_self, void *ignored)
{ {
PyTclObject *self = (PyTclObject *)_self; PyTclObject *self = (PyTclObject *)_self;
if (!self->string) { if (!self->string) {
self->string = unicodeFromTclObj(self->value); self->string = unicodeFromTclObj(NULL, self->value);
if (!self->string) if (!self->string)
return NULL; return NULL;
} }
@ -808,7 +812,7 @@ PyTclObject_str(PyObject *_self)
return Py_NewRef(self->string); return Py_NewRef(self->string);
} }
/* XXX Could cache result if it is non-ASCII. */ /* XXX Could cache result if it is non-ASCII. */
return unicodeFromTclObj(self->value); return unicodeFromTclObj(NULL, self->value);
} }
static PyObject * static PyObject *
@ -1143,7 +1147,7 @@ FromObj(TkappObject *tkapp, Tcl_Obj *value)
Tcl_Interp *interp = Tkapp_Interp(tkapp); Tcl_Interp *interp = Tkapp_Interp(tkapp);
if (value->typePtr == NULL) { if (value->typePtr == NULL) {
return unicodeFromTclObj(value); return unicodeFromTclObj(tkapp, value);
} }
if (value->typePtr == tkapp->BooleanType || if (value->typePtr == tkapp->BooleanType ||
@ -1208,7 +1212,7 @@ FromObj(TkappObject *tkapp, Tcl_Obj *value)
if (value->typePtr == tkapp->StringType || if (value->typePtr == tkapp->StringType ||
value->typePtr == tkapp->UTF32StringType) value->typePtr == tkapp->UTF32StringType)
{ {
return unicodeFromTclObj(value); return unicodeFromTclObj(tkapp, value);
} }
if (tkapp->BignumType == NULL && if (tkapp->BignumType == NULL &&
@ -1308,7 +1312,7 @@ finally:
static PyObject * static PyObject *
Tkapp_UnicodeResult(TkappObject *self) Tkapp_UnicodeResult(TkappObject *self)
{ {
return unicodeFromTclObj(Tcl_GetObjResult(self->interp)); return unicodeFromTclObj(self, Tcl_GetObjResult(self->interp));
} }
@ -1327,7 +1331,7 @@ Tkapp_ObjectResult(TkappObject *self)
res = FromObj(self, value); res = FromObj(self, value);
Tcl_DecrRefCount(value); Tcl_DecrRefCount(value);
} else { } else {
res = unicodeFromTclObj(value); res = unicodeFromTclObj(self, value);
} }
return res; return res;
} }
@ -1860,7 +1864,7 @@ GetVar(TkappObject *self, PyObject *args, int flags)
res = FromObj(self, tres); res = FromObj(self, tres);
} }
else { else {
res = unicodeFromTclObj(tres); res = unicodeFromTclObj(self, tres);
} }
} }
LEAVE_OVERLAP_TCL LEAVE_OVERLAP_TCL
@ -2307,7 +2311,7 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp,
for (i = 0; i < (objc - 1); i++) { for (i = 0; i < (objc - 1); i++) {
PyObject *s = objargs ? FromObj(data->self, objv[i + 1]) PyObject *s = objargs ? FromObj(data->self, objv[i + 1])
: unicodeFromTclObj(objv[i + 1]); : unicodeFromTclObj(data->self, objv[i + 1]);
if (!s) { if (!s) {
Py_DECREF(args); Py_DECREF(args);
return PythonCmd_Error(interp); return PythonCmd_Error(interp);