diff --git a/Lib/profiling/sampling/stack_collector.py b/Lib/profiling/sampling/stack_collector.py index 60061b4a2ecf11..0c754c3598b57c 100644 --- a/Lib/profiling/sampling/stack_collector.py +++ b/Lib/profiling/sampling/stack_collector.py @@ -505,6 +505,10 @@ def _create_flamegraph_html(self, data): # Python recursion limit. iterencode() uses the Python encoder. data_json = "".join(json.JSONEncoder().iterencode(data)) + # Prevent profile strings from closing the script element or starting + # an HTML comment, while preserving their values in JavaScript. + data_json = data_json.replace("<", "\\u003c") + template_dir = importlib.resources.files(__package__) vendor_dir = template_dir / "_vendor" assets_dir = template_dir / "_assets" 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 d4ba2df5044559..a8c99fc5b29a13 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py @@ -7,6 +7,7 @@ import sys import tempfile import unittest +from unittest import mock from test.support import is_emscripten, run_with_limited_c_stack, set_recursion_limit @@ -596,6 +597,35 @@ def test_flamegraph_collector_export(self): self.assertIn('"sample_interval_usec": 10000', content) self.assertIn("samples * data.stats.sample_interval_usec / 1000", content) + def test_flamegraph_html_escapes_script_data(self): + collector = FlamegraphCollector(1000) + data = { + "strings": [ + "", + "", + "