@@ -1054,6 +1054,52 @@ def test_cow_mutable(self):
10541054 memio = self .ioclass (ba )
10551055 self .assertEqual (sys .getrefcount (ba ), old_rc )
10561056
1057+ def test_cow_hashed (self ):
1058+ # A buffer which was exposed to Python code and hashed must not be
1059+ # modified in place, even if BytesIO holds the only reference to it:
1060+ # it would keep the cached hash of its old contents.
1061+ def write (memio ):
1062+ memio .seek (0 )
1063+ memio .write (b"efgh" )
1064+ def getbuffer (memio ):
1065+ with memio .getbuffer () as view :
1066+ view [:] = b"efgh"
1067+ def truncate (memio ):
1068+ memio .truncate (2 )
1069+ memio .seek (0 )
1070+ memio .write (b"efgh" )
1071+ def getvalue (memio ):
1072+ return memio .getvalue ()
1073+ def read (memio ):
1074+ memio .seek (0 )
1075+ return memio .read ()
1076+ def peek (memio ):
1077+ memio .seek (0 )
1078+ return memio .peek (4 )
1079+ for mutation in write , getbuffer , truncate :
1080+ for expose in getvalue , read , peek :
1081+ with self .subTest (mutation = mutation .__name__ ,
1082+ expose = expose .__name__ ):
1083+ memio = self .ioclass ()
1084+ memio .write (b"abcd" )
1085+ # Shrink the overallocated buffer to the exact size,
1086+ # so read() and peek() can return it without copying.
1087+ memio .getvalue ()
1088+ hash (expose (memio ))
1089+ mutation (memio )
1090+ self .assertEqual (memio .getvalue (), b"efgh" )
1091+ self .assertEqual (hash (memio .getvalue ()), hash (b"efgh" ))
1092+ self .assertIn (memio .getvalue (), {b"efgh" })
1093+
1094+ with self .subTest (mutation = mutation .__name__ , expose = "__init__" ):
1095+ initial = bytes (bytearray (b"abcd" ))
1096+ hash (initial )
1097+ memio = self .ioclass (initial )
1098+ del initial
1099+ mutation (memio )
1100+ self .assertEqual (memio .getvalue (), b"efgh" )
1101+ self .assertEqual (hash (memio .getvalue ()), hash (b"efgh" ))
1102+
10571103 def test_write_with_export (self ):
10581104 memio = self .ioclass (b"abcd" )
10591105 memio .seek (2 )
0 commit comments