Skip to content

Commit c525cb3

Browse files
[3.12] gh-101830: Fix Tcl_Obj to string conversion (GH-120884) (GH-120913)
Accessing the Tkinter object's string representation no longer converts the underlying Tcl object to a string on Windows. (cherry picked from commit f4ddaa3)
1 parent 32d43e5 commit c525cb3

File tree

3 files changed

+78
-22
lines changed

3 files changed

+78
-22
lines changed

Lib/test/test_tcl.py

+51-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test_eval_null_in_result(self):
5151

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

5656
def testEvalException(self):
5757
tcl = self.interp
@@ -61,6 +61,13 @@ def testEvalException2(self):
6161
tcl = self.interp
6262
self.assertRaises(TclError,tcl.eval,'this is wrong')
6363

64+
def test_eval_returns_tcl_obj(self):
65+
tcl = self.interp.tk
66+
tcl.eval(r'set a "\u20ac \ud83d\udcbb \0 \udcab"; regexp -about $a')
67+
a = tcl.eval('set a')
68+
expected = '\u20ac \U0001f4bb \0 \udced\udcb2\udcab'
69+
self.assertEqual(a, expected)
70+
6471
def testCall(self):
6572
tcl = self.interp
6673
tcl.call('set','a','1')
@@ -74,6 +81,18 @@ def testCallException2(self):
7481
tcl = self.interp
7582
self.assertRaises(TclError,tcl.call,'this','is','wrong')
7683

84+
def test_call_returns_tcl_obj(self):
85+
tcl = self.interp.tk
86+
tcl.eval(r'set a "\u20ac \ud83d\udcbb \0 \udcab"; regexp -about $a')
87+
a = tcl.call('set', 'a')
88+
expected = '\u20ac \U0001f4bb \0 \udced\udcb2\udcab'
89+
if self.wantobjects:
90+
self.assertEqual(str(a), expected)
91+
self.assertEqual(a.string, expected)
92+
self.assertEqual(a.typename, 'regexp')
93+
else:
94+
self.assertEqual(a, expected)
95+
7796
def testSetVar(self):
7897
tcl = self.interp
7998
tcl.setvar('a','1')
@@ -102,6 +121,18 @@ def testGetVarArrayException(self):
102121
tcl = self.interp
103122
self.assertRaises(TclError,tcl.getvar,'a(1)')
104123

124+
def test_getvar_returns_tcl_obj(self):
125+
tcl = self.interp.tk
126+
tcl.eval(r'set a "\u20ac \ud83d\udcbb \0 \udcab"; regexp -about $a')
127+
a = tcl.getvar('a')
128+
expected = '\u20ac \U0001f4bb \0 \udced\udcb2\udcab'
129+
if self.wantobjects:
130+
self.assertEqual(str(a), expected)
131+
self.assertEqual(a.string, expected)
132+
self.assertEqual(a.typename, 'regexp')
133+
else:
134+
self.assertEqual(a, expected)
135+
105136
def testUnsetVar(self):
106137
tcl = self.interp
107138
tcl.setvar('a',1)
@@ -538,6 +569,24 @@ def float_eq(actual, expected):
538569
check((1, (2,), (3, 4), '5 6', ()), '1 2 {3 4} {5 6} {}')
539570
check([1, [2,], [3, 4], '5 6', []], '1 2 {3 4} {5 6} {}')
540571

572+
def test_passing_tcl_obj(self):
573+
tcl = self.interp.tk
574+
a = None
575+
def testfunc(arg):
576+
nonlocal a
577+
a = arg
578+
self.interp.createcommand('testfunc', testfunc)
579+
self.addCleanup(self.interp.tk.deletecommand, 'testfunc')
580+
tcl.eval(r'set a "\u20ac \ud83d\udcbb \0 \udcab"; regexp -about $a')
581+
tcl.eval(r'testfunc $a')
582+
expected = '\u20ac \U0001f4bb \0 \udced\udcb2\udcab'
583+
if self.wantobjects >= 2:
584+
self.assertEqual(str(a), expected)
585+
self.assertEqual(a.string, expected)
586+
self.assertEqual(a.typename, 'regexp')
587+
else:
588+
self.assertEqual(a, expected)
589+
541590
def test_splitlist(self):
542591
splitlist = self.interp.tk.splitlist
543592
call = self.interp.tk.call
@@ -662,6 +711,7 @@ def test_new_tcl_obj(self):
662711
support.check_disallow_instantiation(self, _tkinter.TkttType)
663712
support.check_disallow_instantiation(self, _tkinter.TkappType)
664713

714+
665715
class BigmemTclTest(unittest.TestCase):
666716

667717
def setUp(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Accessing the :mod:`tkinter` object's string representation no longer converts
2+
the underlying Tcl object to a string on Windows.

Modules/_tkinter.c

+25-21
Original file line numberDiff line numberDiff line change
@@ -497,24 +497,28 @@ unicodeFromTclString(const char *s)
497497
}
498498

499499
static PyObject *
500-
unicodeFromTclObj(Tcl_Obj *value)
500+
unicodeFromTclObj(TkappObject *tkapp, Tcl_Obj *value)
501501
{
502502
Tcl_Size len;
503503
#if USE_TCL_UNICODE
504-
int byteorder = NATIVE_BYTEORDER;
505-
const Tcl_UniChar *u = Tcl_GetUnicodeFromObj(value, &len);
506-
if (sizeof(Tcl_UniChar) == 2)
507-
return PyUnicode_DecodeUTF16((const char *)u, len * 2,
508-
"surrogatepass", &byteorder);
509-
else if (sizeof(Tcl_UniChar) == 4)
510-
return PyUnicode_DecodeUTF32((const char *)u, len * 4,
511-
"surrogatepass", &byteorder);
512-
else
513-
Py_UNREACHABLE();
514-
#else
504+
if (value->typePtr != NULL && tkapp != NULL &&
505+
(value->typePtr == tkapp->StringType ||
506+
value->typePtr == tkapp->UTF32StringType))
507+
{
508+
int byteorder = NATIVE_BYTEORDER;
509+
const Tcl_UniChar *u = Tcl_GetUnicodeFromObj(value, &len);
510+
if (sizeof(Tcl_UniChar) == 2)
511+
return PyUnicode_DecodeUTF16((const char *)u, len * 2,
512+
"surrogatepass", &byteorder);
513+
else if (sizeof(Tcl_UniChar) == 4)
514+
return PyUnicode_DecodeUTF32((const char *)u, len * 4,
515+
"surrogatepass", &byteorder);
516+
else
517+
Py_UNREACHABLE();
518+
}
519+
#endif
515520
const char *s = Tcl_GetStringFromObj(value, &len);
516521
return unicodeFromTclStringAndSize(s, len);
517-
#endif
518522
}
519523

520524
/*[clinic input]
@@ -796,7 +800,7 @@ static PyObject *
796800
PyTclObject_string(PyTclObject *self, void *ignored)
797801
{
798802
if (!self->string) {
799-
self->string = unicodeFromTclObj(self->value);
803+
self->string = unicodeFromTclObj(NULL, self->value);
800804
if (!self->string)
801805
return NULL;
802806
}
@@ -810,7 +814,7 @@ PyTclObject_str(PyTclObject *self)
810814
return Py_NewRef(self->string);
811815
}
812816
/* XXX Could cache result if it is non-ASCII. */
813-
return unicodeFromTclObj(self->value);
817+
return unicodeFromTclObj(NULL, self->value);
814818
}
815819

816820
static PyObject *
@@ -1149,7 +1153,7 @@ FromObj(TkappObject *tkapp, Tcl_Obj *value)
11491153
Tcl_Interp *interp = Tkapp_Interp(tkapp);
11501154

11511155
if (value->typePtr == NULL) {
1152-
return unicodeFromTclObj(value);
1156+
return unicodeFromTclObj(tkapp, value);
11531157
}
11541158

11551159
if (value->typePtr == tkapp->BooleanType ||
@@ -1214,7 +1218,7 @@ FromObj(TkappObject *tkapp, Tcl_Obj *value)
12141218
if (value->typePtr == tkapp->StringType ||
12151219
value->typePtr == tkapp->UTF32StringType)
12161220
{
1217-
return unicodeFromTclObj(value);
1221+
return unicodeFromTclObj(tkapp, value);
12181222
}
12191223

12201224
if (tkapp->BignumType == NULL &&
@@ -1314,7 +1318,7 @@ Tkapp_CallArgs(PyObject *args, Tcl_Obj** objStore, Tcl_Size *pobjc)
13141318
static PyObject *
13151319
Tkapp_UnicodeResult(TkappObject *self)
13161320
{
1317-
return unicodeFromTclObj(Tcl_GetObjResult(self->interp));
1321+
return unicodeFromTclObj(self, Tcl_GetObjResult(self->interp));
13181322
}
13191323

13201324

@@ -1333,7 +1337,7 @@ Tkapp_ObjectResult(TkappObject *self)
13331337
res = FromObj(self, value);
13341338
Tcl_DecrRefCount(value);
13351339
} else {
1336-
res = unicodeFromTclObj(value);
1340+
res = unicodeFromTclObj(self, value);
13371341
}
13381342
return res;
13391343
}
@@ -1863,7 +1867,7 @@ GetVar(TkappObject *self, PyObject *args, int flags)
18631867
res = FromObj(self, tres);
18641868
}
18651869
else {
1866-
res = unicodeFromTclObj(tres);
1870+
res = unicodeFromTclObj(self, tres);
18671871
}
18681872
}
18691873
LEAVE_OVERLAP_TCL
@@ -2308,7 +2312,7 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp,
23082312
return PythonCmd_Error(interp);
23092313

23102314
for (i = 0; i < (objc - 1); i++) {
2311-
PyObject *s = unicodeFromTclObj(objv[i + 1]);
2315+
PyObject *s = unicodeFromTclObj((TkappObject *)data->self, objv[i + 1]);
23122316
if (!s) {
23132317
Py_DECREF(args);
23142318
return PythonCmd_Error(interp);

0 commit comments

Comments
 (0)