1919from _testcapi import PY_SSIZE_T_MIN , PY_SSIZE_T_MAX , SIZEOF_WCHAR_T
2020
2121
22+ MAX_UNICODE = _testinternalcapi ._Py_MAX_UNICODE
23+ # The first invalid character after MAX_UNICODE
24+ INVALID_CHAR = MAX_UNICODE + 1
25+ # Maximum invalid character which fits into 32-bit Py_UCS4
26+ MAX_INVALID_CHAR = 0xFFFF_FFFF
2227NULL = None
2328
2429class Str (str ):
@@ -41,6 +46,12 @@ def __str__(self):
4146SSTATE_INTERNED_IMMORTAL_STATIC = 3
4247
4348
49+ def assert_invalid_string (testcase , text ):
50+ # Check that a Unicode string contains invalid characters:
51+ # not in range [U+0000; U+10ffff]
52+ testcase .assertRaises (SystemError , list , text )
53+
54+
4455class CAPITest (unittest .TestCase ):
4556
4657 def _test_check (self , check , * , exact ):
@@ -73,14 +84,14 @@ def test_new(self):
7384 self .assertEqual (new (0 , maxchar ), '' )
7485 self .assertEqual (new (5 , maxchar ), chr (maxchar )* 5 )
7586 self .assertRaises (MemoryError , new , PY_SSIZE_T_MAX , maxchar )
76- self .assertEqual (new (0 , 0x110000 ), '' )
87+ self .assertEqual (new (0 , INVALID_CHAR ), '' )
7788 self .assertRaises (MemoryError , new , PY_SSIZE_T_MAX // 2 , 0x4f60 )
7889 self .assertRaises (MemoryError , new , PY_SSIZE_T_MAX // 2 + 1 , 0x4f60 )
7990 self .assertRaises (MemoryError , new , PY_SSIZE_T_MAX // 2 , 0x1f600 )
8091 self .assertRaises (MemoryError , new , PY_SSIZE_T_MAX // 2 + 1 , 0x1f600 )
8192 self .assertRaises (MemoryError , new , PY_SSIZE_T_MAX // 4 , 0x1f600 )
8293 self .assertRaises (MemoryError , new , PY_SSIZE_T_MAX // 4 + 1 , 0x1f600 )
83- self .assertRaises (SystemError , new , 5 , 0x110000 )
94+ self .assertRaises (SystemError , new , 5 , INVALID_CHAR )
8495 self .assertRaises (SystemError , new , - 1 , 0 )
8596 self .assertRaises (SystemError , new , PY_SSIZE_T_MIN , 0 )
8697
@@ -115,7 +126,7 @@ def test_fill(self):
115126 s = strings [0 ]
116127 self .assertRaises (IndexError , fill , s , - 1 , 0 , 0x78 )
117128 self .assertRaises (IndexError , fill , s , PY_SSIZE_T_MIN , 0 , 0x78 )
118- self .assertRaises (ValueError , fill , s , 0 , 0 , 0x110000 )
129+ self .assertRaises (ValueError , fill , s , 0 , 0 , INVALID_CHAR )
119130 self .assertRaises (SystemError , fill , b'abc' , 0 , 0 , 0x78 )
120131 self .assertRaises (SystemError , fill , [], 0 , 0 , 0x78 )
121132 # CRASHES fill(s, 0, NULL, 0, 0)
@@ -129,7 +140,7 @@ def _test_writechar(self, writechar, *, check):
129140 '\U0001f600 \U0001f601 \U0001f602 '
130141 ]
131142 # one character for every kind + out of range code
132- chars = [0x78 , 0xa9 , 0x20ac , 0x1f638 , 0x110000 ]
143+ chars = [0x78 , 0xa9 , 0x20ac , 0x1f638 , INVALID_CHAR ]
133144 for i , s in enumerate (strings ):
134145 for j , c in enumerate (chars ):
135146 if j <= i :
@@ -301,7 +312,22 @@ def test_fromkindanddata(self):
301312 self .assertRaises (ValueError , fromkindanddata , 1 , NULL , - 1 )
302313 self .assertRaises (ValueError , fromkindanddata , 1 , NULL , PY_SSIZE_T_MIN )
303314 # CRASHES fromkindanddata(1, NULL, 1)
304- # CRASHES fromkindanddata(4, b'\xff\xff\xff\xff')
315+
316+ # Test invalid UCS-4 string. Create an invalid string in release mode,
317+ # or raise SystemError in debug mode.
318+ for invalid_char in (INVALID_CHAR , MAX_INVALID_CHAR ):
319+ with self .subTest (invalid_char = invalid_char ):
320+ # Test single character
321+ ucs4_char = invalid_char .to_bytes (4 , byteorder = sys .byteorder )
322+ self .assertRaises (SystemError , fromkindanddata , 4 , ucs4_char )
323+
324+ # Test multiple characters
325+ s = 'valid' .encode (enc4 ) + ucs4_char
326+ if support .Py_DEBUG :
327+ self .assertRaises (SystemError , fromkindanddata , 4 , s )
328+ else :
329+ result = fromkindanddata (4 , s )
330+ assert_invalid_string (self , result )
305331
306332 def test_substring (self ):
307333 """Test PyUnicode_Substring()"""
@@ -446,7 +472,7 @@ def check_format(expected, format, *args):
446472 check_format ('\U0010ffff ' ,
447473 b'%c' , c_int (0x10ffff ))
448474 with self .assertRaises (OverflowError ):
449- PyUnicode_FromFormat (b'%c' , c_int (0x110000 ))
475+ PyUnicode_FromFormat (b'%c' , c_int (INVALID_CHAR ))
450476 # Issue #18183
451477 check_format ('\U00010000 \U00100000 ' ,
452478 b'%c%c' , c_int (0x10000 ), c_int (0x100000 ))
@@ -1015,7 +1041,7 @@ def test_fromordinal(self):
10151041 self .assertEqual (fromordinal (0x20ac ), '\u20ac ' )
10161042 self .assertEqual (fromordinal (0x1f600 ), '\U0001f600 ' )
10171043
1018- self .assertRaises (ValueError , fromordinal , 0x110000 )
1044+ self .assertRaises (ValueError , fromordinal , INVALID_CHAR )
10191045 self .assertRaises (ValueError , fromordinal , - 1 )
10201046
10211047 def test_asutf8 (self ):
@@ -1367,8 +1393,8 @@ def test_findchar(self):
13671393 self .assertEqual (unicode_findchar (str , ord (ch ), 0 , len (str ), - 1 ), i )
13681394
13691395 str = "!>_<!"
1370- self .assertEqual (unicode_findchar (str , 0x110000 , 0 , len (str ), 1 ), - 1 )
1371- self .assertEqual (unicode_findchar (str , 0x110000 , 0 , len (str ), - 1 ), - 1 )
1396+ self .assertEqual (unicode_findchar (str , INVALID_CHAR , 0 , len (str ), 1 ), - 1 )
1397+ self .assertEqual (unicode_findchar (str , INVALID_CHAR , 0 , len (str ), - 1 ), - 1 )
13721398 # start < end
13731399 self .assertEqual (unicode_findchar (str , ord ('!' ), 1 , len (str )+ 1 , 1 ), 4 )
13741400 self .assertEqual (unicode_findchar (str , ord ('!' ), 1 , PY_SSIZE_T_MAX , 1 ), 4 )
@@ -1757,7 +1783,7 @@ def test_max_char_value(self):
17571783 self .assertEqual (max_char_value ('ascii' ), 0x7f )
17581784 self .assertEqual (max_char_value ('latin1:\xe9 ' ), 0xff )
17591785 self .assertEqual (max_char_value ('bmp:\u20ac ' ), 0xffff )
1760- self .assertEqual (max_char_value (' \U0010ffff ' ), 0x10_ffff )
1786+ self .assertEqual (max_char_value (chr ( 0x10_0000 ) ), 0x10_ffff )
17611787
17621788 # CRASHES max_char_value(NULL)
17631789
@@ -1935,8 +1961,8 @@ def test_write_char(self):
19351961 writer .write_char (ord ('$' ))
19361962 writer .write_char (0x20ac )
19371963 writer .write_char (0x10_ffff )
1938- self .assertRaises (ValueError , writer .write_char , 0x11_0000 )
1939- self .assertRaises (ValueError , writer .write_char , 0xFFFF_FFFF )
1964+ self .assertRaises (ValueError , writer .write_char , INVALID_CHAR )
1965+ self .assertRaises (ValueError , writer .write_char , MAX_INVALID_CHAR )
19401966 self .assertEqual (writer .finish (),
19411967 "\0 $\u20AC \U0010FFFF " )
19421968
@@ -2106,15 +2132,37 @@ def test_ucs4(self):
21062132 writer .write_ucs4 ("pair\uD83D \uDC0D " .encode (encoding , 'surrogatepass' ))
21072133 writer .write_char (ord ("-" ))
21082134 writer .write_ucs4 ("null[\0 ]" .encode (encoding ), 7 )
2109- invalid = (b'\x00 \x00 \x11 \x00 ' if sys .byteorder == 'little' else
2110- b'\x00 \x11 \x00 \x00 ' )
2111- # CRASHES writer.write_ucs4("invalid".encode(encoding) + invalid)
21122135 writer .write_ucs4 (NULL , 0 )
21132136 # CRASHES writer.write_ucs4(NULL, 1)
21142137 self .assertEqual (writer .finish (),
21152138 "lone\udc80 -pair\ud83d \udc0d -null[\x00 ]" )
21162139
2117- # invalid size
2140+ # Invalid UCS-4 characters. Create an invalid string in release mode,
2141+ # or raise SystemError in debug mode.
2142+ writer = self .create_writer (0 )
2143+ for invalid_char in (INVALID_CHAR , MAX_INVALID_CHAR ):
2144+ with self .subTest (invalid_char = invalid_char ):
2145+ # Test single character
2146+ ucs4_char = invalid_char .to_bytes (4 , byteorder = sys .byteorder )
2147+ if support .Py_DEBUG :
2148+ self .assertRaises (SystemError , writer .write_ucs4 , ucs4_char )
2149+ else :
2150+ writer .write_ucs4 (ucs4_char )
2151+
2152+ # Test multiple characters
2153+ s = 'valid' .encode (encoding ) + ucs4_char
2154+ if support .Py_DEBUG :
2155+ self .assertRaises (SystemError , writer .write_ucs4 , s )
2156+ else :
2157+ writer .write_ucs4 (s )
2158+
2159+ if support .Py_DEBUG :
2160+ self .assertEqual (writer .finish (), '' )
2161+ else :
2162+ result = writer .finish ()
2163+ assert_invalid_string (self , result )
2164+
2165+ # Invalid size
21182166 writer = self .create_writer (0 )
21192167 with self .assertRaises (ValueError ):
21202168 writer .write_ucs4 ("text" .encode (encoding ), - 1 )
0 commit comments