]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-101830: Fix Tcl_Obj to string conversion (GH-120884)
authorSerhiy Storchaka <storchaka@gmail.com>
Sun, 23 Jun 2024 13:34:14 +0000 (16:34 +0300)
committerGitHub <noreply@github.com>
Sun, 23 Jun 2024 13:34:14 +0000 (16:34 +0300)
Accessing the Tkinter object's string representation no longer converts
the underlying Tcl object to a string on Windows.

Lib/test/test_tcl.py
Misc/NEWS.d/next/Library/2024-06-22-22-23-56.gh-issue-101830.1BAoxH.rst [new file with mode: 0644]
Modules/_tkinter.c

index e6cf2c7ace0617ad009f9d4547fc801bdb674d58..443787d721d5fbf1b10942cdf2a3889b862c904e 100644 (file)
@@ -51,7 +51,7 @@ class TclTest(unittest.TestCase):
 
     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 @@ class TclTest(unittest.TestCase):
         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 @@ class TclTest(unittest.TestCase):
         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 @@ class TclTest(unittest.TestCase):
         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)
@@ -549,6 +580,24 @@ class TclTest(unittest.TestCase):
               '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
@@ -673,6 +722,7 @@ class TclTest(unittest.TestCase):
         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 (file)
index 0000000..46c18b0
--- /dev/null
@@ -0,0 +1,2 @@
+Accessing the :mod:`tkinter` object's string representation no longer converts
+the underlying Tcl object to a string on Windows.
index a34646aecb3ec804a4af7c3a7b5895e10fbe9a5c..8a5347be7de6ad263dd667cc4a725173d0997467 100644 (file)
@@ -493,24 +493,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]
@@ -793,7 +797,7 @@ PyTclObject_string(PyObject *_self, void *ignored)
 {
     PyTclObject *self = (PyTclObject *)_self;
     if (!self->string) {
-        self->string = unicodeFromTclObj(self->value);
+        self->string = unicodeFromTclObj(NULL, self->value);
         if (!self->string)
             return NULL;
     }
@@ -808,7 +812,7 @@ PyTclObject_str(PyObject *_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 *
@@ -1143,7 +1147,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 ||
@@ -1208,7 +1212,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 &&
@@ -1308,7 +1312,7 @@ finally:
 static PyObject *
 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);
         Tcl_DecrRefCount(value);
     } else {
-        res = unicodeFromTclObj(value);
+        res = unicodeFromTclObj(self, value);
     }
     return res;
 }
@@ -1860,7 +1864,7 @@ GetVar(TkappObject *self, PyObject *args, int flags)
             res = FromObj(self, tres);
         }
         else {
-            res = unicodeFromTclObj(tres);
+            res = unicodeFromTclObj(self, tres);
         }
     }
     LEAVE_OVERLAP_TCL
@@ -2307,7 +2311,7 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp,
 
     for (i = 0; i < (objc - 1); i++) {
         PyObject *s = objargs ? FromObj(data->self, objv[i + 1])
-                              : unicodeFromTclObj(objv[i + 1]);
+                              : unicodeFromTclObj(data->self, objv[i + 1]);
         if (!s) {
             Py_DECREF(args);
             return PythonCmd_Error(interp);