From bbbc096920d1cc0abfb5f85f763dd9b50de6ce69 Mon Sep 17 00:00:00 2001 From: Jeff Charles Date: Fri, 30 Oct 2026 17:48:23 -0400 Subject: [PATCH] Encode stackframe names --- src/collapsed_stack.rs | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/collapsed_stack.rs b/src/collapsed_stack.rs index 04d2f49..a96d5da 100644 --- a/src/collapsed_stack.rs +++ b/src/collapsed_stack.rs @@ -7,6 +7,8 @@ pub struct CollapsedStack { /// Contains the data collected by the profiler in collapsed stacks format. /// The main use case is to call `write_to_file` on a `CollapsedStacks` instance. /// The resulting file can be opened in a variety of profiling tools. +/// Frame names percent-encode `%`, `;`, carriage returns, and newlines so they +/// cannot be confused with frame or sample delimiters. /// The format is explained here: https://github.com/jlfwong/speedscope/wiki/Importing-from-custom-sources#brendan-greggs-collapsed-stack-format pub struct CollapsedStacks(Vec); @@ -42,7 +44,15 @@ impl fmt::Display for CollapsedStack { } else { write!(f, ";")?; } - write!(f, "{}", frame)?; + for character in frame.chars() { + match character { + '%' => write!(f, "%25")?, + ';' => write!(f, "%3B")?, + '\r' => write!(f, "%0D")?, + '\n' => write!(f, "%0A")?, + other => write!(f, "{other}")?, + } + } } write!(f, " {}", self.weight) } @@ -56,3 +66,28 @@ impl fmt::Display for CollapsedStacks { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::CollapsedStacks; + + #[test] + fn escapes_frame_delimiters_and_literal_percent_signs() { + let stacks = CollapsedStacks::new( + vec!["leaf;frame\nnext\r%3B".into(), "root".into()], + vec![vec![0, 1]], + Some(vec![7]), + ); + assert_eq!(stacks.to_string(), "root;leaf%3Bframe%0Anext%0D%253B 7\n"); + } + + #[test] + fn preserves_ordinary_frame_names() { + let stacks = CollapsedStacks::new( + vec!["space::foo(int, β)".into(), "root".into()], + vec![vec![0, 1]], + Some(vec![7]), + ); + assert_eq!(stacks.to_string(), "root;space::foo(int, β) 7\n"); + } +}