memscope_rs/analyzer/
export.rs1use crate::core::error::{MemScopeError, MemScopeResult};
4use crate::view::MemoryView;
5use std::path::Path;
6
7pub struct ExportEngine<'a> {
11 view: &'a MemoryView,
12}
13
14impl<'a> ExportEngine<'a> {
15 pub fn new(view: &'a MemoryView) -> Self {
17 Self { view }
18 }
19
20 pub fn json<P: AsRef<Path>>(&self, path: P) -> MemScopeResult<()> {
22 let json = self.to_json()?;
23 std::fs::write(path, json)
24 .map_err(|e| MemScopeError::export("json", format!("Failed to write JSON file: {}", e)))
25 }
26
27 pub fn html<P: AsRef<Path>>(&self, path: P) -> MemScopeResult<()> {
29 let html = self.to_html()?;
30 std::fs::write(path, html)
31 .map_err(|e| MemScopeError::export("html", format!("Failed to write HTML file: {}", e)))
32 }
33
34 pub fn to_json(&self) -> MemScopeResult<String> {
36 let report = self.build_report();
37 serde_json::to_string_pretty(&report)
38 .map_err(|e| MemScopeError::export("json", format!("Failed to serialize JSON: {}", e)))
39 }
40
41 pub fn to_html(&self) -> MemScopeResult<String> {
45 let report = self.build_report();
46 let json = serde_json::to_string_pretty(&report).unwrap_or_default();
47 let escaped_json = html_escape(&json);
48
49 Ok(format!(
50 r#"<!DOCTYPE html>
51<html lang="en">
52<head>
53 <meta charset="UTF-8">
54 <meta name="viewport" content="width=device-width, initial-scale=1.0">
55 <title>Memory Analysis Report</title>
56 <style>
57 body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 2rem; }}
58 h1 {{ color: #333; }}
59 pre {{ background: #f5f5f5; padding: 1rem; border-radius: 4px; overflow-x: auto; }}
60 </style>
61</head>
62<body>
63 <h1>Memory Analysis Report</h1>
64 <pre>{}</pre>
65</body>
66</html>"#,
67 escaped_json
68 ))
69 }
70
71 fn build_report(&self) -> serde_json::Value {
72 serde_json::json!({
73 "stats": {
74 "allocation_count": self.view.len(),
75 "total_bytes": self.view.total_memory(),
76 },
77 "snapshot": self.view.snapshot(),
78 })
79 }
80}
81
82fn html_escape(s: &str) -> String {
91 s.chars()
92 .map(|c| match c {
93 '&' => "&".to_string(),
94 '<' => "<".to_string(),
95 '>' => ">".to_string(),
96 '"' => """.to_string(),
97 '\'' => "'".to_string(),
98 _ => c.to_string(),
99 })
100 .collect()
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use crate::event_store::MemoryEvent;
107
108 #[test]
109 fn test_export_to_json() {
110 let events = vec![MemoryEvent::allocate(0x1000, 64, 1)];
111 let view = MemoryView::from_events(events);
112 let engine = ExportEngine::new(&view);
113 let json = engine.to_json().expect("JSON export should succeed");
114 assert!(json.contains("allocation_count"));
115 }
116
117 #[test]
118 fn test_html_escape() {
119 assert_eq!(html_escape("&"), "&");
120 assert_eq!(html_escape("<"), "<");
121 assert_eq!(html_escape(">"), ">");
122 assert_eq!(html_escape("\""), """);
123 assert_eq!(html_escape("'"), "'");
124 assert_eq!(html_escape("Hello <World>"), "Hello <World>");
125 }
126
127 #[test]
128 fn test_html_escape_prevents_xss() {
129 let malicious = "<script>alert('xss')</script>";
130 let escaped = html_escape(malicious);
131 assert!(!escaped.contains("<script>"));
132 assert!(escaped.contains("<script>"));
133 }
134
135 #[test]
136 fn test_to_html_escapes_content() {
137 let events = vec![MemoryEvent::allocate(0x1000, 64, 1)
138 .with_var_name("<script>alert('xss')</script>".to_string())];
139 let view = MemoryView::from_events(events);
140 let engine = ExportEngine::new(&view);
141 let html = engine.to_html().expect("HTML export should succeed");
142
143 assert!(!html.contains("<script>"));
145 assert!(html.contains("<script>"));
147 }
148}