https://github.com/python/cpython/commit/722687092f8aaa005708302a502e930a729e8660
commit: 722687092f8aaa005708302a502e930a729e8660
branch: main
author: tonghuaroot (童话) <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-10-05T15:50:03+01:00
summary:

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 <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-07-17-12-00-00.gh-issue-153838.HmCp5s.rst
M Lib/profiling/sampling/heatmap_collector.py
M Lib/test/test_profiling/test_heatmap.py

diff --git a/Lib/profiling/sampling/heatmap_collector.py 
b/Lib/profiling/sampling/heatmap_collector.py
index 220b6b8150ac98..62f42755827f06 100644
--- a/Lib/profiling/sampling/heatmap_collector.py
+++ b/Lib/profiling/sampling/heatmap_collector.py
@@ -783,14 +783,14 @@ def _generate_file_html(self, output_path: Path, 
filename: str,
                           line_counts: Dict[int, int], self_counts: Dict[int, 
int],
                           file_stat: FileStats):
         """Generate HTML for a single source file with heatmap coloring."""
-        # Read source file
+        source_lines = [f"# Source file not available: {filename}"]
         try:
-            source_lines = Path(filename).read_text(encoding='utf-8', 
errors='replace').splitlines()
-        except (IOError, OSError) as e:
-            if not (filename.startswith('<') or filename.startswith('[') or
-                    filename in ('~', '...', '.') or len(filename) < 2):
-                print(f"Warning: Could not read source file {filename}: {e}")
-            source_lines = [f"# Source file not available: {filename}"]
+            path = Path(filename)
+            if path.is_file():
+                source_lines = path.read_text(
+                    encoding='utf-8', errors='replace').splitlines()
+        except (IOError, OSError):
+            pass
 
         # Generate HTML for each line
         max_samples = max(line_counts.values()) if line_counts else 1
diff --git a/Lib/test/test_profiling/test_heatmap.py 
b/Lib/test/test_profiling/test_heatmap.py
index ee27fdd3fa3053..1b4de0caa3d00f 100644
--- a/Lib/test/test_profiling/test_heatmap.py
+++ b/Lib/test/test_profiling/test_heatmap.py
@@ -4,6 +4,7 @@
 import shutil
 import tempfile
 import unittest
+from unittest import mock
 from collections import namedtuple
 from pathlib import Path
 
@@ -615,13 +616,43 @@ def test_export_file_html_has_line_numbers(self):
         html_files = [f for f in os.listdir(output_path)
                       if f.startswith('file_') and f.endswith('.html')]
 
-        if html_files:
-            with open(os.path.join(output_path, html_files[0]), 'r', 
encoding='utf-8') as f:
-                content = f.read()
+        self.assertEqual(len(html_files), 1)
+        with open(os.path.join(output_path, html_files[0]), 'r', 
encoding='utf-8') as f:
+            content = f.read()
+
+        # Should have line-related content
+        self.assertIn('line-', content)
+
+    def test_export_skips_nonexistent_source(self):
+        self.check_unavailable_source(os.path.join(self.test_dir, 
'missing.py'))
 
-            # Should have line-related content
-            self.assertIn('line-', content)
+    def test_export_skips_directory_source(self):
+        self.check_unavailable_source(self.test_dir)
 
+    @unittest.skipUnless(hasattr(os, 'mkfifo'), 'requires os.mkfifo')
+    def test_export_skips_fifo_source(self):
+        filename = os.path.join(self.test_dir, 'source.fifo')
+        os.mkfifo(filename)
+        self.check_unavailable_source(filename)
+
+    def check_unavailable_source(self, filename):
+        collector = HeatmapCollector(sample_interval_usec=100)
+        frames = [(filename, (1, 1, -1, -1), 'f', None)]
+        collector.process_frames(frames, thread_id=1)
+        output_path = Path(self.test_dir) / 'unavailable_source'
+        read_text = Path.read_text
+
+        def check_read(path, *args, **kwargs):
+            self.assertNotEqual(path, Path(filename),
+                                'Non-regular source must not be opened')
+            return read_text(path, *args, **kwargs)
+
+        with (captured_stdout(), captured_stderr(),
+              mock.patch.object(Path, 'read_text', check_read)):
+            collector.export(output_path)
+        content = (output_path / collector.file_index[filename]).read_text(
+            encoding='utf-8')
+        self.assertIn('Source file not available', content)
 
 class MockFrameInfo:
     """Mock FrameInfo for testing.
diff --git 
a/Misc/NEWS.d/next/Library/2026-07-17-12-00-00.gh-issue-153838.HmCp5s.rst 
b/Misc/NEWS.d/next/Library/2026-07-17-12-00-00.gh-issue-153838.HmCp5s.rst
new file mode 100644
index 00000000000000..faecab8cff3e7b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-17-12-00-00.gh-issue-153838.HmCp5s.rst
@@ -0,0 +1,2 @@
+Skip non-regular files in the ``profiling.sampling`` heatmap exporter
+instead of reading them unconditionally.  Patch by tonghuaroot.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to