@@ -980,6 +980,9 @@ class TestBinaryFormatValidation(BinaryFormatTestBase):
980980 HDR_OFF_STR_TABLE = 36
981981 HDR_OFF_FRAME_TABLE = 44
982982 FILE_HEADER_PLACEHOLDER_SIZE = 64
983+ FILE_FOOTER_SIZE = 32
984+ FTR_OFF_STRINGS = 0
985+ FTR_OFF_FRAMES = 4
983986
984987 def test_replay_rejects_more_threads_than_declared (self ):
985988 """Replay rejects files with more unique threads than the header declares."""
@@ -1041,6 +1044,85 @@ def test_replay_rejects_trailing_partial_sample_header(self):
10411044 reader .replay_samples (RawCollector ())
10421045 self .assertEqual (str (cm .exception ), "Truncated sample data: 1 trailing bytes" )
10431046
1047+ # Minimum on-disk size of one table entry (see binary_io.h).
1048+ MIN_STRING_ENTRY_SIZE = 1
1049+ MIN_FRAME_ENTRY_SIZE = 7
1050+
1051+ def _read_offset (self , filename , hdr_off ):
1052+ with open (filename , "rb" ) as raw :
1053+ raw .seek (hdr_off )
1054+ return struct .unpack ("=Q" , raw .read (8 ))[0 ]
1055+
1056+ def _patch_footer_count (self , filename , ftr_off , value ):
1057+ size = os .path .getsize (filename )
1058+ with open (filename , "r+b" ) as raw :
1059+ raw .seek (size - self .FILE_FOOTER_SIZE + ftr_off )
1060+ raw .write (struct .pack ("=I" , value ))
1061+
1062+ def test_open_rejects_string_count_larger_than_file (self ):
1063+ """Open rejects a footer string count larger than the file."""
1064+ samples = [[make_interpreter (0 , [
1065+ make_thread (1 , [make_frame ("s.py" , 10 , "s" )])
1066+ ])]]
1067+ filename = self .create_binary_file (samples , compression = "none" )
1068+ size = os .path .getsize (filename )
1069+ str_off = self ._read_offset (filename , self .HDR_OFF_STR_TABLE )
1070+ max_strings = (size - str_off ) // self .MIN_STRING_ENTRY_SIZE
1071+ self ._patch_footer_count (filename , self .FTR_OFF_STRINGS , 0xFFFFFFFF )
1072+
1073+ with self .assertRaises (ValueError ) as cm :
1074+ with BinaryReader (filename ):
1075+ pass
1076+ self .assertEqual (
1077+ str (cm .exception ),
1078+ f"Invalid string count 4294967295 exceeds maximum "
1079+ f"possible { max_strings } " ,
1080+ )
1081+
1082+ def test_open_rejects_frame_count_larger_than_file (self ):
1083+ """Open rejects a footer frame count larger than the file."""
1084+ samples = [[make_interpreter (0 , [
1085+ make_thread (1 , [make_frame ("f.py" , 10 , "f" )])
1086+ ])]]
1087+ filename = self .create_binary_file (samples , compression = "none" )
1088+ size = os .path .getsize (filename )
1089+ frame_off = self ._read_offset (filename , self .HDR_OFF_FRAME_TABLE )
1090+ max_frames = (size - frame_off ) // self .MIN_FRAME_ENTRY_SIZE
1091+ self ._patch_footer_count (filename , self .FTR_OFF_FRAMES , 0xFFFFFFFF )
1092+
1093+ with self .assertRaises (ValueError ) as cm :
1094+ with BinaryReader (filename ):
1095+ pass
1096+ self .assertEqual (
1097+ str (cm .exception ),
1098+ f"Invalid frame count 4294967295 exceeds maximum "
1099+ f"possible { max_frames } " ,
1100+ )
1101+
1102+ def test_open_accepts_frame_count_at_capacity_boundary (self ):
1103+ """A frame count at the file-size cap opens; one more is rejected."""
1104+ samples = [[make_interpreter (0 , [
1105+ make_thread (1 , [make_frame ("f.py" , 10 , "f" )])
1106+ ])]]
1107+ filename = self .create_binary_file (samples , compression = "none" )
1108+ size = os .path .getsize (filename )
1109+ frame_off = self ._read_offset (filename , self .HDR_OFF_FRAME_TABLE )
1110+ max_frames = (size - frame_off ) // self .MIN_FRAME_ENTRY_SIZE
1111+
1112+ self ._patch_footer_count (filename , self .FTR_OFF_FRAMES , max_frames )
1113+ with BinaryReader (filename ):
1114+ pass
1115+
1116+ self ._patch_footer_count (filename , self .FTR_OFF_FRAMES , max_frames + 1 )
1117+ with self .assertRaises (ValueError ) as cm :
1118+ with BinaryReader (filename ):
1119+ pass
1120+ self .assertEqual (
1121+ str (cm .exception ),
1122+ f"Invalid frame count { max_frames + 1 } exceeds maximum "
1123+ f"possible { max_frames } " ,
1124+ )
1125+
10441126
10451127class TestBinaryEncodings (BinaryFormatTestBase ):
10461128 """Tests specifically targeting different stack encodings."""
0 commit comments