Skip to content

Commit 76f989d

Browse files
authored
gh-98783: Fix crashes when str subclasses are used in _PyUnicode_Equal (#98806)
1 parent 3ac8c0a commit 76f989d

File tree

5 files changed

+36
-3
lines changed

5 files changed

+36
-3
lines changed

Include/cpython/unicodeobject.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -945,7 +945,7 @@ PyAPI_FUNC(PyObject*) _PyUnicode_FromId(_Py_Identifier*);
945945
and where the hash values are equal (i.e. a very probable match) */
946946
PyAPI_FUNC(int) _PyUnicode_EQ(PyObject *, PyObject *);
947947

948-
/* Equality check. Returns -1 on failure. */
948+
/* Equality check. */
949949
PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *, PyObject *);
950950

951951
PyAPI_FUNC(int) _PyUnicode_WideCharString_Converter(PyObject *, void *);

Lib/test/test_descr.py

+19
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,15 @@ class X(object):
13171317
with self.assertRaisesRegex(AttributeError, "'X' object has no attribute 'a'"):
13181318
X().a
13191319

1320+
# Test string subclass in `__slots__`, see gh-98783
1321+
class SubStr(str):
1322+
pass
1323+
class X(object):
1324+
__slots__ = (SubStr('x'),)
1325+
X().x = 1
1326+
with self.assertRaisesRegex(AttributeError, "'X' object has no attribute 'a'"):
1327+
X().a
1328+
13201329
def test_slots_special(self):
13211330
# Testing __dict__ and __weakref__ in __slots__...
13221331
class D(object):
@@ -3589,6 +3598,16 @@ def __repr__(self):
35893598
self.assertEqual(o.__str__(), '41')
35903599
self.assertEqual(o.__repr__(), 'A repr')
35913600

3601+
def test_repr_with_module_str_subclass(self):
3602+
# gh-98783
3603+
class StrSub(str):
3604+
pass
3605+
class Some:
3606+
pass
3607+
Some.__module__ = StrSub('example')
3608+
self.assertIsInstance(repr(Some), str) # should not crash
3609+
self.assertIsInstance(repr(Some()), str) # should not crash
3610+
35923611
def test_keyword_arguments(self):
35933612
# Testing keyword arguments to __init__, __call__...
35943613
def f(a): return a

Lib/test/test_long.py

+12
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,12 @@ def equivalent_python(n, length, byteorder, signed=False):
13341334
b'\xff\xff\xff\xff\xff')
13351335
self.assertRaises(OverflowError, (1).to_bytes, 0, 'big')
13361336

1337+
# gh-98783
1338+
class SubStr(str):
1339+
pass
1340+
self.assertEqual((0).to_bytes(1, SubStr('big')), b'\x00')
1341+
self.assertEqual((0).to_bytes(0, SubStr('little')), b'')
1342+
13371343
def test_from_bytes(self):
13381344
def check(tests, byteorder, signed=False):
13391345
def equivalent_python(byte_array, byteorder, signed=False):
@@ -1534,6 +1540,12 @@ def __bytes__(self):
15341540
self.assertRaises(TypeError, int.from_bytes, MissingBytes())
15351541
self.assertRaises(ZeroDivisionError, int.from_bytes, RaisingBytes())
15361542

1543+
# gh-98783
1544+
class SubStr(str):
1545+
pass
1546+
self.assertEqual(int.from_bytes(b'', SubStr('big')), 0)
1547+
self.assertEqual(int.from_bytes(b'\x00', SubStr('little')), 0)
1548+
15371549
@support.cpython_only
15381550
def test_from_bytes_small(self):
15391551
# bpo-46361
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix multiple crashes in debug mode when ``str`` subclasses
2+
are used instead of ``str`` itself.

Objects/unicodeobject.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -10444,8 +10444,8 @@ unicode_compare_eq(PyObject *str1, PyObject *str2)
1044410444
int
1044510445
_PyUnicode_Equal(PyObject *str1, PyObject *str2)
1044610446
{
10447-
assert(PyUnicode_CheckExact(str1));
10448-
assert(PyUnicode_CheckExact(str2));
10447+
assert(PyUnicode_Check(str1));
10448+
assert(PyUnicode_Check(str2));
1044910449
if (str1 == str2) {
1045010450
return 1;
1045110451
}

0 commit comments

Comments
 (0)