https://github.com/python/cpython/commit/c3706f4c687d76b85702c1f946abd3f3d3eafcb8
commit: c3706f4c687d76b85702c1f946abd3f3d3eafcb8
branch: main
author: tonghuaroot (童话) <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-09-02T10:01:29Z
summary:

gh-156810: Write the profiler's collapsed-stack export as UTF-8 (#156811)

files:
A Misc/NEWS.d/next/Library/2026-09-02-17-28-16.gh-issue-156810.cLpEnc.rst
M Lib/profiling/sampling/stack_collector.py
M Lib/test/test_profiling/test_sampling_profiler/test_collectors.py

diff --git a/Lib/profiling/sampling/stack_collector.py 
b/Lib/profiling/sampling/stack_collector.py
index 8de460856666d7f..60ff5d7776b061d 100644
--- a/Lib/profiling/sampling/stack_collector.py
+++ b/Lib/profiling/sampling/stack_collector.py
@@ -60,7 +60,8 @@ def export(self, filename):
 
         lines.sort(key=lambda x: (-x[1], x[0]))
 
-        with open(filename, "w") as f:
+        with open(filename, "w",
+                  encoding="utf-8", errors="surrogatepass") as f:
             for stack, count in lines:
                 f.write(f"{stack} {count}\n")
         print(f"Collapsed stack output written to {filename}")
diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py 
b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
index 069a72eb1c88a6a..dcfca9a9bceb503 100644
--- a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
+++ b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
@@ -466,6 +466,28 @@ def test_collapsed_stack_collector_export(self):
         self.assertIn(stack1_expected, lines)
         self.assertIn(stack2_expected, lines)
 
+    def test_collapsed_stack_collector_export_non_ascii_names(self):
+        # gh-156810: frame names are written verbatim, so the output must be
+        # opened with an encoding that can represent non-ASCII and
+        # surrogate-escaped (undecodable-path) names.
+        collapsed_out = tempfile.NamedTemporaryFile(delete=False)
+        self.addCleanup(close_and_unlink, collapsed_out)
+
+        collector = CollapsedStackCollector(1000)
+        frame = MockFrameInfo("/tmp/ba\udc80d.py", 5, "计算")
+        collector.collect([
+            MockInterpreterInfo(0, [MockThreadInfo(1, [frame])])
+        ])
+
+        with captured_stdout(), captured_stderr():
+            collector.export(collapsed_out.name)
+
+        with open(collapsed_out.name, encoding="utf-8",
+                  errors="surrogatepass") as f:
+            content = f.read()
+        self.assertIn("计算", content)
+        self.assertIn("ba\udc80d.py", content)
+
     def test_flamegraph_collector_basic(self):
         """Test basic FlamegraphCollector functionality."""
         collector = FlamegraphCollector(1000)
diff --git 
a/Misc/NEWS.d/next/Library/2026-09-02-17-28-16.gh-issue-156810.cLpEnc.rst 
b/Misc/NEWS.d/next/Library/2026-09-02-17-28-16.gh-issue-156810.cLpEnc.rst
new file mode 100644
index 000000000000000..610b0b4fcdc1baa
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-02-17-28-16.gh-issue-156810.cLpEnc.rst
@@ -0,0 +1,4 @@
+Fix a :exc:`UnicodeEncodeError` crash in the sampling profiler's
+collapsed-stack export (``--collapsed``) when a sampled frame's function or
+file name contains non-ASCII or surrogate-escaped characters. The output file
+is now written as UTF-8.

_______________________________________________
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