@@ -570,6 +570,38 @@ def test_flamegraph_collector_export(self):
570570 self .assertIn ('"value":' , content )
571571 self .assertIn ('"children":' , content )
572572
573+ def test_flamegraph_escapes_special_chars_in_funcname (self ):
574+ """Function names with <, >, & must be escaped in the HTML."""
575+ flamegraph_out = tempfile .NamedTemporaryFile (
576+ suffix = ".html" , delete = False
577+ )
578+ self .addCleanup (close_and_unlink , flamegraph_out )
579+
580+ collector = FlamegraphCollector (1000 )
581+ frames = [
582+ MockInterpreterInfo (
583+ 0 ,
584+ [MockThreadInfo (
585+ 1 ,
586+ [MockFrameInfo ("test.py" , 1 , '</script><b>' )]
587+ )],
588+ )
589+ ]
590+ collector .collect (frames )
591+
592+ with captured_stdout (), captured_stderr ():
593+ collector .export (flamegraph_out .name )
594+
595+ with open (flamegraph_out .name , "r" , encoding = "utf-8" ) as f :
596+ content = f .read ()
597+
598+ # The raw </script> must not appear unescaped in the output
599+ # (it would break out of the inline <script> block)
600+ idx = content .find ("EMBEDDED_DATA" )
601+ if idx != - 1 :
602+ after_embed = content [idx :]
603+ self .assertNotIn ("</script><b>" , after_embed .split ("</script>" )[0 ])
604+
573605 def test_flamegraph_collector_empty_export_fails (self ):
574606 """Test empty flamegraph export reports no output."""
575607 flamegraph_out = tempfile .NamedTemporaryFile (
0 commit comments