From 23f736a95880ec18fac7f5e67a789784005d2250 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 23 Jun 2024 16:34:14 +0300 Subject: [PATCH] [3.12] 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. (cherry picked from commit f4ddaa396715855ffbd94590f89ab7d55feeec07) Co-authored-by: Serhiy Storchaka --- Lib/test/test_tcl.py | 52 ++++++++++++++++++- ...-06-22-22-23-56.gh-issue-101830.1BAoxH.rst | 2 + Modules/_tkinter.c | 46 ++++++++-------- 3 files changed, 78 insertions(+), 22 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-06-22-22-23-56.gh-issue-101830.1BAoxH.rst diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index dbec5ea7e80433..55a1fa66914c36 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -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 @@ -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') @@ -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') @@ -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) @@ -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 @@ -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): diff --git a/Misc/NEWS.d/next/Library/2024-06-22-22-23-56.gh-issue-101830.1BAoxH.rst b/Misc/NEWS.d/next/Library/2024-06-22-22-23-56.gh-issue-101830.1BAoxH.rst new file mode 100644 index 00000000000000..46c18b040f30d7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-22-22-23-56.gh-issue-101830.1BAoxH.rst @@ -0,0 +1,2 @@ +Accessing the :mod:`tkinter` object's string representation no longer converts +the underlying Tcl object to a string on Windows. diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 37dc604b5b53a5..29a3d28c090c43 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -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] @@ -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; } @@ -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 * @@ -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 || @@ -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 && @@ -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)); } @@ -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; } @@ -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 @@ -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);