2525# Maximum invalid character which fits into 32-bit Py_UCS4
2626MAX_INVALID_CHAR = 0xFFFF_FFFF
2727NULL = None
28+ USED_STR_ERROR = 'Cannot modify a string currently used'
2829
2930class Str (str ):
3031 pass
@@ -76,9 +77,27 @@ def test_checkexact(self):
7677 # Test PyUnicode_CheckExact()
7778 self ._test_check (_testlimitedcapi .unicode_checkexact , exact = True )
7879
80+ def assert_is_mutable (self , result , refcnt ):
81+ # Check that result is a "mutable" Unicode string
82+ self .assertEqual (refcnt , 1 )
83+ self .assertFalse (sys ._is_immortal (result ))
84+
85+ def assert_is_empty_singleton (self , result ):
86+ # Check that result is the empty string singleton
87+ self .assertEqual (result , '' )
88+ self .assertTrue (sys ._is_immortal (result ))
89+
7990 def test_new (self ):
8091 """Test PyUnicode_New()"""
81- new = _testcapi .unicode_new
92+ _unicode_new = _testcapi .unicode_new
93+
94+ def new (size , maxchar ):
95+ result = _unicode_new (size , maxchar )
96+ if size != 0 :
97+ self .assert_is_mutable (result , sys .getrefcount (result ))
98+ else :
99+ self .assert_is_empty_singleton (result )
100+ return result
82101
83102 for maxchar in 0 , 0x61 , 0xa1 , 0x4f60 , 0x1f600 , 0x10ffff :
84103 self .assertEqual (new (0 , maxchar ), '' )
@@ -123,6 +142,10 @@ def test_fill(self):
123142 self .assertEqual (fill (to , start , length , fill_char ),
124143 (expected , filled ))
125144
145+ # A string with 2 references cannot be modified
146+ with self .assertRaisesRegex (SystemError , USED_STR_ERROR ):
147+ fill ('abc' , 0 , 3 , ord ('x' ), incref = True )
148+
126149 s = strings [0 ]
127150 self .assertRaises (IndexError , fill , s , - 1 , 0 , 0x78 )
128151 self .assertRaises (IndexError , fill , s , PY_SSIZE_T_MIN , 0 , 0x78 )
@@ -162,7 +185,12 @@ def _test_writechar(self, writechar, *, check):
162185
163186 def test_writechar (self ):
164187 """Test PyUnicode_WriteChar()"""
165- self ._test_writechar (_testlimitedcapi .unicode_writechar , check = True )
188+ writechar = _testlimitedcapi .unicode_writechar
189+ self ._test_writechar (writechar , check = True )
190+
191+ # A string with 2 references cannot be modified
192+ with self .assertRaisesRegex (SystemError , USED_STR_ERROR ):
193+ writechar ('abc' , 1 , ord ('x' ), incref = True )
166194
167195 def test_write_macro (self ):
168196 """Test PyUnicode_WRITE()"""
@@ -187,18 +215,16 @@ def resize(s, length, new=True, compute_hash=False):
187215 self .assertFalse (is_new_obj )
188216 elif length == 0 :
189217 # Get the empty Unicode string
190- self .assertEqual (result , '' )
191- self .assertTrue (sys ._is_immortal (result ))
218+ self .assert_is_empty_singleton (result )
192219 self .assertTrue (is_new_obj )
193220 elif (not new ) or compute_hash :
194221 # Get a fresh copy
195- self .assertEqual ( refcnt , 1 )
222+ self .assert_is_mutable ( result , refcnt )
196223 self .assertTrue (is_new_obj )
197- self .assertFalse (sys ._is_immortal (result ))
198224 else :
199225 # In-size replace can return the same address, or not.
200226 # So 'is_new_obj' cannot be tested.
201- self .assertFalse ( sys . _is_immortal ( result ) )
227+ self .assert_is_mutable ( result , refcnt )
202228
203229 return result
204230
@@ -1793,6 +1819,11 @@ def test_copycharacters(self):
17931819 self .assertRaises (SystemError , unicode_copycharacters , s , 0 , s , 0 , PY_SSIZE_T_MIN )
17941820 self .assertRaises (SystemError , unicode_copycharacters , s , 0 , b'' , 0 , 0 )
17951821 self .assertRaises (SystemError , unicode_copycharacters , s , 0 , [], 0 , 0 )
1822+
1823+ # A string with 2 references cannot be modified
1824+ with self .assertRaisesRegex (SystemError , USED_STR_ERROR ):
1825+ unicode_copycharacters ('abc' , 0 , 'abc' , 0 , 1 , incref = True )
1826+
17961827 # CRASHES unicode_copycharacters(s, 0, NULL, 0, 0)
17971828 # TODO: Test PyUnicode_CopyCharacters() with non-unicode and
17981829 # non-modifiable unicode as "to".
0 commit comments