Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Lib/profiling/sampling/_flamegraph_assets/flamegraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ function createPythonTooltip(data) {
const childCount = d.children ? d.children.length : 0;
const source = d.data.source;

const funcname = resolveString(d.data.funcname) || resolveString(d.data.name);
const funcname = escapeHtml(resolveString(d.data.funcname) || resolveString(d.data.name) || "");
const filename = resolveString(d.data.filename) || "";
const moduleName = resolveString(d.data.module) || "";
const displayName = escapeHtml(useModuleNames ? (moduleName || filename) : filename);
Expand Down
5 changes: 4 additions & 1 deletion Lib/profiling/sampling/stack_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,10 @@ def _get_source_lines(self, func):
return None

def _create_flamegraph_html(self, data):
data_json = json.dumps(data)
data_json = (json.dumps(data)
.replace("<", "\\u003c")
.replace(">", "\\u003e")
.replace("&", "\\u0026"))

template_dir = importlib.resources.files(__package__)
vendor_dir = template_dir / "_vendor"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,38 @@ def test_flamegraph_collector_export(self):
self.assertIn('"value":', content)
self.assertIn('"children":', content)

def test_flamegraph_escapes_special_chars_in_funcname(self):
"""Function names with <, >, & must be escaped in the HTML."""
flamegraph_out = tempfile.NamedTemporaryFile(
suffix=".html", delete=False
)
self.addCleanup(close_and_unlink, flamegraph_out)

collector = FlamegraphCollector(1000)
frames = [
MockInterpreterInfo(
0,
[MockThreadInfo(
1,
[MockFrameInfo("test.py", 1, '</script><b>')]
)],
)
]
collector.collect(frames)

with captured_stdout(), captured_stderr():
collector.export(flamegraph_out.name)

with open(flamegraph_out.name, "r", encoding="utf-8") as f:
content = f.read()

# The raw </script> must not appear unescaped in the output
# (it would break out of the inline <script> block)
idx = content.find("EMBEDDED_DATA")
if idx != -1:
after_embed = content[idx:]
self.assertNotIn("</script><b>", after_embed.split("</script>")[0])

def test_flamegraph_collector_empty_export_fails(self):
"""Test empty flamegraph export reports no output."""
flamegraph_out = tempfile.NamedTemporaryFile(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Escape special characters in ``profiling.sampling`` flamegraph function
names to prevent markup injection in the generated HTML report. Patch by
tonghuaroot.
Loading