Skip to content

Commit 7226870

Browse files
gh-153838: Skip non-regular source files in the heatmap exporter (#153839)
* gh-153838: Skip non-regular and oversized files in heatmap exporter * Drop the size cap, keep the is_file() guard only * gh-153838: Verify non-regular sources are never opened --------- Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
1 parent 1441e51 commit 7226870

3 files changed

Lines changed: 45 additions & 12 deletions

File tree

‎Lib/profiling/sampling/heatmap_collector.py‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -783,14 +783,14 @@ def _generate_file_html(self, output_path: Path, filename: str,
783783
line_counts: Dict[int, int], self_counts: Dict[int, int],
784784
file_stat: FileStats):
785785
"""Generate HTML for a single source file with heatmap coloring."""
786-
# Read source file
786+
source_lines = [f"# Source file not available: {filename}"]
787787
try:
788-
source_lines = Path(filename).read_text(encoding='utf-8', errors='replace').splitlines()
789-
except (IOError, OSError) as e:
790-
if not (filename.startswith('<') or filename.startswith('[') or
791-
filename in ('~', '...', '.') or len(filename) < 2):
792-
print(f"Warning: Could not read source file {filename}: {e}")
793-
source_lines = [f"# Source file not available: {filename}"]
788+
path = Path(filename)
789+
if path.is_file():
790+
source_lines = path.read_text(
791+
encoding='utf-8', errors='replace').splitlines()
792+
except (IOError, OSError):
793+
pass
794794

795795
# Generate HTML for each line
796796
max_samples = max(line_counts.values()) if line_counts else 1

‎Lib/test/test_profiling/test_heatmap.py‎

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import shutil
55
import tempfile
66
import unittest
7+
from unittest import mock
78
from collections import namedtuple
89
from pathlib import Path
910

@@ -615,13 +616,43 @@ def test_export_file_html_has_line_numbers(self):
615616
html_files = [f for f in os.listdir(output_path)
616617
if f.startswith('file_') and f.endswith('.html')]
617618

618-
if html_files:
619-
with open(os.path.join(output_path, html_files[0]), 'r', encoding='utf-8') as f:
620-
content = f.read()
619+
self.assertEqual(len(html_files), 1)
620+
with open(os.path.join(output_path, html_files[0]), 'r', encoding='utf-8') as f:
621+
content = f.read()
622+
623+
# Should have line-related content
624+
self.assertIn('line-', content)
625+
626+
def test_export_skips_nonexistent_source(self):
627+
self.check_unavailable_source(os.path.join(self.test_dir, 'missing.py'))
621628

622-
# Should have line-related content
623-
self.assertIn('line-', content)
629+
def test_export_skips_directory_source(self):
630+
self.check_unavailable_source(self.test_dir)
624631

632+
@unittest.skipUnless(hasattr(os, 'mkfifo'), 'requires os.mkfifo')
633+
def test_export_skips_fifo_source(self):
634+
filename = os.path.join(self.test_dir, 'source.fifo')
635+
os.mkfifo(filename)
636+
self.check_unavailable_source(filename)
637+
638+
def check_unavailable_source(self, filename):
639+
collector = HeatmapCollector(sample_interval_usec=100)
640+
frames = [(filename, (1, 1, -1, -1), 'f', None)]
641+
collector.process_frames(frames, thread_id=1)
642+
output_path = Path(self.test_dir) / 'unavailable_source'
643+
read_text = Path.read_text
644+
645+
def check_read(path, *args, **kwargs):
646+
self.assertNotEqual(path, Path(filename),
647+
'Non-regular source must not be opened')
648+
return read_text(path, *args, **kwargs)
649+
650+
with (captured_stdout(), captured_stderr(),
651+
mock.patch.object(Path, 'read_text', check_read)):
652+
collector.export(output_path)
653+
content = (output_path / collector.file_index[filename]).read_text(
654+
encoding='utf-8')
655+
self.assertIn('Source file not available', content)
625656

626657
class MockFrameInfo:
627658
"""Mock FrameInfo for testing.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Skip non-regular files in the ``profiling.sampling`` heatmap exporter
2+
instead of reading them unconditionally. Patch by tonghuaroot.

0 commit comments

Comments
 (0)