Skip to content

Commit f4ddaa3

Browse files
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.
1 parent 18b6ca9 commit f4ddaa3

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)
@@ -549,6 +580,24 @@ def float_eq(actual, expected):
549580
'1 2 {3 4} {5 6} {}',
550581
(1, (2,), (3, 4), '5 6', ''))
551582

583+
def test_passing_tcl_obj(self):
584+
tcl = self.interp.tk
585+
a = None
586+
def testfunc(arg):
587+
nonlocal a
588+
a = arg
589+
self.interp.createcommand('testfunc', testfunc)
590+
self.addCleanup(self.interp.tk.deletecommand, 'testfunc')
591+
tcl.eval(r'set a "\u20ac \ud83d\udcbb \0 \udcab"; regexp -about $a')
592+
tcl.eval(r'testfunc $a')
593+
expected = '\u20ac \U0001f4bb \0 \udced\udcb2\udcab'
594+
if self.wantobjects >= 2:
595+
self.assertEqual(str(a), expected)
596+
self.assertEqual(a.string, expected)
597+
self.assertEqual(a.typename, 'regexp')
598+
else:
599+
self.assertEqual(a, expected)
600+
552601
def test_splitlist(self):
553602
splitlist = self.interp.tk.splitlist
554603
call = self.interp.tk.call
@@ -673,6 +722,7 @@ def test_new_tcl_obj(self):
673722
support.check_disallow_instantiation(self, _tkinter.TkttType)
674723
support.check_disallow_instantiation(self, _tkinter.TkappType)
675724

725+
676726
class BigmemTclTest(unittest.TestCase):
677727

678728
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
@@ -493,24 +493,28 @@ unicodeFromTclString(const char *s)
493493
}
494494

495495
static PyObject *
496-
unicodeFromTclObj(Tcl_Obj *value)
496+
unicodeFromTclObj(TkappObject *tkapp, Tcl_Obj *value)
497497
{
498498
Tcl_Size len;
499499
#if USE_TCL_UNICODE
500-
int byteorder = NATIVE_BYTEORDER;
501-
const Tcl_UniChar *u = Tcl_GetUnicodeFromObj(value, &len);
502-
if (sizeof(Tcl_UniChar) == 2)
503-
return PyUnicode_DecodeUTF16((const char *)u, len * 2,
504-
"surrogatepass", &byteorder);
505-
else if (sizeof(Tcl_UniChar) == 4)
506-
return PyUnicode_DecodeUTF32((const char *)u, len * 4,
507-
"surrogatepass", &byteorder);
508-
else
509-
Py_UNREACHABLE();
510-
#else
500+
if (value->typePtr != NULL && tkapp != NULL &&
501+
(value->typePtr == tkapp->StringType ||
502+
value->typePtr == tkapp->UTF32StringType))
503+
{
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+
}
515+
#endif
511516
const char *s = Tcl_GetStringFromObj(value, &len);
512517
return unicodeFromTclStringAndSize(s, len);
513-
#endif
514518
}
515519

516520
/*[clinic input]
@@ -793,7 +797,7 @@ PyTclObject_string(PyObject *_self, void *ignored)
793797
{
794798
PyTclObject *self = (PyTclObject *)_self;
795799
if (!self->string) {
796-
self->string = unicodeFromTclObj(self->value);
800+
self->string = unicodeFromTclObj(NULL, self->value);
797801
if (!self->string)
798802
return NULL;
799803
}
@@ -808,7 +812,7 @@ PyTclObject_str(PyObject *_self)
808812
return Py_NewRef(self->string);
809813
}
810814
/* XXX Could cache result if it is non-ASCII. */
811-
return unicodeFromTclObj(self->value);
815+
return unicodeFromTclObj(NULL, self->value);
812816
}
813817

814818
static PyObject *
@@ -1143,7 +1147,7 @@ FromObj(TkappObject *tkapp, Tcl_Obj *value)
11431147
Tcl_Interp *interp = Tkapp_Interp(tkapp);
11441148

11451149
if (value->typePtr == NULL) {
1146-
return unicodeFromTclObj(value);
1150+
return unicodeFromTclObj(tkapp, value);
11471151
}
11481152

11491153
if (value->typePtr == tkapp->BooleanType ||
@@ -1208,7 +1212,7 @@ FromObj(TkappObject *tkapp, Tcl_Obj *value)
12081212
if (value->typePtr == tkapp->StringType ||
12091213
value->typePtr == tkapp->UTF32StringType)
12101214
{
1211-
return unicodeFromTclObj(value);
1215+
return unicodeFromTclObj(tkapp, value);
12121216
}
12131217

12141218
if (tkapp->BignumType == NULL &&
@@ -1308,7 +1312,7 @@ Tkapp_CallArgs(PyObject *args, Tcl_Obj** objStore, Tcl_Size *pobjc)
13081312
static PyObject *
13091313
Tkapp_UnicodeResult(TkappObject *self)
13101314
{
1311-
return unicodeFromTclObj(Tcl_GetObjResult(self->interp));
1315+
return unicodeFromTclObj(self, Tcl_GetObjResult(self->interp));
13121316
}
13131317

13141318

@@ -1327,7 +1331,7 @@ Tkapp_ObjectResult(TkappObject *self)
13271331
res = FromObj(self, value);
13281332
Tcl_DecrRefCount(value);
13291333
} else {
1330-
res = unicodeFromTclObj(value);
1334+
res = unicodeFromTclObj(self, value);
13311335
}
13321336
return res;
13331337
}
@@ -1860,7 +1864,7 @@ GetVar(TkappObject *self, PyObject *args, int flags)
18601864
res = FromObj(self, tres);
18611865
}
18621866
else {
1863-
res = unicodeFromTclObj(tres);
1867+
res = unicodeFromTclObj(self, tres);
18641868
}
18651869
}
18661870
LEAVE_OVERLAP_TCL
@@ -2307,7 +2311,7 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp,
23072311

23082312
for (i = 0; i < (objc - 1); i++) {
23092313
PyObject *s = objargs ? FromObj(data->self, objv[i + 1])
2310-
: unicodeFromTclObj(objv[i + 1]);
2314+
: unicodeFromTclObj(data->self, objv[i + 1]);
23112315
if (!s) {
23122316
Py_DECREF(args);
23132317
return PythonCmd_Error(interp);

0 commit comments

Comments
 (0)