1use std::fs;
5use std::sync::OnceLock;
6
7static BINARY_DASHBOARD_TEMPLATE: OnceLock<String> = OnceLock::new();
8
9fn get_binary_dashboard_template() -> &'static str {
10 BINARY_DASHBOARD_TEMPLATE.get_or_init(|| {
11 if let Ok(external_path) = std::env::var("MEMSCOPE_BINARY_TEMPLATE") {
13 if let Ok(content) = fs::read_to_string(&external_path) {
14 println!("📁 Loaded external binary template: {}", external_path);
15 return content;
16 }
17 }
18
19 EMBEDDED_BINARY_DASHBOARD_TEMPLATE.to_string()
21 })
22}
23
24const EMBEDDED_BINARY_DASHBOARD_TEMPLATE: &str = r#"
26<!DOCTYPE html>
27<html lang="en">
28
29<head>
30 <meta charset="UTF-8" />
31 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
32 <title>{{PROJECT_NAME}} - Binary Memory Analysis Dashboard</title>
33 <script src="https://cdn.tailwindcss.com"></script>
34 <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet" />
35 <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
36 <script src="https://d3js.org/d3.v7.min.js"></script>
37 <script src="https://unpkg.com/three@0.128.0/build/three.min.js"></script>
38 <script src="https://unpkg.com/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
39
40 <style>
41 {
42 {
43 CSS_CONTENT
44 }
45 }
46
47 /* Clean, high-contrast layout variables */
48 :root {
49 --primary-blue: #2563eb;
50 --primary-green: #059669;
51 --primary-red: #dc2626;
52 --primary-orange: #ea580c;
53 --text-primary: #1f2937;
54 --text-secondary: #6b7280;
55 --bg-primary: #ffffff;
56 --bg-secondary: #f8fafc;
57 --border-light: #e5e7eb;
58 --shadow-light: 0 1px 3px 0 rgb(0 0 0 / 0.1);
59 }
60
61 .dark {
62 --text-primary: #f9fafb;
63 --text-secondary: #d1d5db;
64 --bg-primary: #111827;
65 --bg-secondary: #1f2937;
66 --border-light: #374151;
67 --shadow-light: 0 4px 6px -1px rgb(0 0 0 / 0.3);
68 }
69
70 body {
71 font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
72 background: var(--bg-secondary);
73 color: var(--text-primary);
74 transition: all 0.3s ease;
75 line-height: 1.6;
76 margin: 0;
77 padding: 0;
78 }
79
80 .dashboard-container {
81 max-width: 1400px;
82 margin: 0 auto;
83 padding: 24px;
84 min-height: 100vh;
85 }
86
87 /* 顶部标题栏 */
88 .header {
89 display: flex;
90 justify-content: space-between;
91 align-items: center;
92 margin-bottom: 32px;
93 padding: 20px 0;
94 border-bottom: 1px solid var(--border-light);
95 }
96
97 .header h1 {
98 font-size: 2rem;
99 font-weight: 700;
100 color: var(--text-primary);
101 margin: 0;
102 }
103
104 .header .subtitle {
105 color: var(--text-secondary);
106 font-size: 0.9rem;
107 margin-top: 4px;
108 }
109
110 /* 主题切换按钮 */
111 .theme-toggle {
112 background: var(--primary-blue);
113 color: white;
114 border: none;
115 padding: 10px 16px;
116 border-radius: 8px;
117 cursor: pointer;
118 font-size: 14px;
119 font-weight: 500;
120 transition: all 0.2s ease;
121 display: flex;
122 align-items: center;
123 gap: 8px;
124 }
125
126 .theme-toggle:hover {
127 background: #1d4ed8;
128 transform: translateY(-1px);
129 }
130
131 /* 卡片样式 */
132 .card {
133 background: var(--bg-primary);
134 border: 1px solid var(--border-light);
135 border-radius: 12px;
136 padding: 24px;
137 box-shadow: var(--shadow-light);
138 transition: all 0.3s ease;
139 }
140
141 .card:hover {
142 transform: translateY(-2px);
143 box-shadow: 0 8px 25px -5px rgb(0 0 0 / 0.1);
144 }
145
146 .card h2 {
147 font-size: 1.25rem;
148 font-weight: 600;
149 color: var(--text-primary);
150 margin: 0 0 16px 0;
151 border-bottom: 2px solid var(--primary-blue);
152 padding-bottom: 8px;
153 }
154
155 /* 网格布局 */
156 .grid {
157 display: grid;
158 gap: 24px;
159 margin-bottom: 32px;
160 }
161
162 .grid-2 {
163 grid-template-columns: 1fr 1fr;
164 }
165
166 .grid-3 {
167 grid-template-columns: repeat(3, 1fr);
168 }
169
170 .grid-4 {
171 grid-template-columns: repeat(4, 1fr);
172 }
173
174 /* KPI */
175 .kpi-card {
176 text-align: center;
177 padding: 20px;
178 background: linear-gradient(135deg, var(--primary-blue) 0%, #3b82f6 100%);
179 color: white;
180 border-radius: 12px;
181 border: none;
182 box-shadow: var(--shadow-light);
183 }
184
185 .kpi-value {
186 font-size: 2rem;
187 font-weight: 700;
188 margin-bottom: 4px;
189 }
190
191 .kpi-label {
192 font-size: 0.875rem;
193 opacity: 0.9;
194 font-weight: 500;
195 }
196
197
198 .chart-container {
199 height: 300px;
200 background: var(--bg-primary);
201 border-radius: 8px;
202 position: relative;
203 padding: 16px;
204 }
205
206 table {
207 width: 100%;
208 border-collapse: collapse;
209 margin-top: 16px;
210 }
211
212 th,
213 td {
214 padding: 12px;
215 text-align: left;
216 border-bottom: 1px solid var(--border-light);
217 }
218
219 th {
220 background: var(--bg-secondary);
221 font-weight: 600;
222 color: var(--text-primary);
223 }
224
225 tr:hover {
226 background: var(--bg-secondary);
227 }
228
229 @media (max-width: 768px) {
230
231 .grid-2,
232 .grid-3,
233 .grid-4 {
234 grid-template-columns: 1fr;
235 }
236
237 .dashboard-container {
238 padding: 16px;
239 }
240
241 .header {
242 flex-direction: column;
243 gap: 16px;
244 text-align: center;
245 }
246 }
247
248 .scroll {
249 max-height: 400px;
250 overflow: auto;
251 }
252
253 .scroll::-webkit-scrollbar {
254 width: 6px;
255 }
256
257 .scroll::-webkit-scrollbar-track {
258 background: var(--bg-secondary);
259 }
260
261 .scroll::-webkit-scrollbar-thumb {
262 background: var(--border-light);
263 border-radius: 3px;
264 }
265
266 .status-badge {
267 padding: 4px 8px;
268 border-radius: 4px;
269 font-size: 0.75rem;
270 font-weight: 500;
271 }
272
273 .status-active {
274 background: #dcfce7;
275 color: #166534;
276 }
277
278 .status-leaked {
279 background: #fee2e2;
280 color: #dc2626;
281 }
282
283 .status-freed {
284 background: #e5e7eb;
285 color: #374151;
286 }
287
288 .dark .status-active {
289 background: #064e3b;
290 color: #34d399;
291 }
292
293 .dark .status-leaked {
294 background: #7f1d1d;
295 color: #fca5a5;
296 }
297
298 .dark .status-freed {
299 background: #374151;
300 color: #d1d5db;
301 }
302
303 .risk-low {
304 background: #dcfce7;
305 color: #166534;
306 }
307
308 .risk-medium {
309 background: #fef3c7;
310 color: #92400e;
311 }
312
313 .risk-high {
314 background: #fee2e2;
315 color: #dc2626;
316 }
317
318 .dark .risk-low {
319 background: #064e3b;
320 color: #34d399;
321 }
322
323 .dark .risk-medium {
324 background: #78350f;
325 color: #fbbf24;
326 }
327
328 .dark .risk-high {
329 background: #7f1d1d;
330 color: #fca5a5;
331 }
332
333 /* Enhanced Lifecycle Visualization Styles */
334 .allocation-type {
335 padding: 3px 6px;
336 border-radius: 3px;
337 font-size: 0.7rem;
338 font-weight: 600;
339 text-transform: uppercase;
340 display: inline-block;
341 }
342
343 .type-heap {
344 background: #fef3c7;
345 color: #92400e;
346 border: 1px solid #f59e0b;
347 }
348
349 .type-stack {
350 background: #dbeafe;
351 color: #1e40af;
352 border: 1px solid #3b82f6;
353 }
354
355 .type-unknown {
356 background: #f3f4f6;
357 color: #6b7280;
358 border: 1px solid #9ca3af;
359 }
360
361 .dark .type-heap {
362 background: #78350f;
363 color: #fbbf24;
364 }
365
366 .dark .type-stack {
367 background: #1e3a8a;
368 color: #60a5fa;
369 }
370
371 .dark .type-unknown {
372 background: #374151;
373 color: #d1d5db;
374 }
375
376 /* Enhanced progress bar animations */
377 @keyframes shine {
378 0% {
379 left: -100%;
380 }
381
382 50% {
383 left: 100%;
384 }
385
386 100% {
387 left: 100%;
388 }
389 }
390
391 @keyframes pulse {
392
393 0%,
394 100% {
395 opacity: 1;
396 }
397
398 50% {
399 opacity: 0.7;
400 }
401 }
402
403 /* Enhanced lifecycle item styles */
404 .lifecycle-item.heap {
405 border-left-color: #ff6b35 !important;
406 }
407
408 .lifecycle-item.stack {
409 border-left-color: #4dabf7 !important;
410 }
411
412 .lifecycle-item:hover {
413 animation: pulse 1s ease-in-out;
414 }
415
416 .lifecycle-bar {
417 height: 16px;
418 background: var(--bg-secondary);
419 border-radius: 8px;
420 position: relative;
421 overflow: hidden;
422 margin: 6px 0;
423 border: 1px solid var(--border-light);
424 }
425
426 .lifecycle-progress {
427 height: 100%;
428 background: linear-gradient(90deg, var(--primary-green), var(--primary-blue));
429 border-radius: 7px;
430 position: relative;
431 transition: width 0.3s ease;
432 }
433
434 .lifecycle-item {
435 margin: 8px 0;
436 padding: 12px;
437 background: var(--bg-secondary);
438 border-radius: 8px;
439 border-left: 4px solid var(--primary-blue);
440 transition: all 0.2s ease;
441 }
442
443 .lifecycle-item:hover {
444 background: var(--bg-primary);
445 box-shadow: var(--shadow-light);
446 }
447
448 .lifecycle-item.heap {
449 border-left-color: var(--primary-orange);
450 }
451
452 .lifecycle-item.stack {
453 border-left-color: var(--primary-blue);
454 }
455
456 .time-info {
457 font-size: 0.75rem;
458 color: var(--text-secondary);
459 margin-top: 6px;
460 font-family: 'Courier New', monospace;
461 }
462
463 .time-badge {
464 display: inline-block;
465 padding: 2px 6px;
466 background: var(--bg-primary);
467 border-radius: 3px;
468 margin-right: 8px;
469 border: 1px solid var(--border-light);
470 }
471
472 /* Enhanced 3D Memory Layout Styles */
473 .memory-3d-container {
474 position: relative;
475 width: 100%;
476 height: 400px;
477 background: var(--bg-secondary);
478 border: 1px solid var(--border-light);
479 border-radius: 8px;
480 overflow: hidden;
481 }
482
483 /* Removed problematic absolute positioning CSS for memory-3d-controls */
484
485 .memory-3d-info {
486 position: absolute;
487 bottom: 10px;
488 left: 10px;
489 z-index: 100;
490 background: rgba(0, 0, 0, 0.7);
491 color: white;
492 padding: 8px 12px;
493 border-radius: 6px;
494 font-size: 0.8rem;
495 font-family: monospace;
496 }
497
498 /* Timeline Playback Styles */
499 .timeline-container {
500 background: var(--bg-secondary);
501 border: 1px solid var(--border-light);
502 border-radius: 8px;
503 padding: 16px;
504 margin: 16px 0;
505 }
506
507 .timeline-slider {
508 width: 100%;
509 height: 6px;
510 background: var(--border-light);
511 border-radius: 3px;
512 position: relative;
513 cursor: pointer;
514 margin: 12px 0;
515 }
516
517 .timeline-progress {
518 height: 100%;
519 background: linear-gradient(90deg, var(--primary-blue), var(--primary-green));
520 border-radius: 3px;
521 transition: width 0.1s ease;
522 }
523
524 .timeline-thumb {
525 position: absolute;
526 top: -6px;
527 width: 18px;
528 height: 18px;
529 background: var(--primary-blue);
530 border: 2px solid white;
531 border-radius: 50%;
532 cursor: grab;
533 box-shadow: var(--shadow-light);
534 }
535
536 .timeline-thumb:active {
537 cursor: grabbing;
538 transform: scale(1.1);
539 }
540
541 .timeline-controls {
542 display: flex;
543 justify-content: center;
544 gap: 12px;
545 margin-top: 12px;
546 }
547
548 .timeline-btn {
549 background: var(--primary-blue);
550 color: white;
551 border: none;
552 padding: 8px 12px;
553 border-radius: 6px;
554 cursor: pointer;
555 font-size: 0.8rem;
556 transition: all 0.2s ease;
557 }
558
559 .timeline-btn:hover {
560 background: #1d4ed8;
561 transform: translateY(-1px);
562 }
563
564 .timeline-btn:disabled {
565 background: var(--text-secondary);
566 cursor: not-allowed;
567 transform: none;
568 }
569
570 /* Memory Heatmap Styles */
571 .heatmap-container {
572 position: relative;
573 width: 100%;
574 height: 300px;
575 background: var(--bg-secondary);
576 border: 1px solid var(--border-light);
577 border-radius: 8px;
578 overflow: hidden;
579 }
580
581 .heatmap-legend {
582 position: absolute;
583 top: 10px;
584 right: 10px;
585 z-index: 100;
586 background: rgba(255, 255, 255, 0.9);
587 padding: 8px;
588 border-radius: 6px;
589 font-size: 0.7rem;
590 }
591
592 .dark .heatmap-legend {
593 background: rgba(0, 0, 0, 0.8);
594 color: white;
595 }
596
597 .heatmap-mode-selector {
598 display: flex;
599 gap: 6px;
600 margin-bottom: 12px;
601 }
602
603 .heatmap-mode-btn {
604 background: var(--bg-primary);
605 color: var(--text-primary);
606 border: 1px solid var(--border-light);
607 padding: 6px 12px;
608 border-radius: 4px;
609 cursor: pointer;
610 font-size: 0.8rem;
611 transition: all 0.2s ease;
612 }
613
614 .heatmap-mode-btn.active {
615 background: var(--primary-blue);
616 color: white;
617 border-color: var(--primary-blue);
618 }
619
620 .heatmap-mode-btn:hover {
621 background: var(--primary-blue);
622 color: white;
623 }
624
625 /* Memory Block Visualization */
626 .memory-block {
627 position: absolute;
628 border: 1px solid rgba(255, 255, 255, 0.3);
629 border-radius: 2px;
630 cursor: pointer;
631 transition: all 0.2s ease;
632 }
633
634 .memory-block:hover {
635 border-color: white;
636 border-width: 2px;
637 z-index: 10;
638 }
639
640 .memory-block.heap {
641 background: linear-gradient(45deg, #ff6b35, #f7931e);
642 }
643
644 .memory-block.stack {
645 background: linear-gradient(45deg, #4dabf7, #339af0);
646 }
647
648 .memory-block.leaked {
649 background: linear-gradient(45deg, #dc2626, #ef4444);
650 box-shadow: 0 0 8px rgba(239, 68, 68, 0.5);
651 }
652
653 /* Tooltip Styles */
654 .memory-tooltip {
655 position: absolute;
656 background: rgba(0, 0, 0, 0.9);
657 color: white;
658 padding: 8px 12px;
659 border-radius: 6px;
660 font-size: 0.8rem;
661 font-family: monospace;
662 pointer-events: none;
663 z-index: 1000;
664 max-width: 300px;
665 line-height: 1.4;
666 }
667
668 .memory-tooltip::after {
669 content: '';
670 position: absolute;
671 top: 100%;
672 left: 50%;
673 margin-left: -5px;
674 border-width: 5px;
675 border-style: solid;
676 border-color: rgba(0, 0, 0, 0.9) transparent transparent transparent;
677 }
678 </style>
679 <script>
680 // Global safe update function - must be defined first
681 function safeUpdateElement(id, value, defaultValue = '-') {
682 try {
683 const el = document.getElementById(id);
684 if (el) {
685 el.textContent = value;
686 return true;
687 } else {
688 console.warn(`Element with ID '${id}' not found`);
689 return false;
690 }
691 } catch (error) {
692 console.error(`Error updating element '${id}':`, error);
693 return false;
694 }
695 }
696
697 // Global formatBytes function
698 function formatBytes(bytes) {
699 if (bytes === 0) return '0 B';
700 if (typeof bytes !== 'number') return '0 B';
701
702 const k = 1024;
703 const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
704 const i = Math.floor(Math.log(bytes) / Math.log(k));
705
706 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
707 }
708
709 // Beautiful modal dialog system
710 function createModal(title, content) {
711 // Remove existing modal if any
712 const existingModal = document.getElementById('custom-modal');
713 if (existingModal) {
714 existingModal.remove();
715 }
716
717 const modal = document.createElement('div');
718 modal.id = 'custom-modal';
719 modal.style.cssText = `
720 position: fixed;
721 top: 0;
722 left: 0;
723 width: 100%;
724 height: 100%;
725 background: rgba(0, 0, 0, 0.7);
726 display: flex;
727 align-items: center;
728 justify-content: center;
729 z-index: 10000;
730 opacity: 0;
731 transition: opacity 0.3s ease;
732 `;
733
734 const modalContent = document.createElement('div');
735 modalContent.style.cssText = `
736 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
737 border-radius: 16px;
738 padding: 0;
739 max-width: 500px;
740 width: 90%;
741 max-height: 80%;
742 overflow: hidden;
743 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
744 transform: scale(0.7);
745 transition: transform 0.3s ease;
746 border: 2px solid rgba(255, 255, 255, 0.2);
747 `;
748
749 const header = document.createElement('div');
750 header.style.cssText = `
751 background: rgba(255, 255, 255, 0.15);
752 padding: 20px 24px;
753 border-bottom: 1px solid rgba(255, 255, 255, 0.1);
754 display: flex;
755 justify-content: space-between;
756 align-items: center;
757 `;
758
759 const titleEl = document.createElement('h3');
760 titleEl.style.cssText = `
761 margin: 0;
762 color: white;
763 font-size: 20px;
764 font-weight: 600;
765 text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
766 `;
767 titleEl.textContent = title;
768
769 const closeBtn = document.createElement('button');
770 closeBtn.style.cssText = `
771 background: rgba(255, 255, 255, 0.2);
772 border: none;
773 color: white;
774 width: 32px;
775 height: 32px;
776 border-radius: 50%;
777 cursor: pointer;
778 display: flex;
779 align-items: center;
780 justify-content: center;
781 font-size: 18px;
782 font-weight: bold;
783 transition: background 0.2s ease;
784 `;
785 closeBtn.innerHTML = '×';
786 closeBtn.addEventListener('mouseenter', () => {
787 closeBtn.style.background = 'rgba(255, 255, 255, 0.3)';
788 });
789 closeBtn.addEventListener('mouseleave', () => {
790 closeBtn.style.background = 'rgba(255, 255, 255, 0.2)';
791 });
792
793 const body = document.createElement('div');
794 body.style.cssText = `
795 padding: 24px;
796 color: white;
797 line-height: 1.6;
798 overflow-y: auto;
799 max-height: 400px;
800 `;
801 body.innerHTML = content;
802
803 function closeModal() {
804 modal.style.opacity = '0';
805 modalContent.style.transform = 'scale(0.7)';
806 setTimeout(() => {
807 modal.remove();
808 }, 300);
809 }
810
811 closeBtn.addEventListener('click', closeModal);
812 modal.addEventListener('click', (e) => {
813 if (e.target === modal) closeModal();
814 });
815
816 document.addEventListener('keydown', function escapeHandler(e) {
817 if (e.key === 'Escape') {
818 closeModal();
819 document.removeEventListener('keydown', escapeHandler);
820 }
821 });
822
823 header.appendChild(titleEl);
824 header.appendChild(closeBtn);
825 modalContent.appendChild(header);
826 modalContent.appendChild(body);
827 modal.appendChild(modalContent);
828 document.body.appendChild(modal);
829
830 // Animate in
831 setTimeout(() => {
832 modal.style.opacity = '1';
833 modalContent.style.transform = 'scale(1)';
834 }, 10);
835
836 return modal;
837 }
838
839 // Data injection placeholder - will be replaced by build tool
840 window.analysisData = {{BINARY_DATA}};
841
842 // Emergency fallback: Load data directly from JSON files if injection failed
843 if (!window.analysisData || Object.keys(window.analysisData).length === 0 ||
844 !window.analysisData.memory_analysis || !window.analysisData.memory_analysis.allocations) {
845
846 console.warn('Data injection failed, attempting to load from JSON files...');
847
848 // Try to fetch the JSON data directly
849 fetch('./large_scale_user_memory_analysis.json')
850 .then(response => response.json())
851 .then(memoryData => {
852 console.log('✅ Loaded memory analysis data:', memoryData);
853
854 // Construct the expected data structure
855 window.analysisData = {
856 memory_analysis: memoryData,
857 lifetime: {},
858 complex_types: {},
859 unsafe_ffi: {},
860 performance: {}
861 };
862
863 // Try to load other JSON files
864 Promise.all([
865 fetch('./large_scale_user_lifetime.json').then(r => r.json()).catch(() => ({})),
866 fetch('./large_scale_user_complex_types.json').then(r => r.json()).catch(() => ({})),
867 fetch('./large_scale_user_unsafe_ffi.json').then(r => r.json()).catch(() => ({})),
868 fetch('./large_scale_user_performance.json').then(r => r.json()).catch(() => ({}))
869 ]).then(([lifetime, complexTypes, unsafeFfi, performance]) => {
870 window.analysisData.lifetime = lifetime;
871 window.analysisData.complex_types = complexTypes;
872 window.analysisData.unsafe_ffi = unsafeFfi;
873 window.analysisData.performance = performance;
874
875 console.log('✅ All data loaded, initializing enhanced features...');
876
877 // Trigger enhanced features initialization
878 console.log('🚀 Triggering enhanced features initialization...');
879 if (typeof initEnhancedLifecycleVisualization === 'function') {
880 setTimeout(() => {
881 console.log('🔄 Calling initEnhancedLifecycleVisualization...');
882 initEnhancedLifecycleVisualization();
883 }, 100);
884 } else {
885 console.error('❌ initEnhancedLifecycleVisualization function not found');
886 }
887
888 // Also trigger the main dashboard initialization if needed
889 if (typeof initDashboard === 'function') {
890 setTimeout(() => {
891 console.log('🔄 Calling initDashboard...');
892 initDashboard();
893 }, 200);
894 }
895 });
896 })
897 .catch(error => {
898 console.error('❌ Failed to load JSON data:', error);
899
900 // Last resort: Create dummy data for testing
901 window.analysisData = {
902 memory_analysis: {
903 allocations: [
904 {
905 var_name: 'test_var_1',
906 type_name: 'Arc<String>',
907 size: 1024,
908 timestamp_alloc: Date.now() * 1000000,
909 lifetime_ms: 100.5,
910 is_leaked: false
911 },
912 {
913 var_name: 'test_var_2',
914 type_name: 'Vec<i32>',
915 size: 2048,
916 timestamp_alloc: Date.now() * 1000000 + 1000000,
917 lifetime_ms: 250.0,
918 is_leaked: true
919 }
920 ]
921 }
922 };
923 console.log('⚠️ Using dummy data for testing');
924 });
925 } else {
926 console.log('✅ Data injection successful');
927 }
928
929 console.log('Final analysisData:', window.analysisData);
930 console.log('Allocations available:', window.analysisData?.memory_analysis?.allocations?.length || 0);
931
932 // Enhanced Memory Visualization Functions
933 class EnhancedMemoryVisualizer {
934 constructor() {
935 this.scene = null;
936 this.camera = null;
937 this.renderer = null;
938 this.controls = null;
939 this.memoryBlocks = [];
940 this.timeline = {
941 isPlaying: false,
942 currentTime: 0,
943 totalTime: 0,
944 speed: 1000, // ms per step
945 data: []
946 };
947 this.heatmapMode = 'density';
948 this.tooltip = null;
949 this.initialized = false;
950 }
951
952 init() {
953 if (this.initialized) return;
954 console.log('Initializing EnhancedMemoryVisualizer...');
955
956 this.initTooltip();
957 this.init3DVisualization();
958 this.initTimelineControls();
959 this.initHeatmap();
960 this.bindEvents();
961
962 this.initialized = true;
963 console.log('EnhancedMemoryVisualizer initialized successfully');
964 }
965
966 initTooltip() {
967 this.tooltip = document.createElement('div');
968 this.tooltip.className = 'memory-tooltip';
969 this.tooltip.style.display = 'none';
970 document.body.appendChild(this.tooltip);
971 }
972
973 init3DVisualization() {
974 const container = document.getElementById('memory3DContainer');
975 if (!container) return;
976
977 // Scene setup with dark gradient background
978 this.scene = new THREE.Scene();
979 this.scene.background = new THREE.Color(0x1a1a2e);
980
981 // Camera setup - closer view for better data inspection
982 this.camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
983 this.camera.position.set(15, 10, 15);
984
985 // Renderer setup with enhanced settings
986 this.renderer = new THREE.WebGLRenderer({
987 antialias: true,
988 alpha: true,
989 powerPreference: "high-performance"
990 });
991 this.renderer.setSize(container.clientWidth, container.clientHeight);
992 this.renderer.shadowMap.enabled = true;
993 this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
994 this.renderer.setClearColor(0x1a1a2e, 1);
995 this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
996 container.appendChild(this.renderer.domElement);
997
998 // Controls setup with enhanced interaction
999 try {
1000 if (typeof THREE !== 'undefined') {
1001 const OrbitControls = THREE.OrbitControls || window.OrbitControls;
1002 if (OrbitControls) {
1003 this.controls = new OrbitControls(this.camera, this.renderer.domElement);
1004 this.controls.enableDamping = true;
1005 this.controls.dampingFactor = 0.05;
1006 this.controls.enableZoom = true;
1007 this.controls.enablePan = true;
1008 this.controls.enableRotate = true;
1009 this.controls.autoRotate = false;
1010 this.controls.autoRotateSpeed = 0.5;
1011 this.controls.minDistance = 2; // Allow closer inspection
1012 this.controls.maxDistance = 100;
1013 this.controls.maxPolarAngle = Math.PI;
1014 this.controls.minPolarAngle = 0;
1015 } else {
1016 console.warn('OrbitControls not available, setting up manual controls');
1017 this.setupManualControls();
1018 }
1019 }
1020 } catch (error) {
1021 console.warn('Failed to initialize OrbitControls:', error);
1022 }
1023
1024 // Enhanced lighting setup
1025 const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
1026 this.scene.add(ambientLight);
1027
1028 const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
1029 directionalLight.position.set(50, 50, 25);
1030 directionalLight.castShadow = true;
1031 directionalLight.shadow.mapSize.width = 2048;
1032 directionalLight.shadow.mapSize.height = 2048;
1033 directionalLight.shadow.camera.near = 0.5;
1034 directionalLight.shadow.camera.far = 500;
1035 this.scene.add(directionalLight);
1036
1037 // Add subtle rim lighting
1038 const rimLight = new THREE.DirectionalLight(0x4a90e2, 0.3);
1039 rimLight.position.set(-50, 20, -25);
1040 this.scene.add(rimLight);
1041
1042 // Add point light for dynamic effects
1043 this.pointLight = new THREE.PointLight(0x4a90e2, 0.5, 100);
1044 this.pointLight.position.set(0, 20, 0);
1045 this.scene.add(this.pointLight);
1046
1047 // Create subtle floor plane instead of grid
1048 const floorGeometry = new THREE.PlaneGeometry(100, 100);
1049 const floorMaterial = new THREE.MeshLambertMaterial({
1050 color: 0x2a2a3e,
1051 transparent: true,
1052 opacity: 0.3
1053 });
1054 this.floor = new THREE.Mesh(floorGeometry, floorMaterial);
1055 this.floor.rotation.x = -Math.PI / 2;
1056 this.floor.position.y = -0.1;
1057 this.floor.receiveShadow = true;
1058 this.scene.add(this.floor);
1059
1060 // Initialize animation properties
1061 this.animationTime = 0;
1062 this.isAutoRotating = false;
1063
1064 this.animate3D();
1065 }
1066
1067 setupManualControls() {
1068 if (!this.renderer || !this.camera) return;
1069
1070 const canvas = this.renderer.domElement;
1071 let isMouseDown = false;
1072 let mouseX = 0, mouseY = 0;
1073 let cameraDistance = 20;
1074 let cameraAngleX = 0;
1075 let cameraAngleY = 0;
1076
1077 // Mouse controls for rotation
1078 canvas.addEventListener('mousedown', (event) => {
1079 isMouseDown = true;
1080 mouseX = event.clientX;
1081 mouseY = event.clientY;
1082 canvas.style.cursor = 'grabbing';
1083 });
1084
1085 canvas.addEventListener('mousemove', (event) => {
1086 if (!isMouseDown) return;
1087
1088 const deltaX = event.clientX - mouseX;
1089 const deltaY = event.clientY - mouseY;
1090
1091 cameraAngleY += deltaX * 0.01;
1092 cameraAngleX += deltaY * 0.01;
1093
1094 // Limit vertical rotation
1095 cameraAngleX = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, cameraAngleX));
1096
1097 // Update camera position
1098 this.camera.position.x = Math.cos(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
1099 this.camera.position.y = Math.sin(cameraAngleX) * cameraDistance;
1100 this.camera.position.z = Math.sin(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
1101
1102 this.camera.lookAt(0, 0, 0);
1103
1104 mouseX = event.clientX;
1105 mouseY = event.clientY;
1106 });
1107
1108 canvas.addEventListener('mouseup', () => {
1109 isMouseDown = false;
1110 canvas.style.cursor = 'grab';
1111 });
1112
1113 canvas.addEventListener('mouseleave', () => {
1114 isMouseDown = false;
1115 canvas.style.cursor = 'default';
1116 });
1117
1118 // Zoom with mouse wheel
1119 canvas.addEventListener('wheel', (event) => {
1120 event.preventDefault();
1121
1122 const zoomSpeed = 0.1;
1123 cameraDistance += event.deltaY * zoomSpeed;
1124 cameraDistance = Math.max(2, Math.min(100, cameraDistance));
1125
1126 // Update camera position
1127 this.camera.position.x = Math.cos(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
1128 this.camera.position.y = Math.sin(cameraAngleX) * cameraDistance;
1129 this.camera.position.z = Math.sin(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
1130
1131 this.camera.lookAt(0, 0, 0);
1132 });
1133
1134 // Touch controls for mobile
1135 let lastTouchDistance = 0;
1136
1137 canvas.addEventListener('touchstart', (event) => {
1138 if (event.touches.length === 1) {
1139 isMouseDown = true;
1140 mouseX = event.touches[0].clientX;
1141 mouseY = event.touches[0].clientY;
1142 } else if (event.touches.length === 2) {
1143 const touch1 = event.touches[0];
1144 const touch2 = event.touches[1];
1145 lastTouchDistance = Math.sqrt(
1146 Math.pow(touch2.clientX - touch1.clientX, 2) +
1147 Math.pow(touch2.clientY - touch1.clientY, 2)
1148 );
1149 }
1150 });
1151
1152 canvas.addEventListener('touchmove', (event) => {
1153 event.preventDefault();
1154
1155 if (event.touches.length === 1 && isMouseDown) {
1156 const deltaX = event.touches[0].clientX - mouseX;
1157 const deltaY = event.touches[0].clientY - mouseY;
1158
1159 cameraAngleY += deltaX * 0.01;
1160 cameraAngleX += deltaY * 0.01;
1161 cameraAngleX = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, cameraAngleX));
1162
1163 this.camera.position.x = Math.cos(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
1164 this.camera.position.y = Math.sin(cameraAngleX) * cameraDistance;
1165 this.camera.position.z = Math.sin(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
1166
1167 this.camera.lookAt(0, 0, 0);
1168
1169 mouseX = event.touches[0].clientX;
1170 mouseY = event.touches[0].clientY;
1171 } else if (event.touches.length === 2) {
1172 const touch1 = event.touches[0];
1173 const touch2 = event.touches[1];
1174 const touchDistance = Math.sqrt(
1175 Math.pow(touch2.clientX - touch1.clientX, 2) +
1176 Math.pow(touch2.clientY - touch1.clientY, 2)
1177 );
1178
1179 if (lastTouchDistance > 0) {
1180 const zoomDelta = (lastTouchDistance - touchDistance) * 0.01;
1181 cameraDistance += zoomDelta;
1182 cameraDistance = Math.max(2, Math.min(100, cameraDistance));
1183
1184 this.camera.position.x = Math.cos(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
1185 this.camera.position.y = Math.sin(cameraAngleX) * cameraDistance;
1186 this.camera.position.z = Math.sin(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
1187
1188 this.camera.lookAt(0, 0, 0);
1189 }
1190
1191 lastTouchDistance = touchDistance;
1192 }
1193 });
1194
1195 canvas.addEventListener('touchend', () => {
1196 isMouseDown = false;
1197 lastTouchDistance = 0;
1198 });
1199
1200 canvas.style.cursor = 'grab';
1201 console.log('✅ Manual 3D controls initialized (mouse drag to rotate, wheel to zoom)');
1202 }
1203
1204 animate3D() {
1205 requestAnimationFrame(() => this.animate3D());
1206
1207 this.animationTime += 0.01;
1208
1209 // Animate point light for dynamic lighting
1210 if (this.pointLight) {
1211 this.pointLight.position.x = Math.sin(this.animationTime) * 30;
1212 this.pointLight.position.z = Math.cos(this.animationTime) * 30;
1213 this.pointLight.intensity = 0.3 + Math.sin(this.animationTime * 2) * 0.2;
1214 }
1215
1216 // Animate memory blocks with subtle floating effect
1217 this.memoryBlocks.forEach((block, index) => {
1218 if (block && block.position) {
1219 const originalY = block.userData.originalY || block.position.y;
1220 block.position.y = originalY + Math.sin(this.animationTime + index * 0.1) * 0.2;
1221
1222 // Add subtle rotation for leaked memory blocks
1223 if (block.userData.is_leaked) {
1224 block.rotation.y += 0.02;
1225 }
1226 }
1227 });
1228
1229 // Update controls
1230 if (this.controls) {
1231 this.controls.update();
1232 }
1233
1234 // Render scene
1235 if (this.renderer && this.scene && this.camera) {
1236 this.renderer.render(this.scene, this.camera);
1237 }
1238 }
1239
1240 create3DMemoryBlocks(allocations) {
1241 if (!this.scene || !allocations) return;
1242
1243 // Clear existing blocks with fade out animation
1244 this.memoryBlocks.forEach(block => {
1245 if (block && block.material) {
1246 // Fade out animation
1247 const fadeOut = () => {
1248 block.material.opacity -= 0.05;
1249 if (block.material.opacity <= 0) {
1250 this.scene.remove(block);
1251 if (block.geometry) block.geometry.dispose();
1252 if (block.material) block.material.dispose();
1253 } else {
1254 requestAnimationFrame(fadeOut);
1255 }
1256 };
1257 fadeOut();
1258 }
1259 });
1260 this.memoryBlocks = [];
1261
1262 // Sort allocations by size for better visual hierarchy
1263 const sortedAllocs = [...allocations].sort((a, b) => (b.size || 0) - (a.size || 0));
1264
1265 const maxBlocksPerRow = 15;
1266 const spacing = 4;
1267
1268 sortedAllocs.forEach((alloc, index) => {
1269 const size = Math.max(alloc.size || 1, 1);
1270 const blockSize = Math.cbrt(size / 50) + 0.8; // Enhanced size calculation
1271
1272 // Spiral positioning for better visual distribution
1273 const angle = index * 0.5;
1274 const radius = Math.sqrt(index) * 2;
1275 const x = Math.cos(angle) * radius;
1276 const z = Math.sin(angle) * radius;
1277 const y = blockSize / 2;
1278
1279 // Enhanced color scheme based on type and size
1280 let color = 0x3b82f6;
1281 let emissive = 0x000000;
1282 const typeName = alloc.type_name || '';
1283
1284 if (typeName.includes('String')) {
1285 color = 0x4a90e2;
1286 emissive = 0x001122;
1287 } else if (typeName.includes('Box')) {
1288 color = 0xe74c3c;
1289 emissive = 0x220011;
1290 } else if (typeName.includes('Rc')) {
1291 color = 0x2ecc71;
1292 emissive = 0x001100;
1293 } else if (typeName.includes('Arc')) {
1294 color = 0x9b59b6;
1295 emissive = 0x110022;
1296 } else if (typeName.includes('Vec')) {
1297 color = 0xf39c12;
1298 emissive = 0x221100;
1299 }
1300
1301 // Create enhanced geometry with rounded edges
1302 const geometry = new THREE.BoxGeometry(blockSize, blockSize, blockSize);
1303
1304 // Enhanced material with better visual effects
1305 const material = new THREE.MeshPhongMaterial({
1306 color: color,
1307 emissive: emissive,
1308 transparent: true,
1309 opacity: alloc.is_leaked ? 0.7 : 0.9,
1310 shininess: 100,
1311 specular: 0x222222
1312 });
1313
1314 const cube = new THREE.Mesh(geometry, material);
1315 cube.position.set(x, y, z);
1316 cube.castShadow = true;
1317 cube.receiveShadow = true;
1318
1319 // Store original position and allocation data
1320 cube.userData = {
1321 ...alloc,
1322 originalY: y,
1323 originalColor: color,
1324 originalEmissive: emissive
1325 };
1326
1327 // Add entrance animation
1328 cube.scale.set(0, 0, 0);
1329 const targetScale = 1;
1330 const animateEntrance = () => {
1331 cube.scale.x += (targetScale - cube.scale.x) * 0.1;
1332 cube.scale.y += (targetScale - cube.scale.y) * 0.1;
1333 cube.scale.z += (targetScale - cube.scale.z) * 0.1;
1334
1335 if (Math.abs(cube.scale.x - targetScale) > 0.01) {
1336 requestAnimationFrame(animateEntrance);
1337 }
1338 };
1339 setTimeout(() => animateEntrance(), index * 50);
1340
1341 // Add hover effects
1342 cube.addEventListener = (event, handler) => {
1343 // Custom event handling for 3D objects
1344 };
1345
1346 this.scene.add(cube);
1347 this.memoryBlocks.push(cube);
1348 });
1349
1350 this.update3DInfo(allocations.length);
1351 this.setupRaycasting();
1352 }
1353
1354 setupRaycasting() {
1355 if (!this.renderer || !this.camera) return;
1356
1357 this.raycaster = new THREE.Raycaster();
1358 this.mouse = new THREE.Vector2();
1359 this.hoveredObject = null;
1360
1361 const canvas = this.renderer.domElement;
1362
1363 canvas.addEventListener('mousemove', (event) => {
1364 const rect = canvas.getBoundingClientRect();
1365 this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
1366 this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
1367
1368 this.raycaster.setFromCamera(this.mouse, this.camera);
1369 const intersects = this.raycaster.intersectObjects(this.memoryBlocks);
1370
1371 // Reset previous hover
1372 if (this.hoveredObject) {
1373 this.hoveredObject.material.emissive.setHex(this.hoveredObject.userData.originalEmissive);
1374 this.hoveredObject.scale.set(1, 1, 1);
1375 }
1376
1377 if (intersects.length > 0) {
1378 this.hoveredObject = intersects[0].object;
1379 // Highlight hovered object
1380 this.hoveredObject.material.emissive.setHex(0x444444);
1381 this.hoveredObject.scale.set(1.2, 1.2, 1.2);
1382
1383 // Show tooltip
1384 this.show3DTooltip(event, this.hoveredObject.userData);
1385 canvas.style.cursor = 'pointer';
1386 } else {
1387 this.hoveredObject = null;
1388 this.hide3DTooltip();
1389 canvas.style.cursor = 'default';
1390 }
1391 });
1392
1393 canvas.addEventListener('click', (event) => {
1394 if (this.hoveredObject) {
1395 this.selectMemoryBlock(this.hoveredObject);
1396 }
1397 });
1398
1399 canvas.addEventListener('mouseleave', () => {
1400 if (this.hoveredObject) {
1401 this.hoveredObject.material.emissive.setHex(this.hoveredObject.userData.originalEmissive);
1402 this.hoveredObject.scale.set(1, 1, 1);
1403 this.hoveredObject = null;
1404 }
1405 this.hide3DTooltip();
1406 canvas.style.cursor = 'default';
1407 });
1408 }
1409
1410 selectMemoryBlock(block) {
1411 // Animate selection
1412 const originalScale = { x: 1, y: 1, z: 1 };
1413 const targetScale = { x: 1.5, y: 1.5, z: 1.5 };
1414
1415 const animateSelection = () => {
1416 block.scale.x += (targetScale.x - block.scale.x) * 0.2;
1417 block.scale.y += (targetScale.y - block.scale.y) * 0.2;
1418 block.scale.z += (targetScale.z - block.scale.z) * 0.2;
1419
1420 if (Math.abs(block.scale.x - targetScale.x) > 0.01) {
1421 requestAnimationFrame(animateSelection);
1422 } else {
1423 // Return to normal size after selection
1424 setTimeout(() => {
1425 const returnToNormal = () => {
1426 block.scale.x += (originalScale.x - block.scale.x) * 0.2;
1427 block.scale.y += (originalScale.y - block.scale.y) * 0.2;
1428 block.scale.z += (originalScale.z - block.scale.z) * 0.2;
1429
1430 if (Math.abs(block.scale.x - originalScale.x) > 0.01) {
1431 requestAnimationFrame(returnToNormal);
1432 }
1433 };
1434 returnToNormal();
1435 }, 1000);
1436 }
1437 };
1438 animateSelection();
1439
1440 // Show detailed info
1441 this.showDetailedInfo(block.userData);
1442 }
1443
1444 show3DTooltip(event, alloc) {
1445 if (!this.tooltip) return;
1446
1447 this.tooltip.innerHTML = `
1448 <div style="font-weight: bold; color: #4a90e2; margin-bottom: 4px;">${alloc.var_name || 'Unknown'}</div>
1449 <div style="margin-bottom: 2px;"><span style="color: #888;">Type:</span> ${alloc.type_name || 'Unknown'}</div>
1450 <div style="margin-bottom: 2px;"><span style="color: #888;">Size:</span> ${this.formatBytes(alloc.size || 0)}</div>
1451 <div style="margin-bottom: 2px;"><span style="color: #888;">Address:</span> 0x${(alloc.ptr || 0).toString(16)}</div>
1452 <div style="margin-bottom: 2px;"><span style="color: #888;">Lifetime:</span> ${(alloc.lifetime_ms || 0).toFixed(2)}ms</div>
1453 <div style="color: ${alloc.is_leaked ? '#e74c3c' : '#2ecc71'};">
1454 ${alloc.is_leaked ? '⚠️ Leaked' : '✅ Active'}
1455 </div>
1456 `;
1457
1458 this.tooltip.style.display = 'block';
1459 this.tooltip.style.left = `${event.pageX + 15}px`;
1460 this.tooltip.style.top = `${event.pageY - 10}px`;
1461 }
1462
1463 hide3DTooltip() {
1464 if (this.tooltip) {
1465 this.tooltip.style.display = 'none';
1466 }
1467 }
1468
1469 showDetailedInfo(alloc) {
1470 const infoEl = document.getElementById('memory3DInfo');
1471 if (infoEl) {
1472 infoEl.innerHTML = `
1473 <div style="font-weight: bold; color: #4a90e2; margin-bottom: 8px;">Selected: ${alloc.var_name}</div>
1474 <div style="font-size: 0.8rem; line-height: 1.4;">
1475 <div>Type: ${alloc.type_name}</div>
1476 <div>Size: ${this.formatBytes(alloc.size || 0)}</div>
1477 <div>Address: 0x${(alloc.ptr || 0).toString(16)}</div>
1478 <div>Scope: ${alloc.scope_name || 'unknown'}</div>
1479 <div>Lifetime: ${(alloc.lifetime_ms || 0).toFixed(2)}ms</div>
1480 <div>Status: ${alloc.is_leaked ? 'Leaked' : 'Active'}</div>
1481 </div>
1482 `;
1483 }
1484 }
1485
1486 update3DInfo(blockCount) {
1487 const infoEl = document.getElementById('memory3DInfo');
1488 if (infoEl) {
1489 infoEl.innerHTML = `
1490 Memory Blocks: ${blockCount}<br>
1491 Camera: [${this.camera.position.x.toFixed(1)}, ${this.camera.position.y.toFixed(1)}, ${this.camera.position.z.toFixed(1)}]<br>
1492 Use mouse to rotate, zoom, and pan
1493 `;
1494 }
1495 }
1496
1497 initTimelineControls() {
1498 console.log('Initializing timeline controls...');
1499
1500 const playBtn = document.getElementById('timelinePlay');
1501 const pauseBtn = document.getElementById('timelinePause');
1502 const resetBtn = document.getElementById('timelineReset');
1503 const stepBtn = document.getElementById('timelineStep');
1504 const slider = document.getElementById('timelineSlider');
1505 const thumb = document.getElementById('timelineThumb');
1506
1507 console.log('Found timeline buttons:', {
1508 playBtn: !!playBtn,
1509 pauseBtn: !!pauseBtn,
1510 resetBtn: !!resetBtn,
1511 stepBtn: !!stepBtn,
1512 slider: !!slider,
1513 thumb: !!thumb
1514 });
1515
1516 if (playBtn) {
1517 playBtn.addEventListener('click', () => {
1518 console.log('Timeline play button clicked');
1519 this.playTimeline();
1520 });
1521 console.log('Play button event bound');
1522 } else {
1523 console.error('timelinePlay button not found');
1524 }
1525
1526 if (pauseBtn) {
1527 pauseBtn.addEventListener('click', () => {
1528 console.log('Timeline pause button clicked');
1529 this.pauseTimeline();
1530 });
1531 console.log('Pause button event bound');
1532 } else {
1533 console.error('timelinePause button not found');
1534 }
1535
1536 if (resetBtn) {
1537 resetBtn.addEventListener('click', () => {
1538 console.log('Timeline reset button clicked');
1539 this.resetTimeline();
1540 });
1541 console.log('Reset button event bound');
1542 } else {
1543 console.error('timelineReset button not found');
1544 }
1545
1546 if (stepBtn) {
1547 stepBtn.addEventListener('click', () => {
1548 console.log('Timeline step button clicked');
1549 this.stepTimeline();
1550 });
1551 console.log('Step button event bound');
1552 } else {
1553 console.error('timelineStep button not found');
1554 }
1555
1556 if (slider && thumb) {
1557 let isDragging = false;
1558
1559 thumb.addEventListener('mousedown', (e) => {
1560 isDragging = true;
1561 e.preventDefault();
1562 });
1563
1564 document.addEventListener('mousemove', (e) => {
1565 if (!isDragging) return;
1566
1567 const rect = slider.getBoundingClientRect();
1568 const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
1569 const percentage = x / rect.width;
1570
1571 this.setTimelinePosition(percentage);
1572 });
1573
1574 document.addEventListener('mouseup', () => {
1575 isDragging = false;
1576 });
1577
1578 slider.addEventListener('click', (e) => {
1579 if (isDragging) return;
1580
1581 const rect = slider.getBoundingClientRect();
1582 const x = e.clientX - rect.left;
1583 const percentage = x / rect.width;
1584
1585 this.setTimelinePosition(percentage);
1586 });
1587 }
1588 }
1589
1590 formatBytes(bytes) {
1591 if (bytes === 0) return '0 Bytes';
1592 const k = 1024;
1593 const sizes = ['Bytes', 'KB', 'MB', 'GB'];
1594 const i = Math.floor(Math.log(bytes) / Math.log(k));
1595 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
1596 }
1597
1598 prepareTimelineData(allocations) {
1599 if (!allocations || allocations.length === 0) {
1600 this.timeline.data = [];
1601 this.timeline.totalTime = 0;
1602 this.updateTimelineDisplay();
1603 return;
1604 }
1605
1606 // Filter out allocations with invalid timestamps and sort
1607 const validAllocs = allocations.filter(alloc =>
1608 alloc.timestamp_alloc &&
1609 !isNaN(alloc.timestamp_alloc) &&
1610 alloc.timestamp_alloc > 0
1611 );
1612
1613 if (validAllocs.length === 0) {
1614 // If no valid timestamps, create synthetic timeline based on order
1615 this.timeline.data = allocations.map((alloc, index) => ({
1616 ...alloc,
1617 timestamp_alloc: index * 1000000, // 1ms intervals
1618 timestamp_dealloc: alloc.timestamp_dealloc || (index + 1) * 1000000
1619 }));
1620 this.timeline.totalTime = allocations.length * 1000000;
1621 } else {
1622 // Sort by allocation timestamp
1623 const sortedAllocs = [...validAllocs].sort((a, b) => {
1624 const timeA = a.timestamp_alloc || 0;
1625 const timeB = b.timestamp_alloc || 0;
1626 return timeA - timeB;
1627 });
1628
1629 this.timeline.data = sortedAllocs;
1630 const minTime = sortedAllocs[0].timestamp_alloc || 0;
1631 const maxTime = sortedAllocs[sortedAllocs.length - 1].timestamp_alloc || 0;
1632 this.timeline.totalTime = Math.max(maxTime - minTime, 1000000); // At least 1ms
1633 }
1634
1635 this.updateTimelineDisplay();
1636 }
1637
1638 playTimeline() {
1639 console.log('Starting timeline playback...');
1640 if (this.timeline.isPlaying) {
1641 console.log('Timeline already playing');
1642 return;
1643 }
1644
1645 console.log('Timeline data:', {
1646 totalTime: this.timeline.totalTime,
1647 currentTime: this.timeline.currentTime,
1648 dataLength: this.timeline.data.length
1649 });
1650
1651 this.timeline.isPlaying = true;
1652 const playBtn = document.getElementById('timelinePlay');
1653 const pauseBtn = document.getElementById('timelinePause');
1654
1655 if (playBtn) playBtn.disabled = true;
1656 if (pauseBtn) pauseBtn.disabled = false;
1657
1658 console.log('Timeline playback started');
1659
1660 // Get speed from control
1661 const speedSelect = document.getElementById('timelineSpeed');
1662 const speed = speedSelect ? parseFloat(speedSelect.value) : 1.0;
1663
1664 // Use requestAnimationFrame for smoother animation
1665 const animate = () => {
1666 if (!this.timeline.isPlaying) return;
1667
1668 // Calculate time increment based on speed and total time for better visualization
1669 const baseIncrement = Math.max(this.timeline.totalTime * 0.0005, 12500); // 0.05% of total time or 12.5μs minimum
1670 this.timeline.currentTime += baseIncrement * speed;
1671
1672 if (this.timeline.currentTime >= this.timeline.totalTime) {
1673 this.timeline.currentTime = this.timeline.totalTime;
1674 this.updateTimelineVisualization();
1675 this.pauseTimeline();
1676 console.log('Timeline playback completed');
1677 return;
1678 }
1679
1680 this.updateTimelineVisualization();
1681
1682 // Continue animation with consistent frame rate
1683 const frameDelay = Math.max(16, 200 / speed); // Slower frame rate for better visualization
1684 setTimeout(() => {
1685 if (this.timeline.isPlaying) {
1686 requestAnimationFrame(animate);
1687 }
1688 }, frameDelay);
1689 };
1690
1691 requestAnimationFrame(animate);
1692 }
1693
1694 pauseTimeline() {
1695 this.timeline.isPlaying = false;
1696
1697 const playBtn = document.getElementById('timelinePlay');
1698 const pauseBtn = document.getElementById('timelinePause');
1699
1700 if (playBtn) playBtn.disabled = false;
1701 if (pauseBtn) pauseBtn.disabled = true;
1702
1703 if (this.timelineInterval) {
1704 clearInterval(this.timelineInterval);
1705 this.timelineInterval = null;
1706 }
1707 }
1708
1709 resetTimeline() {
1710 console.log('Resetting timeline...');
1711 this.pauseTimeline();
1712 this.timeline.currentTime = 0;
1713 this.updateTimelineVisualization();
1714
1715 // Reset 3D visualization to show all allocations
1716 if (window.analysisData && window.analysisData.memory_analysis) {
1717 this.create3DMemoryBlocks(window.analysisData.memory_analysis.allocations || []);
1718 }
1719
1720 console.log('Timeline reset to beginning');
1721 }
1722
1723 stepTimeline() {
1724 // Step forward by 1% of total time or 1ms, whichever is larger
1725 const stepSize = Math.max(this.timeline.totalTime * 0.01, 1000000); // 1ms minimum
1726 this.timeline.currentTime += stepSize;
1727 if (this.timeline.currentTime > this.timeline.totalTime) {
1728 this.timeline.currentTime = this.timeline.totalTime;
1729 }
1730 this.updateTimelineVisualization();
1731 console.log(`Timeline stepped to ${(this.timeline.currentTime / 1000000).toFixed(1)}ms`);
1732 }
1733
1734 setTimelinePosition(percentage) {
1735 this.timeline.currentTime = percentage * this.timeline.totalTime;
1736 this.updateTimelineVisualization();
1737 }
1738
1739 updateTimelineVisualization() {
1740 const percentage = this.timeline.totalTime > 0 ? this.timeline.currentTime / this.timeline.totalTime : 0;
1741
1742 // Cache DOM elements to avoid repeated queries
1743 if (!this.timelineElements) {
1744 this.timelineElements = {
1745 progress: document.getElementById('timelineProgress'),
1746 thumb: document.getElementById('timelineThumb'),
1747 currentTime: document.getElementById('timelineCurrentTime'),
1748 totalTime: document.getElementById('timelineTotalTime'),
1749 activeCount: document.getElementById('timelineActiveCount')
1750 };
1751 }
1752
1753 // Only update if percentage changed significantly (reduce flickering)
1754 const roundedPercentage = Math.round(percentage * 1000) / 10; // Round to 0.1%
1755 if (this.lastPercentage !== roundedPercentage) {
1756 this.lastPercentage = roundedPercentage;
1757
1758 if (this.timelineElements.progress) {
1759 this.timelineElements.progress.style.width = `${roundedPercentage}%`;
1760 }
1761 if (this.timelineElements.thumb) {
1762 this.timelineElements.thumb.style.left = `calc(${roundedPercentage}% - 9px)`;
1763 }
1764 }
1765
1766 // Update time display less frequently
1767 if (!this.lastTimeUpdate || Date.now() - this.lastTimeUpdate > 100) {
1768 this.lastTimeUpdate = Date.now();
1769
1770 const currentTimeMs = isNaN(this.timeline.currentTime) ? 0 : this.timeline.currentTime / 1000000;
1771 const totalTimeMs = isNaN(this.timeline.totalTime) ? 0 : this.timeline.totalTime / 1000000;
1772
1773 if (this.timelineElements.currentTime) {
1774 safeUpdateElement('timelineCurrentTime', `${currentTimeMs.toFixed(1)}ms`);
1775 }
1776 if (this.timelineElements.totalTime) {
1777 safeUpdateElement('timelineTotalTime', `${totalTimeMs.toFixed(1)}ms`);
1778 }
1779 }
1780
1781 // Count active allocations at current time
1782 const activeAllocs = this.timeline.data.filter(alloc => {
1783 const allocTime = alloc.timestamp_alloc || 0;
1784 const deallocTime = alloc.timestamp_dealloc || Infinity;
1785 const currentTime = this.timeline.data[0] ? (this.timeline.data[0].timestamp_alloc || 0) + this.timeline.currentTime : 0;
1786
1787 return allocTime <= currentTime && currentTime < deallocTime;
1788 });
1789
1790 if (this.timelineElements.activeCount) {
1791 safeUpdateElement('timelineActiveCount', activeAllocs.length);
1792 }
1793
1794 // Update 3D visualization with current active allocations
1795 this.create3DMemoryBlocks(activeAllocs);
1796 }
1797
1798 updateTimelineDisplay() {
1799 const totalTimeEl = document.getElementById('timelineTotalTime');
1800 const currentTimeEl = document.getElementById('timelineCurrentTime');
1801
1802 const totalTimeMs = isNaN(this.timeline.totalTime) ? 0 : this.timeline.totalTime / 1000000;
1803 const currentTimeMs = isNaN(this.timeline.currentTime) ? 0 : this.timeline.currentTime / 1000000;
1804
1805 safeUpdateElement('timelineTotalTime', `${totalTimeMs.toFixed(1)}ms`);
1806 safeUpdateElement('timelineCurrentTime', `${currentTimeMs.toFixed(1)}ms`);
1807 }
1808
1809 initHeatmap() {
1810 const container = document.getElementById('memoryHeatmap');
1811 const modeButtons = document.querySelectorAll('.heatmap-mode-btn');
1812
1813 modeButtons.forEach(btn => {
1814 btn.addEventListener('click', (e) => {
1815 modeButtons.forEach(b => b.classList.remove('active'));
1816 e.target.classList.add('active');
1817 this.heatmapMode = e.target.dataset.mode;
1818 this.updateHeatmap();
1819 });
1820 });
1821 }
1822
1823 generateHeatmap(allocations) {
1824 console.log('Generating enhanced heatmap with', allocations?.length || 0, 'allocations');
1825 if (!allocations) return;
1826
1827 const container = document.getElementById('memoryHeatmap');
1828 if (!container) return;
1829
1830 // Clear existing heatmap
1831 container.innerHTML = '<div class="heatmap-legend" id="heatmapLegend"></div>';
1832
1833 const width = container.clientWidth;
1834 const height = container.clientHeight - 40; // Account for legend
1835 const cellSize = 6; // Smaller cells for better resolution
1836 const cols = Math.floor(width / cellSize);
1837 const rows = Math.floor(height / cellSize);
1838
1839 // Create heatmap data based on mode
1840 let heatmapData = [];
1841 let metadata = {};
1842
1843 switch (this.heatmapMode) {
1844 case 'density':
1845 const densityResult = this.calculateDensityHeatmap(allocations, cols, rows);
1846 heatmapData = densityResult.data || densityResult;
1847 metadata = densityResult.metadata || {};
1848 break;
1849 case 'type':
1850 const typeResult = this.calculateTypeHeatmap(allocations, cols, rows);
1851 heatmapData = typeResult.data || typeResult;
1852 metadata = typeResult.metadata || {};
1853 break;
1854 case 'scope':
1855 const scopeResult = this.calculateScopeHeatmap(allocations, cols, rows);
1856 heatmapData = scopeResult.data || scopeResult;
1857 metadata = scopeResult.metadata || {};
1858 break;
1859 case 'activity':
1860 const activityResult = this.calculateActivityHeatmap(allocations, cols, rows);
1861 heatmapData = activityResult.data || activityResult;
1862 metadata = activityResult.metadata || {};
1863 break;
1864 case 'fragmentation':
1865 const fragResult = this.calculateFragmentationHeatmap(allocations, cols, rows);
1866 heatmapData = fragResult.data || [];
1867 metadata = fragResult.metadata || {};
1868 break;
1869 case 'lifetime':
1870 const lifetimeResult = this.calculateLifetimeHeatmap(allocations, cols, rows);
1871 heatmapData = lifetimeResult.data || [];
1872 metadata = lifetimeResult.metadata || {};
1873 break;
1874 }
1875
1876 // Render enhanced heatmap with smooth transitions
1877 const fragment = document.createDocumentFragment();
1878 for (let row = 0; row < rows; row++) {
1879 for (let col = 0; col < cols; col++) {
1880 const index = row * cols + col;
1881 const intensity = heatmapData[index] || 0;
1882
1883 if (intensity > 0.01) { // Only render visible cells for performance
1884 const cell = document.createElement('div');
1885 cell.style.position = 'absolute';
1886 cell.style.left = `${col * cellSize}px`;
1887 cell.style.top = `${row * cellSize + 40}px`;
1888 cell.style.width = `${cellSize}px`;
1889 cell.style.height = `${cellSize}px`;
1890 cell.style.backgroundColor = this.getHeatmapColor(intensity, this.heatmapMode);
1891 cell.style.opacity = Math.max(0.1, intensity);
1892 cell.style.transition = 'all 0.3s ease';
1893 cell.style.borderRadius = '1px';
1894
1895 // Add hover effects for interactivity
1896 cell.addEventListener('mouseenter', (e) => {
1897 e.target.style.transform = 'scale(1.2)';
1898 e.target.style.zIndex = '10';
1899 this.showHeatmapTooltip(e, intensity, metadata, row, col);
1900 });
1901
1902 cell.addEventListener('mouseleave', (e) => {
1903 e.target.style.transform = 'scale(1)';
1904 e.target.style.zIndex = '1';
1905 this.hideHeatmapTooltip();
1906 });
1907
1908 fragment.appendChild(cell);
1909 }
1910 }
1911 }
1912
1913 container.appendChild(fragment);
1914 this.updateHeatmapLegend(metadata);
1915 }
1916
1917 calculateDensityHeatmap(allocations, cols, rows) {
1918 const data = new Array(cols * rows).fill(0);
1919 const maxSize = Math.max(...allocations.map(a => a.size || 0));
1920
1921 allocations.forEach((alloc, index) => {
1922 const x = index % cols;
1923 const y = Math.floor(index / cols) % rows;
1924 const cellIndex = y * cols + x;
1925
1926 if (cellIndex < data.length) {
1927 data[cellIndex] += (alloc.size || 0) / maxSize;
1928 }
1929 });
1930
1931 return data;
1932 }
1933
1934 calculateTypeHeatmap(allocations, cols, rows) {
1935 const data = new Array(cols * rows).fill(0);
1936 const typeMap = new Map();
1937
1938 allocations.forEach(alloc => {
1939 const type = alloc.type_name || 'unknown';
1940 typeMap.set(type, (typeMap.get(type) || 0) + 1);
1941 });
1942
1943 const maxCount = Math.max(...typeMap.values());
1944 let index = 0;
1945
1946 for (const [type, count] of typeMap.entries()) {
1947 const intensity = count / maxCount;
1948 const startIndex = index * Math.floor(data.length / typeMap.size);
1949 const endIndex = Math.min(startIndex + Math.floor(data.length / typeMap.size), data.length);
1950
1951 for (let i = startIndex; i < endIndex; i++) {
1952 data[i] = intensity;
1953 }
1954 index++;
1955 }
1956
1957 return data;
1958 }
1959
1960 calculateScopeHeatmap(allocations, cols, rows) {
1961 const data = new Array(cols * rows).fill(0);
1962 const scopeMap = new Map();
1963
1964 allocations.forEach(alloc => {
1965 const scope = alloc.scope || 'global';
1966 scopeMap.set(scope, (scopeMap.get(scope) || 0) + 1);
1967 });
1968
1969 const maxCount = Math.max(...scopeMap.values());
1970 let index = 0;
1971
1972 for (const [scope, count] of scopeMap.entries()) {
1973 const intensity = count / maxCount;
1974 const startIndex = index * Math.floor(data.length / scopeMap.size);
1975 const endIndex = Math.min(startIndex + Math.floor(data.length / scopeMap.size), data.length);
1976
1977 for (let i = startIndex; i < endIndex; i++) {
1978 data[i] = intensity;
1979 }
1980 index++;
1981 }
1982
1983 return data;
1984 }
1985
1986 calculateActivityHeatmap(allocations, cols, rows) {
1987 const data = new Array(cols * rows).fill(0);
1988
1989 if (allocations.length === 0) return { data, metadata: {} };
1990
1991 const minTime = Math.min(...allocations.map(a => a.timestamp_alloc || 0));
1992 const maxTime = Math.max(...allocations.map(a => a.timestamp_alloc || 0));
1993 const timeRange = maxTime - minTime || 1;
1994
1995 allocations.forEach(alloc => {
1996 const timeRatio = ((alloc.timestamp_alloc || 0) - minTime) / timeRange;
1997 const cellIndex = Math.floor(timeRatio * data.length);
1998
1999 if (cellIndex < data.length) {
2000 data[cellIndex] += 0.1;
2001 }
2002 });
2003
2004 const maxActivity = Math.max(...data);
2005 const normalizedData = maxActivity > 0 ? data.map(d => d / maxActivity) : data;
2006
2007 return {
2008 data: normalizedData,
2009 metadata: {
2010 maxActivity,
2011 totalAllocations: allocations.length,
2012 timeRange: timeRange / 1000000 // Convert to ms
2013 }
2014 };
2015 }
2016
2017 calculateFragmentationHeatmap(allocations, cols, rows) {
2018 const data = new Array(cols * rows).fill(0);
2019
2020 // Sort allocations by memory address
2021 const sortedAllocs = allocations
2022 .filter(a => a.ptr && a.size)
2023 .map(a => ({
2024 address: parseInt(a.ptr.replace('0x', ''), 16),
2025 size: a.size,
2026 ...a
2027 }))
2028 .sort((a, b) => a.address - b.address);
2029
2030 // Calculate fragmentation score for each memory region
2031 for (let i = 0; i < sortedAllocs.length - 1; i++) {
2032 const current = sortedAllocs[i];
2033 const next = sortedAllocs[i + 1];
2034 const gap = next.address - (current.address + current.size);
2035
2036 if (gap > 0) {
2037 // Map to heatmap coordinates
2038 const normalizedAddr = (current.address % (cols * rows * 1000)) / (cols * rows * 1000);
2039 const row = Math.floor(normalizedAddr * rows);
2040 const col = Math.floor((i / sortedAllocs.length) * cols);
2041 const cellIndex = Math.min(row * cols + col, data.length - 1);
2042
2043 // Higher gap = higher fragmentation
2044 data[cellIndex] += Math.min(gap / 1000, 1); // Normalize gap size
2045 }
2046 }
2047
2048 const maxFrag = Math.max(...data);
2049 const normalizedData = maxFrag > 0 ? data.map(d => d / maxFrag) : data;
2050
2051 return {
2052 data: normalizedData,
2053 metadata: {
2054 maxFragmentation: maxFrag,
2055 totalGaps: data.filter(d => d > 0).length,
2056 avgFragmentation: data.reduce((a, b) => a + b, 0) / data.length
2057 }
2058 };
2059 }
2060
2061 calculateLifetimeHeatmap(allocations, cols, rows) {
2062 const data = new Array(cols * rows).fill(0);
2063
2064 allocations.forEach((alloc, index) => {
2065 const allocTime = alloc.timestamp_alloc || 0;
2066 const deallocTime = alloc.timestamp_dealloc || Date.now() * 1000000;
2067 const lifetime = deallocTime - allocTime;
2068
2069 // Map lifetime to heatmap position
2070 const row = Math.floor((index / allocations.length) * rows);
2071 const col = Math.floor((lifetime / 1000000000) * cols) % cols; // Convert to seconds
2072 const cellIndex = Math.min(row * cols + col, data.length - 1);
2073
2074 data[cellIndex] += 1;
2075 });
2076
2077 const maxLifetime = Math.max(...data);
2078 const normalizedData = maxLifetime > 0 ? data.map(d => d / maxLifetime) : data;
2079
2080 return {
2081 data: normalizedData,
2082 metadata: {
2083 maxLifetime,
2084 avgLifetime: data.reduce((a, b) => a + b, 0) / data.length,
2085 activeAllocations: allocations.filter(a => !a.timestamp_dealloc).length
2086 }
2087 };
2088 }
2089
2090 getHeatmapColor(intensity, mode = 'density') {
2091 const scaledIntensity = Math.min(Math.max(intensity, 0), 1);
2092
2093 // Different color schemes for different modes
2094 const colorSchemes = {
2095 density: [
2096 [59, 130, 246], // Blue
2097 [245, 158, 11], // Orange
2098 [220, 38, 38] // Red
2099 ],
2100 type: [
2101 [34, 197, 94], // Green
2102 [168, 85, 247], // Purple
2103 [239, 68, 68] // Red
2104 ],
2105 scope: [
2106 [14, 165, 233], // Sky blue
2107 [251, 191, 36], // Amber
2108 [239, 68, 68] // Red
2109 ],
2110 activity: [
2111 [99, 102, 241], // Indigo
2112 [236, 72, 153], // Pink
2113 [220, 38, 38] // Red
2114 ],
2115 fragmentation: [
2116 [34, 197, 94], // Green (low fragmentation)
2117 [251, 191, 36], // Amber (medium)
2118 [239, 68, 68] // Red (high fragmentation)
2119 ],
2120 lifetime: [
2121 [147, 51, 234], // Purple (short-lived)
2122 [59, 130, 246], // Blue (medium)
2123 [34, 197, 94] // Green (long-lived)
2124 ]
2125 };
2126
2127 const colors = colorSchemes[mode] || colorSchemes.density;
2128 const colorIndex = scaledIntensity * (colors.length - 1);
2129 const lowerIndex = Math.floor(colorIndex);
2130 const upperIndex = Math.ceil(colorIndex);
2131 const ratio = colorIndex - lowerIndex;
2132
2133 if (lowerIndex === upperIndex) {
2134 const [r, g, b] = colors[lowerIndex];
2135 return `rgb(${r}, ${g}, ${b})`;
2136 }
2137
2138 const [r1, g1, b1] = colors[lowerIndex];
2139 const [r2, g2, b2] = colors[upperIndex];
2140
2141 const r = Math.round(r1 + (r2 - r1) * ratio);
2142 const g = Math.round(g1 + (g2 - g1) * ratio);
2143 const b = Math.round(b1 + (b2 - b1) * ratio);
2144
2145 return `rgb(${r}, ${g}, ${b})`;
2146 }
2147
2148 showHeatmapTooltip(event, intensity, metadata, row, col) {
2149 if (!this.tooltip) {
2150 this.tooltip = document.createElement('div');
2151 this.tooltip.style.cssText = `
2152 position: absolute;
2153 background: rgba(0, 0, 0, 0.9);
2154 color: white;
2155 padding: 8px 12px;
2156 border-radius: 6px;
2157 font-size: 12px;
2158 pointer-events: none;
2159 z-index: 1000;
2160 max-width: 200px;
2161 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
2162 `;
2163 document.body.appendChild(this.tooltip);
2164 }
2165
2166 const modeDescriptions = {
2167 density: 'Memory usage density',
2168 type: 'Type distribution',
2169 scope: 'Scope activity level',
2170 activity: 'Allocation activity',
2171 fragmentation: 'Memory fragmentation',
2172 lifetime: 'Allocation lifetime'
2173 };
2174
2175 this.tooltip.innerHTML = `
2176 <div><strong>${modeDescriptions[this.heatmapMode] || 'Intensity'}</strong></div>
2177 <div>Value: ${(intensity * 100).toFixed(1)}%</div>
2178 <div>Position: (${col}, ${row})</div>
2179 ${metadata.maxActivity ? `<div>Max Activity: ${metadata.maxActivity}</div>` : ''}
2180 ${metadata.totalGaps ? `<div>Total Gaps: ${metadata.totalGaps}</div>` : ''}
2181 ${metadata.activeAllocations ? `<div>Active: ${metadata.activeAllocations}</div>` : ''}
2182 `;
2183
2184 this.tooltip.style.left = `${event.pageX + 10}px`;
2185 this.tooltip.style.top = `${event.pageY - 10}px`;
2186 this.tooltip.style.display = 'block';
2187 }
2188
2189 hideHeatmapTooltip() {
2190 if (this.tooltip) {
2191 this.tooltip.style.display = 'none';
2192 }
2193 }
2194
2195 updateHeatmapLegend(metadata = {}) {
2196 const legend = document.getElementById('heatmapLegend');
2197 if (!legend) return;
2198
2199 const modeLabels = {
2200 density: 'Memory Density',
2201 type: 'Type Distribution',
2202 scope: 'Scope Activity',
2203 activity: 'Allocation Activity',
2204 fragmentation: 'Memory Fragmentation',
2205 lifetime: 'Allocation Lifetime'
2206 };
2207
2208 const modeDescriptions = {
2209 density: 'Shows memory usage concentration',
2210 type: 'Shows distribution of data types',
2211 scope: 'Shows activity by scope',
2212 activity: 'Shows allocation frequency over time',
2213 fragmentation: 'Shows memory fragmentation levels',
2214 lifetime: 'Shows allocation lifetime patterns'
2215 };
2216
2217 const currentMode = this.heatmapMode;
2218 const lowColor = this.getHeatmapColor(0.2, currentMode);
2219 const medColor = this.getHeatmapColor(0.5, currentMode);
2220 const highColor = this.getHeatmapColor(0.8, currentMode);
2221
2222 let metadataHtml = '';
2223 if (metadata.maxActivity) {
2224 metadataHtml += `<div style="font-size: 10px; color: rgba(255,255,255,0.8);">Max Activity: ${metadata.maxActivity}</div>`;
2225 }
2226 if (metadata.totalGaps) {
2227 metadataHtml += `<div style="font-size: 10px; color: rgba(255,255,255,0.8);">Gaps: ${metadata.totalGaps}</div>`;
2228 }
2229 if (metadata.activeAllocations) {
2230 metadataHtml += `<div style="font-size: 10px; color: rgba(255,255,255,0.8);">Active: ${metadata.activeAllocations}</div>`;
2231 }
2232
2233 legend.innerHTML = `
2234 <div style="font-weight: 600; margin-bottom: 2px;">${modeLabels[currentMode]}</div>
2235 <div style="font-size: 10px; color: rgba(255,255,255,0.7); margin-bottom: 4px;">${modeDescriptions[currentMode]}</div>
2236 <div style="display: flex; align-items: center; gap: 4px; margin-bottom: 4px;">
2237 <div style="width: 12px; height: 12px; background: ${lowColor}; border-radius: 2px;"></div>
2238 <span style="font-size: 11px;">Low</span>
2239 <div style="width: 12px; height: 12px; background: ${medColor}; border-radius: 2px;"></div>
2240 <span style="font-size: 11px;">Med</span>
2241 <div style="width: 12px; height: 12px; background: ${highColor}; border-radius: 2px;"></div>
2242 <span style="font-size: 11px;">High</span>
2243 </div>
2244 ${metadataHtml}
2245 `;
2246 }
2247
2248 updateHeatmap() {
2249 if (window.analysisData && window.analysisData.memory_analysis && window.analysisData.memory_analysis.allocations) {
2250 this.generateHeatmap(window.analysisData.memory_analysis.allocations);
2251 }
2252 }
2253
2254 bindEvents() {
2255 console.log('🔧 Binding 3D visualization events...');
2256
2257 // Add visual feedback for button interactions
2258 this.addButtonFeedback();
2259
2260 // Wait for DOM to be fully ready
2261 setTimeout(() => {
2262 const toggle3DBtn = document.getElementById('toggle3DView');
2263 const reset3DBtn = document.getElementById('reset3DView');
2264 const autoRotateBtn = document.getElementById('autoRotate3D');
2265 const focusLargestBtn = document.getElementById('focusLargest');
2266
2267 console.log('🔍 Found buttons:', {
2268 toggle3DBtn: !!toggle3DBtn,
2269 reset3DBtn: !!reset3DBtn,
2270 autoRotateBtn: !!autoRotateBtn,
2271 focusLargestBtn: !!focusLargestBtn
2272 });
2273
2274 if (toggle3DBtn) {
2275 // Remove any existing event listeners
2276 toggle3DBtn.replaceWith(toggle3DBtn.cloneNode(true));
2277 const newToggle3DBtn = document.getElementById('toggle3DView');
2278
2279 newToggle3DBtn.addEventListener('click', (e) => {
2280 e.preventDefault();
2281 console.log('🎯 Toggle 3D view clicked');
2282 const container = document.getElementById('memory3DContainer');
2283 if (container) {
2284 const isHidden = container.style.display === 'none';
2285 if (isHidden) {
2286 // Show 3D view
2287 container.style.display = 'block';
2288 newToggle3DBtn.innerHTML = '<i class="fa fa-eye-slash"></i><span>Hide 3D</span>';
2289 newToggle3DBtn.style.background = 'var(--primary-red)';
2290 console.log('✅ Showing 3D view');
2291
2292 // Reinitialize 3D scene if needed
2293 if (!this.scene) {
2294 console.log('🔄 Reinitializing 3D scene...');
2295 this.init3DVisualization();
2296 }
2297
2298 // Update 3D visualization with current data
2299 if (window.analysisData && window.analysisData.memory_analysis) {
2300 this.create3DMemoryBlocks(window.analysisData.memory_analysis.allocations || []);
2301 }
2302 } else {
2303 // Hide 3D view
2304 container.style.display = 'none';
2305 newToggle3DBtn.innerHTML = '<i class="fa fa-eye"></i><span>Show 3D</span>';
2306 newToggle3DBtn.style.background = 'var(--primary-green)';
2307 console.log('✅ Hiding 3D view');
2308 }
2309 } else {
2310 console.error('❌ 3D container not found');
2311 }
2312 });
2313 console.log('✅ Toggle 3D button event bound');
2314 } else {
2315 console.error('❌ toggle3DView button not found');
2316 }
2317
2318 if (reset3DBtn) {
2319 // Remove any existing event listeners
2320 reset3DBtn.replaceWith(reset3DBtn.cloneNode(true));
2321 const newReset3DBtn = document.getElementById('reset3DView');
2322
2323 newReset3DBtn.addEventListener('click', (e) => {
2324 e.preventDefault();
2325 console.log('🎯 Reset 3D view clicked');
2326 this.reset3DView();
2327 });
2328 console.log('✅ Reset 3D button event bound');
2329 } else {
2330 console.error('❌ reset3DView button not found');
2331 }
2332
2333 if (autoRotateBtn) {
2334 // Remove any existing event listeners
2335 autoRotateBtn.replaceWith(autoRotateBtn.cloneNode(true));
2336 const newAutoRotateBtn = document.getElementById('autoRotate3D');
2337
2338 newAutoRotateBtn.addEventListener('click', (e) => {
2339 e.preventDefault();
2340 console.log('🎯 Auto rotate clicked');
2341 this.toggleAutoRotate();
2342 });
2343 console.log('✅ Auto rotate button event bound');
2344 } else {
2345 console.error('❌ autoRotate3D button not found');
2346 }
2347
2348 if (focusLargestBtn) {
2349 // Remove any existing event listeners
2350 focusLargestBtn.replaceWith(focusLargestBtn.cloneNode(true));
2351 const newFocusLargestBtn = document.getElementById('focusLargest');
2352
2353 newFocusLargestBtn.addEventListener('click', (e) => {
2354 e.preventDefault();
2355 console.log('🎯 Focus largest clicked');
2356 this.focusOnLargestBlock();
2357 });
2358 console.log('✅ Focus largest button event bound');
2359 } else {
2360 console.error('❌ focusLargest button not found');
2361 }
2362 }, 500); // Wait 500ms for DOM to be ready
2363
2364 // Handle window resize
2365 window.addEventListener('resize', () => {
2366 if (this.camera && this.renderer) {
2367 const container = document.getElementById('memory3DContainer');
2368 if (container) {
2369 this.camera.aspect = container.clientWidth / container.clientHeight;
2370 this.camera.updateProjectionMatrix();
2371 this.renderer.setSize(container.clientWidth, container.clientHeight);
2372 }
2373 }
2374 });
2375 }
2376
2377 addButtonFeedback() {
2378 // Add hover and click effects to all 3D control buttons
2379 const buttonIds = ['toggle3DView', 'reset3DView', 'autoRotate3D', 'focusLargest'];
2380
2381 buttonIds.forEach(id => {
2382 const btn = document.getElementById(id);
2383 if (btn) {
2384 // Add hover effect
2385 btn.addEventListener('mouseenter', () => {
2386 btn.style.transform = 'scale(1.05)';
2387 btn.style.transition = 'all 0.2s ease';
2388 });
2389
2390 btn.addEventListener('mouseleave', () => {
2391 btn.style.transform = 'scale(1)';
2392 });
2393
2394 // Add click effect
2395 btn.addEventListener('mousedown', () => {
2396 btn.style.transform = 'scale(0.95)';
2397 });
2398
2399 btn.addEventListener('mouseup', () => {
2400 btn.style.transform = 'scale(1.05)';
2401 });
2402
2403 console.log(`✅ Added feedback effects to ${id}`);
2404 }
2405 });
2406 }
2407
2408 reset3DView() {
2409 console.log('🔄 Resetting 3D view...');
2410
2411 // Show visual feedback
2412 const resetBtn = document.getElementById('reset3DView');
2413 if (resetBtn) {
2414 resetBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i><span>Resetting...</span>';
2415 resetBtn.style.background = 'var(--primary-yellow)';
2416 }
2417
2418 if (this.camera && this.controls) {
2419 // Reset camera position
2420 this.camera.position.set(15, 10, 15);
2421 this.camera.lookAt(0, 0, 0);
2422
2423 // Reset controls
2424 this.controls.reset();
2425
2426 // Update camera
2427 this.camera.updateProjectionMatrix();
2428
2429 // Restore button
2430 setTimeout(() => {
2431 if (resetBtn) {
2432 resetBtn.innerHTML = '<i class="fa fa-refresh"></i><span>Reset</span>';
2433 resetBtn.style.background = 'var(--primary-orange)';
2434 }
2435 }, 500);
2436
2437 console.log('✅ 3D view reset complete');
2438 } else {
2439 console.error('❌ Camera or controls not available for reset');
2440 if (resetBtn) {
2441 resetBtn.innerHTML = '<i class="fa fa-exclamation"></i><span>Error</span>';
2442 resetBtn.style.background = 'var(--primary-red)';
2443 setTimeout(() => {
2444 resetBtn.innerHTML = '<i class="fa fa-refresh"></i><span>Reset</span>';
2445 resetBtn.style.background = 'var(--primary-orange)';
2446 }, 1000);
2447 }
2448 }
2449
2450 // Animation function
2451 const animateReset = () => {
2452 // Animation logic here if needed
2453 };
2454 animateReset();
2455 }
2456
2457 toggleAutoRotate() {
2458 console.log('🔄 Toggling auto rotate...');
2459 if (this.controls) {
2460 this.controls.autoRotate = !this.controls.autoRotate;
2461 this.controls.autoRotateSpeed = 2.0; // Set rotation speed
2462
2463 const btn = document.getElementById('autoRotate3D');
2464 if (btn) {
2465 if (this.controls.autoRotate) {
2466 btn.innerHTML = '<i class="fa fa-pause"></i><span>Stop Rotate</span>';
2467 btn.style.background = 'var(--primary-red)';
2468 console.log('✅ Auto rotate enabled');
2469 } else {
2470 btn.innerHTML = '<i class="fa fa-rotate-right"></i><span>Auto Rotate</span>';
2471 btn.style.background = 'var(--primary-blue)';
2472 console.log('✅ Auto rotate disabled');
2473 }
2474 }
2475 } else {
2476 console.error('❌ Controls not available for auto rotate');
2477 const btn = document.getElementById('autoRotate3D');
2478 if (btn) {
2479 btn.innerHTML = '<i class="fa fa-exclamation"></i><span>Error</span>';
2480 btn.style.background = 'var(--primary-red)';
2481 setTimeout(() => {
2482 btn.innerHTML = '<i class="fa fa-rotate-right"></i><span>Auto Rotate</span>';
2483 btn.style.background = 'var(--primary-blue)';
2484 }, 1000);
2485 }
2486 }
2487 }
2488
2489 focusOnLargestBlock() {
2490 console.log('🎯 Focusing on largest block...');
2491
2492 // Show visual feedback
2493 const focusBtn = document.getElementById('focusLargest');
2494 if (focusBtn) {
2495 focusBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i><span>Focusing...</span>';
2496 focusBtn.style.background = 'var(--primary-yellow)';
2497 }
2498
2499 if (!this.memoryBlocks || this.memoryBlocks.length === 0) {
2500 console.warn('❌ No memory blocks to focus on');
2501 if (focusBtn) {
2502 focusBtn.innerHTML = '<i class="fa fa-exclamation"></i><span>No Blocks</span>';
2503 focusBtn.style.background = 'var(--primary-red)';
2504 setTimeout(() => {
2505 focusBtn.innerHTML = '<i class="fa fa-search-plus"></i><span>Focus Largest</span>';
2506 focusBtn.style.background = 'var(--primary-red)';
2507 }, 1500);
2508 }
2509 return;
2510 }
2511
2512 // Find the largest block
2513 let largestBlock = null;
2514 let largestSize = 0;
2515
2516 this.memoryBlocks.forEach(block => {
2517 const size = block.userData?.size || 0;
2518 if (size > largestSize) {
2519 largestSize = size;
2520 largestBlock = block;
2521 }
2522 });
2523
2524 if (largestBlock && this.camera && this.controls) {
2525 // Calculate optimal camera position
2526 const blockPos = largestBlock.position;
2527 const distance = Math.max(5, Math.sqrt(largestSize) / 10);
2528
2529 // Position camera at an angle for better view
2530 const targetPosition = new THREE.Vector3(
2531 blockPos.x + distance,
2532 blockPos.y + distance * 0.7,
2533 blockPos.z + distance
2534 );
2535
2536 // Smooth camera transition
2537 const startPos = this.camera.position.clone();
2538 let progress = 0;
2539
2540 const animateFocus = () => {
2541 progress += 0.05;
2542 if (progress <= 1) {
2543 this.camera.position.lerpVectors(startPos, targetPosition, progress);
2544 this.camera.lookAt(blockPos);
2545 requestAnimationFrame(animateFocus);
2546 } else {
2547 // Animation complete
2548 console.log(`✅ Focused on largest block: ${largestBlock.userData?.var_name || 'unknown'} (${this.formatBytes(largestSize)})`);
2549 this.update3DInfo(this.memoryBlocks.length);
2550
2551 // Restore button
2552 if (focusBtn) {
2553 focusBtn.innerHTML = '<i class="fa fa-search-plus"></i><span>Focus Largest</span>';
2554 focusBtn.style.background = 'var(--primary-red)';
2555 }
2556 }
2557 };
2558 animateFocus();
2559 } else {
2560 console.error('❌ Camera or controls not available for focus');
2561 if (focusBtn) {
2562 focusBtn.innerHTML = '<i class="fa fa-exclamation"></i><span>Error</span>';
2563 focusBtn.style.background = 'var(--primary-red)';
2564 setTimeout(() => {
2565 focusBtn.innerHTML = '<i class="fa fa-search-plus"></i><span>Focus Largest</span>';
2566 focusBtn.style.background = 'var(--primary-red)';
2567 }, 1000);
2568 }
2569 }
2570 }
2571
2572 // Main initialization method
2573 initializeWithData(analysisData) {
2574 console.log('initializeWithData called with:', analysisData);
2575
2576 let allocations = null;
2577
2578 // Try different data structure paths
2579 if (analysisData && analysisData.memory_analysis && analysisData.memory_analysis.allocations) {
2580 allocations = analysisData.memory_analysis.allocations;
2581 console.log('Found allocations in memory_analysis:', allocations.length);
2582 } else if (analysisData && analysisData.allocations) {
2583 allocations = analysisData.allocations;
2584 console.log('Found allocations directly:', allocations.length);
2585 } else {
2586 console.warn('No allocation data found in analysisData');
2587 console.log('Available keys:', Object.keys(analysisData || {}));
2588 return;
2589 }
2590
2591 if (!allocations || allocations.length === 0) {
2592 console.warn('No allocations to visualize');
2593 return;
2594 }
2595
2596 console.log(`Initializing enhanced visualization with ${allocations.length} allocations`);
2597
2598 // Initialize 3D visualization
2599 this.create3DMemoryBlocks(allocations);
2600
2601 // Initialize timeline
2602 this.prepareTimelineData(allocations);
2603
2604 // Initialize heatmap
2605 this.generateHeatmap(allocations);
2606
2607 // Update memory distribution visualization
2608 this.updateMemoryDistribution(allocations);
2609
2610 // Initialize memory fragmentation visualization
2611 this.initializeMemoryFragmentation(allocations);
2612
2613 console.log('Enhanced memory visualization initialized successfully');
2614 }
2615
2616 initializeMemoryFragmentation(allocations) {
2617 console.log('Initializing memory fragmentation with', allocations?.length || 0, 'allocations');
2618 const container = document.getElementById('memoryFragmentation');
2619 if (!container) {
2620 console.error('Memory fragmentation container not found');
2621 return;
2622 }
2623 if (!allocations || allocations.length === 0) {
2624 console.warn('No allocations data for fragmentation analysis');
2625 container.innerHTML = '<div style="text-align: center; color: var(--text-secondary); padding: 40px;">No allocation data available for fragmentation analysis</div>';
2626 return;
2627 }
2628
2629 console.log('Processing', allocations.length, 'allocations for fragmentation analysis');
2630
2631 // Calculate fragmentation metrics
2632 const sortedAllocs = [...allocations].sort((a, b) => {
2633 const addrA = parseInt(a.ptr || '0x0', 16);
2634 const addrB = parseInt(b.ptr || '0x0', 16);
2635 return addrA - addrB;
2636 });
2637
2638 const totalMemory = allocations.reduce((sum, alloc) => sum + (alloc.size || 0), 0);
2639 const addressRanges = [];
2640 let gaps = 0;
2641 let totalGapSize = 0;
2642
2643 // Calculate memory gaps
2644 for (let i = 0; i < sortedAllocs.length - 1; i++) {
2645 const currentAddr = parseInt(sortedAllocs[i].ptr || '0x0', 16);
2646 const currentSize = sortedAllocs[i].size || 0;
2647 const nextAddr = parseInt(sortedAllocs[i + 1].ptr || '0x0', 16);
2648
2649 const currentEnd = currentAddr + currentSize;
2650 const gap = nextAddr - currentEnd;
2651
2652 if (gap > 0) {
2653 gaps++;
2654 totalGapSize += gap;
2655 addressRanges.push({
2656 type: 'gap',
2657 start: currentEnd,
2658 size: gap,
2659 index: i
2660 });
2661 }
2662
2663 addressRanges.push({
2664 type: 'allocation',
2665 start: currentAddr,
2666 size: currentSize,
2667 allocation: sortedAllocs[i],
2668 index: i
2669 });
2670 }
2671
2672 // Calculate fragmentation percentage
2673 const fragmentation = totalMemory > 0 ? (totalGapSize / (totalMemory + totalGapSize)) * 100 : 0;
2674 const efficiency = Math.max(100 - fragmentation, 0);
2675
2676 console.log('Fragmentation metrics:', { gaps, totalGapSize, fragmentation, efficiency });
2677
2678 // Create visualization
2679 container.innerHTML = `
2680 <div style="margin-bottom: 16px;">
2681 <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 16px;">
2682 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
2683 <div style="font-size: 1.4rem; font-weight: 700; color: var(--primary-red);">${fragmentation.toFixed(1)}%</div>
2684 <div style="font-size: 0.7rem; color: var(--text-secondary);">Fragmentation</div>
2685 </div>
2686 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
2687 <div style="font-size: 1.4rem; font-weight: 700; color: var(--primary-orange);">${gaps}</div>
2688 <div style="font-size: 0.7rem; color: var(--text-secondary);">Memory Gaps</div>
2689 </div>
2690 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
2691 <div style="font-size: 1.4rem; font-weight: 700; color: var(--primary-green);">${efficiency.toFixed(1)}%</div>
2692 <div style="font-size: 0.7rem; color: var(--text-secondary);">Efficiency</div>
2693 </div>
2694 </div>
2695
2696 <div style="margin-bottom: 12px;">
2697 <h4 style="margin: 0 0 8px 0; font-size: 0.9rem; color: var(--text-primary);">Memory Layout Visualization</h4>
2698 <div id="fragmentationChart" style="height: 120px; background: var(--bg-secondary); border: 1px solid var(--border-light); border-radius: 6px; position: relative; overflow: hidden;">
2699 <!-- Memory blocks will be inserted here -->
2700 </div>
2701 </div>
2702
2703 <div style="font-size: 0.8rem; color: var(--text-secondary); text-align: center;">
2704 <div style="display: flex; justify-content: center; gap: 16px; margin-top: 8px;">
2705 <div style="display: flex; align-items: center; gap: 4px;">
2706 <div style="width: 12px; height: 12px; background: var(--primary-blue); border-radius: 2px;"></div>
2707 <span>Allocated</span>
2708 </div>
2709 <div style="display: flex; align-items: center; gap: 4px;">
2710 <div style="width: 12px; height: 12px; background: var(--primary-red); border-radius: 2px;"></div>
2711 <span>Gaps</span>
2712 </div>
2713 <div style="display: flex; align-items: center; gap: 4px;">
2714 <div style="width: 12px; height: 12px; background: var(--primary-orange); border-radius: 2px;"></div>
2715 <span>Leaked</span>
2716 </div>
2717 </div>
2718 </div>
2719 </div>
2720 `;
2721
2722 // Draw memory layout visualization
2723 this.drawFragmentationChart(addressRanges, totalMemory + totalGapSize);
2724 }
2725
2726 drawFragmentationChart(addressRanges, totalSize) {
2727 const chartContainer = document.getElementById('fragmentationChart');
2728 if (!chartContainer || addressRanges.length === 0) return;
2729
2730 const width = chartContainer.clientWidth;
2731 const height = chartContainer.clientHeight;
2732
2733 let currentX = 0;
2734
2735 addressRanges.forEach((range, index) => {
2736 const blockWidth = Math.max((range.size / totalSize) * width, 1);
2737
2738 const block = document.createElement('div');
2739 block.style.position = 'absolute';
2740 block.style.left = `${currentX}px`;
2741 block.style.top = '10px';
2742 block.style.width = `${blockWidth}px`;
2743 block.style.height = `${height - 20}px`;
2744 block.style.borderRadius = '2px';
2745 block.style.cursor = 'pointer';
2746 block.style.transition = 'all 0.2s ease';
2747
2748 if (range.type === 'gap') {
2749 block.style.background = 'linear-gradient(45deg, #dc2626, #ef4444)';
2750 block.style.border = '1px solid #b91c1c';
2751 block.title = `Memory Gap: ${this.formatBytes(range.size)}`;
2752 } else if (range.allocation && range.allocation.is_leaked) {
2753 block.style.background = 'linear-gradient(45deg, #ea580c, #f97316)';
2754 block.style.border = '1px solid #c2410c';
2755 block.title = `Leaked: ${range.allocation.var_name} (${this.formatBytes(range.size)})`;
2756 } else {
2757 block.style.background = 'linear-gradient(45deg, #2563eb, #3b82f6)';
2758 block.style.border = '1px solid #1d4ed8';
2759 block.title = range.allocation ?
2760 `${range.allocation.var_name}: ${range.allocation.type_name} (${this.formatBytes(range.size)})` :
2761 `Allocation: ${this.formatBytes(range.size)}`;
2762 }
2763
2764 // Add hover effects
2765 block.addEventListener('mouseenter', () => {
2766 block.style.transform = 'scaleY(1.2)';
2767 block.style.zIndex = '10';
2768 });
2769
2770 block.addEventListener('mouseleave', () => {
2771 block.style.transform = 'scaleY(1)';
2772 block.style.zIndex = '1';
2773 });
2774
2775 chartContainer.appendChild(block);
2776 currentX += blockWidth;
2777 });
2778 }
2779
2780 updateMemoryDistribution(allocations) {
2781 const container = document.getElementById('memoryDistributionViz');
2782 if (!container || !allocations) return;
2783
2784 container.innerHTML = '';
2785
2786 const containerWidth = container.clientWidth;
2787 const containerHeight = container.clientHeight;
2788 const totalMemory = allocations.reduce((sum, alloc) => sum + (alloc.size || 0), 0);
2789
2790 // 获取当前缩放比例
2791 const scaleSlider = document.getElementById('memoryDistScale');
2792 const scale = scaleSlider ? parseFloat(scaleSlider.value) / 100 : 1;
2793
2794 // 计算实际可用宽度(考虑缩放和边距)
2795 const padding = 20;
2796 const availableWidth = (containerWidth - padding * 2) * scale;
2797 const availableHeight = containerHeight - padding * 2;
2798
2799 // 创建一个可滚动的内容容器
2800 const contentContainer = document.createElement('div');
2801 contentContainer.style.cssText = `
2802 position: absolute;
2803 top: ${padding}px;
2804 left: ${padding}px;
2805 width: ${Math.max(availableWidth, containerWidth - padding * 2)}px;
2806 height: ${availableHeight}px;
2807 overflow-x: auto;
2808 overflow-y: hidden;
2809 `;
2810
2811 // 创建内存块容器
2812 const blocksContainer = document.createElement('div');
2813 blocksContainer.style.cssText = `
2814 position: relative;
2815 width: ${availableWidth}px;
2816 height: 100%;
2817 min-width: ${containerWidth - padding * 2}px;
2818 `;
2819
2820 let currentX = 0;
2821 let fragmentation = 0;
2822 let efficiency = 0;
2823
2824 // Sort by address for fragmentation calculation
2825 const sortedAllocs = [...allocations].sort((a, b) => {
2826 const addrA = parseInt(a.ptr || '0x0', 16);
2827 const addrB = parseInt(b.ptr || '0x0', 16);
2828 return addrA - addrB;
2829 });
2830
2831 // 计算每个块的最小宽度和总宽度需求
2832 const minBlockWidth = 3; // 最小块宽度
2833 const blockGap = 1;
2834 let totalRequiredWidth = 0;
2835
2836 sortedAllocs.forEach((alloc) => {
2837 const proportionalWidth = (alloc.size || 0) / totalMemory * availableWidth;
2838 const blockWidth = Math.max(proportionalWidth, minBlockWidth);
2839 totalRequiredWidth += blockWidth + blockGap;
2840 });
2841
2842 // 如果总宽度超过可用宽度,调整容器宽度
2843 const finalContainerWidth = Math.max(totalRequiredWidth, availableWidth);
2844 blocksContainer.style.width = `${finalContainerWidth}px`;
2845
2846 sortedAllocs.forEach((alloc, index) => {
2847 const proportionalWidth = (alloc.size || 0) / totalMemory * availableWidth;
2848 const blockWidth = Math.max(proportionalWidth, minBlockWidth);
2849 const blockHeight = availableHeight * 0.7;
2850
2851 const block = document.createElement('div');
2852 block.className = 'memory-block';
2853 block.style.cssText = `
2854 position: absolute;
2855 left: ${currentX}px;
2856 top: ${(availableHeight - blockHeight) / 2}px;
2857 width: ${blockWidth}px;
2858 height: ${blockHeight}px;
2859 border-radius: 2px;
2860 cursor: pointer;
2861 transition: all 0.2s ease;
2862 border: 1px solid rgba(255, 255, 255, 0.3);
2863 `;
2864
2865 // Determine allocation type and style
2866 if (alloc.is_leaked) {
2867 block.classList.add('leaked');
2868 block.style.background = 'linear-gradient(45deg, #dc2626, #ef4444)';
2869 block.style.boxShadow = '0 0 8px rgba(239, 68, 68, 0.5)';
2870 } else if (alloc.type_name && alloc.type_name.includes('Box')) {
2871 block.classList.add('heap');
2872 block.style.background = 'linear-gradient(45deg, #ff6b35, #f7931e)';
2873 } else {
2874 block.classList.add('stack');
2875 block.style.background = 'linear-gradient(45deg, #4dabf7, #339af0)';
2876 }
2877
2878 // Enhanced hover effects
2879 block.addEventListener('mouseenter', (e) => {
2880 block.style.transform = 'scaleY(1.2) translateY(-2px)';
2881 block.style.zIndex = '10';
2882 block.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
2883 this.showTooltip(e, alloc);
2884 });
2885
2886 block.addEventListener('mouseleave', () => {
2887 block.style.transform = 'scaleY(1) translateY(0)';
2888 block.style.zIndex = '1';
2889 block.style.boxShadow = 'none';
2890 this.hideTooltip();
2891 });
2892
2893 // 添加点击事件显示详细信息
2894 block.addEventListener('click', () => {
2895 this.showBlockDetails(alloc);
2896 });
2897
2898 blocksContainer.appendChild(block);
2899 currentX += blockWidth + blockGap;
2900 });
2901
2902 contentContainer.appendChild(blocksContainer);
2903 container.appendChild(contentContainer);
2904
2905 // Calculate fragmentation and efficiency
2906 fragmentation = totalRequiredWidth > availableWidth ?
2907 ((totalRequiredWidth - availableWidth) / totalRequiredWidth * 100) : 0;
2908 efficiency = Math.max(100 - fragmentation, 0);
2909
2910 // Update metrics
2911 const fragEl = document.getElementById('memoryFragmentation');
2912 const effEl = document.getElementById('memoryEfficiency');
2913
2914 // Use global safe update function (no need to redefine)
2915
2916 safeUpdateElement('memoryFragmentation', `${fragmentation.toFixed(1)}%`);
2917 safeUpdateElement('memoryEfficiency', `${efficiency.toFixed(1)}%`);
2918
2919 // Setup dynamic controls
2920 this.setupMemoryDistributionControls(allocations);
2921
2922 // Update other metrics with real data
2923 this.updateEnhancedMetrics(allocations);
2924 }
2925
2926 setupMemoryDistributionControls(allocations) {
2927 const scaleSlider = document.getElementById('memoryDistScale');
2928 const scaleValue = document.getElementById('memoryDistScaleValue');
2929 const fitBtn = document.getElementById('memoryDistFit');
2930 const resetBtn = document.getElementById('memoryDistReset');
2931
2932 if (scaleSlider && scaleValue) {
2933 scaleSlider.addEventListener('input', (e) => {
2934 const value = e.target.value;
2935 safeUpdateElement('memoryDistScaleValue', `${value}%`);
2936 this.updateMemoryDistribution(allocations);
2937 });
2938 }
2939
2940 if (fitBtn) {
2941 fitBtn.addEventListener('click', () => {
2942 // 自动计算最佳缩放比例
2943 const container = document.getElementById('memoryDistributionViz');
2944 if (container && allocations) {
2945 const containerWidth = container.clientWidth - 40; // 减去padding
2946 const totalMemory = allocations.reduce((sum, alloc) => sum + (alloc.size || 0), 0);
2947 const minBlockWidth = 3;
2948 const blockGap = 1;
2949
2950 let requiredWidth = 0;
2951 allocations.forEach(() => {
2952 requiredWidth += minBlockWidth + blockGap;
2953 });
2954
2955 const optimalScale = Math.min(200, Math.max(50, (containerWidth / requiredWidth) * 100));
2956
2957 if (scaleSlider) {
2958 scaleSlider.value = optimalScale;
2959 safeUpdateElement('memoryDistScaleValue', `${Math.round(optimalScale)}%`);
2960 this.updateMemoryDistribution(allocations);
2961 }
2962 }
2963 });
2964 }
2965
2966 if (resetBtn) {
2967 resetBtn.addEventListener('click', () => {
2968 if (scaleSlider) {
2969 scaleSlider.value = 100;
2970 safeUpdateElement('memoryDistScaleValue', '100%');
2971 this.updateMemoryDistribution(allocations);
2972 }
2973 });
2974 }
2975 }
2976
2977 showBlockDetails(alloc) {
2978 // 创建详细信息弹窗
2979 const modal = document.createElement('div');
2980 modal.style.cssText = `
2981 position: fixed;
2982 top: 0;
2983 left: 0;
2984 width: 100%;
2985 height: 100%;
2986 background: rgba(0,0,0,0.5);
2987 z-index: 1000;
2988 display: flex;
2989 align-items: center;
2990 justify-content: center;
2991 `;
2992
2993 const content = document.createElement('div');
2994 content.style.cssText = `
2995 background: var(--bg-primary);
2996 border-radius: 12px;
2997 padding: 24px;
2998 max-width: 400px;
2999 width: 90%;
3000 box-shadow: 0 20px 40px rgba(0,0,0,0.3);
3001 color: var(--text-primary);
3002 `;
3003
3004 content.innerHTML = `
3005 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
3006 <h3 style="margin: 0; color: var(--primary-blue);">Memory Block Details</h3>
3007 <button id="closeModal" style="background: none; border: none; font-size: 20px; cursor: pointer; color: var(--text-secondary);">×</button>
3008 </div>
3009 <div style="line-height: 1.6;">
3010 <div><strong>Variable:</strong> ${alloc.var_name || 'Unknown'}</div>
3011 <div><strong>Type:</strong> ${alloc.type_name || 'Unknown'}</div>
3012 <div><strong>Size:</strong> ${this.formatBytes(alloc.size || 0)}</div>
3013 <div><strong>Address:</strong> ${alloc.ptr || 'N/A'}</div>
3014 <div><strong>Status:</strong> ${alloc.is_leaked ? '🚨 Leaked' : '✅ Active'}</div>
3015 <div><strong>Lifetime:</strong> ${alloc.lifetime_ms ? alloc.lifetime_ms.toFixed(2) + 'ms' : 'N/A'}</div>
3016 <div><strong>Scope:</strong> ${alloc.scope_name || 'Unknown'}</div>
3017 </div>
3018 `;
3019
3020 modal.appendChild(content);
3021 document.body.appendChild(modal);
3022
3023 // 关闭事件
3024 const closeModal = () => {
3025 document.body.removeChild(modal);
3026 };
3027
3028 modal.addEventListener('click', (e) => {
3029 if (e.target === modal) closeModal();
3030 });
3031
3032 content.querySelector('#closeModal').addEventListener('click', closeModal);
3033 }
3034
3035 updateEnhancedMetrics(allocations) {
3036 if (!allocations || allocations.length === 0) return;
3037
3038 // Calculate real metrics from data
3039 const totalAllocs = allocations.length;
3040 const totalMemory = allocations.reduce((sum, alloc) => sum + (alloc.size || 0), 0);
3041 const avgLifetime = allocations.reduce((sum, alloc) => sum + (alloc.lifetime_ms || 0), 0) / totalAllocs;
3042 const heapAllocs = allocations.filter(a => a.type_name && (a.type_name.includes('Box') || a.type_name.includes('Vec'))).length;
3043 const stackAllocs = totalAllocs - heapAllocs;
3044 const heapStackRatio = stackAllocs > 0 ? (heapAllocs / stackAllocs).toFixed(2) : heapAllocs.toString();
3045
3046 // Update KPI cards
3047 const totalAllocsEl = document.getElementById('total-allocations');
3048 const activeVarsEl = document.getElementById('active-variables');
3049 const totalMemoryEl = document.getElementById('total-memory');
3050 const avgLifetimeEl = document.getElementById('avg-lifetime');
3051 const peakMemoryEl = document.getElementById('peak-memory');
3052 const allocRateEl = document.getElementById('allocation-rate');
3053 const fragmentationEl = document.getElementById('fragmentation');
3054
3055 safeUpdateElement('total-allocs', totalAllocs);
3056 safeUpdateElement('active-vars', allocations.filter(a => !a.is_leaked).length);
3057 safeUpdateElement('total-memory', this.formatBytes(totalMemory));
3058 safeUpdateElement('avg-lifetime', `${avgLifetime.toFixed(2)}ms`);
3059 safeUpdateElement('peak-memory', this.formatBytes(Math.max(...allocations.map(a => a.size || 0))));
3060
3061 // Calculate allocation rate (allocations per microsecond)
3062 const timeSpan = Math.max(...allocations.map(a => a.timestamp_alloc || 0)) - Math.min(...allocations.map(a => a.timestamp_alloc || 0));
3063 const allocRate = timeSpan > 0 ? ((totalAllocs / (timeSpan / 1000000)).toFixed(2) + '/sec') : '0/sec';
3064 safeUpdateElement('allocation-rate', allocRate);
3065
3066 // Update enhanced statistics
3067 const totalAllocsEnhancedEl = document.getElementById('total-allocs-enhanced');
3068 const heapStackRatioEl = document.getElementById('heap-stack-ratio');
3069 const avgLifetimeEnhancedEl = document.getElementById('avg-lifetime-enhanced');
3070 const memoryEfficiencyEl = document.getElementById('memory-efficiency');
3071
3072 safeUpdateElement('total-allocs-enhanced', totalAllocs);
3073 safeUpdateElement('heap-stack-ratio', heapStackRatio);
3074 safeUpdateElement('avg-lifetime-enhanced', `${avgLifetime.toFixed(1)}ms`);
3075 safeUpdateElement('memory-efficiency', `${((totalMemory / (totalAllocs * 100)) * 100).toFixed(1)}%`);
3076
3077 // Update type counts
3078 safeUpdateElement('arc-count', allocations.filter(a => a.type_name && a.type_name.includes('Arc')).length);
3079 safeUpdateElement('rc-count', allocations.filter(a => a.type_name && a.type_name.includes('Rc')).length);
3080 safeUpdateElement('collections-count', allocations.filter(a => a.type_name && (a.type_name.includes('Vec') || a.type_name.includes('HashMap'))).length);
3081 }
3082
3083 showTooltip(event, alloc) {
3084 if (!this.tooltip) return;
3085
3086 this.tooltip.innerHTML = `
3087 <strong>${alloc.var_name || 'Unknown'}</strong><br>
3088 Type: ${alloc.type_name || 'Unknown'}<br>
3089 Size: ${this.formatBytes(alloc.size || 0)}<br>
3090 Address: ${alloc.ptr || 'N/A'}<br>
3091 Status: ${alloc.is_leaked ? 'Leaked' : 'Active'}<br>
3092 Lifetime: ${alloc.lifetime_ms ? alloc.lifetime_ms.toFixed(2) + 'ms' : 'N/A'}
3093 `;
3094
3095 this.tooltip.style.display = 'block';
3096 this.tooltip.style.left = `${event.pageX + 10}px`;
3097 this.tooltip.style.top = `${event.pageY - 10}px`;
3098 }
3099
3100 hideTooltip() {
3101 if (this.tooltip) {
3102 this.tooltip.style.display = 'none';
3103 }
3104 }
3105
3106 formatBytes(bytes) {
3107 if (bytes === 0) return '0 B';
3108 const k = 1024;
3109 const sizes = ['B', 'KB', 'MB', 'GB'];
3110 const i = Math.floor(Math.log(bytes) / Math.log(k));
3111 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
3112 }
3113 }
3114
3115 // Global instance
3116 window.enhancedVisualizer = new EnhancedMemoryVisualizer();
3117
3118 // Global function to bind 3D controls - can be called from console for debugging
3119 window.bind3DControls = function() {
3120 console.log('🔧 Manually binding 3D controls...');
3121 if (window.enhancedVisualizer) {
3122 window.enhancedVisualizer.bindEvents();
3123 }
3124 };
3125
3126 // Ensure 3D controls are bound when DOM is ready
3127 // Safety Risk Data and Functions
3128 window.safetyRisks = [];
3129
3130 function getRiskAssessment(risk) {
3131 if (risk.risk_level === 'High') {
3132 return 'Critical memory safety issue - immediate attention required';
3133 } else if (risk.risk_level === 'Medium') {
3134 return 'Potential memory issue - review recommended';
3135 } else {
3136 return 'Low risk - monitoring suggested';
3137 }
3138 }
3139
3140 function loadSafetyRisks() {
3141 console.log('🛡️ Loading safety risk data...');
3142 const unsafeTable = document.getElementById('unsafeTable');
3143 if (!unsafeTable) {
3144 console.warn('⚠️ unsafeTable not found');
3145 return;
3146 }
3147
3148 const risks = window.safetyRisks || [];
3149 if (risks.length === 0) {
3150 unsafeTable.innerHTML = '<tr><td colspan="3" class="text-center text-gray-500">No safety risks detected</td></tr>';
3151 return;
3152 }
3153
3154 unsafeTable.innerHTML = '';
3155 risks.forEach((risk, index) => {
3156 const row = document.createElement('tr');
3157 row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700';
3158
3159 const riskLevelClass = risk.risk_level === 'High' ? 'text-red-600 font-bold' :
3160 risk.risk_level === 'Medium' ? 'text-yellow-600 font-semibold' :
3161 'text-green-600';
3162
3163 row.innerHTML = `
3164 <td class="px-3 py-2 text-sm">${risk.location || 'Unknown'}</td>
3165 <td class="px-3 py-2 text-sm">${risk.operation || 'Unknown'}</td>
3166 <td class="px-3 py-2 text-sm"><span class="${riskLevelClass}">${risk.risk_level || 'Low'}</span></td>
3167 `;
3168 unsafeTable.appendChild(row);
3169 });
3170
3171 console.log('✅ Safety risks loaded:', risks.length, 'items');
3172 }
3173
3174 document.addEventListener('DOMContentLoaded', function() {
3175 console.log('🚀 DOM loaded, binding 3D controls...');
3176 setTimeout(() => {
3177 if (window.enhancedVisualizer) {
3178 window.enhancedVisualizer.bindEvents();
3179 }
3180 }, 1000);
3181 });
3182
3183 // Enhanced Unsafe Rust & FFI Memory Analysis
3184 // Global variables for unsafe analysis - must be declared at global scope
3185 window.unsafeAnalysisCurrentFilter = 'critical';
3186 window.unsafeAnalysisData = []; // Initialize as empty array at global scope
3187 window.timelineZoomLevel = 1;
3188 window.timelineOffset = 0;
3189
3190 function initializeEnhancedUnsafeAnalysis() {
3191 console.log('🔧 Initializing Enhanced Unsafe Rust & FFI Memory Analysis...');
3192
3193 // Initialize unsafeAnalysisData if not already done
3194 if (!window.unsafeAnalysisData || window.unsafeAnalysisData.length === 0) {
3195 console.log('🔄 Initializing window.unsafeAnalysisData...');
3196 window.unsafeAnalysisData = [];
3197 }
3198
3199 // Load unsafe/FFI data from multiple sources
3200 const allocations = window.analysisData?.memory_analysis?.allocations || [];
3201 const unsafeFfiData = loadUnsafeFfiSnapshot();
3202
3203 // Transform and merge data for enhanced analysis
3204 try {
3205 window.unsafeAnalysisData = transformUnsafeAnalysisData(allocations, unsafeFfiData);
3206 console.log('✅ Successfully transformed unsafe analysis data:', window.unsafeAnalysisData.length, 'items');
3207 } catch (error) {
3208 console.error('❌ Error transforming unsafe analysis data:', error);
3209 window.unsafeAnalysisData = []; // Fallback to empty array
3210 }
3211
3212 updateUnsafeAnalysisStats(window.unsafeAnalysisData);
3213 setupEnhancedFilterControls();
3214 setupTimelineControls();
3215 setupMemoryPassportModal();
3216
3217 const filteredData = applyUnsafeAnalysisFilter(window.unsafeAnalysisCurrentFilter, window.unsafeAnalysisData);
3218 renderEnhancedUnsafeTimeline(filteredData);
3219
3220 console.log('✅ Enhanced Unsafe Analysis initialized with', window.unsafeAnalysisData.length, 'memory objects');
3221 }
3222
3223 function loadUnsafeFfiSnapshot() {
3224 // Load from the JSON data we saw earlier
3225 try {
3226 if (window.unsafeFfiSnapshot) {
3227 return window.unsafeFfiSnapshot;
3228 }
3229 // Fallback to simulated data based on the structure we observed
3230 return generateEnhancedUnsafeData();
3231 } catch (error) {
3232 console.warn('Failed to load unsafe FFI snapshot, using simulated data');
3233 return generateEnhancedUnsafeData();
3234 }
3235 }
3236
3237 function generateEnhancedUnsafeData() {
3238 // Generate realistic unsafe/FFI data based on the JSON structure we analyzed
3239 const data = [];
3240 for (let i = 0; i < 50; i++) {
3241 const ptr = `0x${(0x60000000 + i * 0x40).toString(16)}`;
3242 const source = Math.random() < 0.4 ? 'UnsafeRust' : 'FfiC';
3243 const hasLeaks = Math.random() < 0.15;
3244 const hasBoundaryEvents = Math.random() < 0.25;
3245
3246 data.push({
3247 base: {
3248 ptr: ptr,
3249 size: [40, 256, 1024, 4096][Math.floor(Math.random() * 4)],
3250 timestamp_alloc: Date.now() * 1000000 + i * 100000,
3251 timestamp_dealloc: hasLeaks ? null : Date.now() * 1000000 + i * 100000 + Math.random() * 5000000,
3252 is_leaked: hasLeaks
3253 },
3254 source: source === 'UnsafeRust' ? {
3255 UnsafeRust: {
3256 unsafe_block_location: `src/lib.rs:${42 + i}:13`,
3257 risk_assessment: {
3258 risk_level: ['Low', 'Medium', 'High'][Math.floor(Math.random() * 3)],
3259 confidence_score: 0.7 + Math.random() * 0.3,
3260 risk_factors: [{
3261 factor_type: 'ManualMemoryManagement',
3262 severity: 3 + Math.random() * 7,
3263 description: 'Manual memory management in unsafe block'
3264 }]
3265 }
3266 }
3267 } : {
3268 FfiC: {
3269 resolved_function: {
3270 library_name: 'libc',
3271 function_name: 'malloc',
3272 risk_level: 'Medium'
3273 }
3274 }
3275 },
3276 cross_boundary_events: hasBoundaryEvents ? [{
3277 event_type: Math.random() < 0.5 ? 'FfiToRust' : 'RustToFfi',
3278 timestamp: Date.now() + i * 1000,
3279 from_context: source === 'UnsafeRust' ? 'rust_context' : 'ffi_context',
3280 to_context: source === 'UnsafeRust' ? 'ffi_context' : 'rust_context'
3281 }] : [],
3282 ffi_tracked: source === 'FfiC' || Math.random() < 0.3
3283 });
3284 }
3285 return data;
3286 }
3287
3288 function transformUnsafeAnalysisData(allocations, unsafeFfiData) {
3289 const transformed = [];
3290
3291 // Transform regular allocations to unsafe analysis format
3292 allocations.forEach(alloc => {
3293 if (alloc.type_name && (alloc.type_name.includes('*') || alloc.type_name.includes('unsafe'))) {
3294 transformed.push({
3295 ...alloc,
3296 analysis_type: 'regular_allocation',
3297 risk_level: 'Low',
3298 has_boundary_events: false
3299 });
3300 }
3301 });
3302
3303 // Add enhanced unsafe/FFI data
3304 unsafeFfiData.forEach(unsafeItem => {
3305 transformed.push({
3306 ...unsafeItem,
3307 analysis_type: 'unsafe_ffi',
3308 risk_level: unsafeItem.source?.UnsafeRust?.risk_assessment?.risk_level || 'Medium',
3309 has_boundary_events: unsafeItem.cross_boundary_events && unsafeItem.cross_boundary_events.length > 0
3310 });
3311 });
3312
3313 return transformed;
3314 }
3315
3316 function updateUnsafeAnalysisStats(data) {
3317 const criticalCount = data.filter(d => d.risk_level === 'High' || d.base?.is_leaked).length;
3318 const leakCount = data.filter(d => d.base?.is_leaked).length;
3319 const boundaryCount = data.filter(d => d.has_boundary_events).length;
3320
3321 safeUpdateElement('unsafe-critical-count', criticalCount);
3322 safeUpdateElement('unsafe-leak-count', leakCount);
3323 safeUpdateElement('unsafe-boundary-count', boundaryCount);
3324 safeUpdateElement('unsafe-total-count', data.length);
3325 }
3326
3327 function applyUnsafeAnalysisFilter(filterType, data) {
3328 switch(filterType) {
3329 case 'critical':
3330 return data.filter(d => d.risk_level === 'High' || d.base?.is_leaked);
3331 case 'leaks':
3332 return data.filter(d => d.base?.is_leaked);
3333 case 'cross-boundary':
3334 return data.filter(d => d.has_boundary_events);
3335 case 'risk-assessment':
3336 return data.filter(d => d.source?.UnsafeRust?.risk_assessment);
3337 case 'all':
3338 default:
3339 return data;
3340 }
3341 }
3342
3343 function setupEnhancedFilterControls() {
3344 const filterTabs = document.querySelectorAll('.unsafe-filter-tab');
3345
3346 filterTabs.forEach(tab => {
3347 tab.addEventListener('click', () => {
3348 filterTabs.forEach(t => t.classList.remove('active'));
3349 tab.classList.add('active');
3350
3351 window.unsafeAnalysisCurrentFilter = tab.dataset.filter;
3352
3353 const filteredData = applyUnsafeAnalysisFilter(window.unsafeAnalysisCurrentFilter, window.unsafeAnalysisData);
3354 renderEnhancedUnsafeTimeline(filteredData);
3355 });
3356 });
3357 }
3358
3359 function setupTimelineControls() {
3360 document.getElementById('timelineZoomIn')?.addEventListener('click', () => {
3361 window.timelineZoomLevel *= 1.5;
3362 rerenderTimeline();
3363 });
3364
3365 document.getElementById('timelineZoomOut')?.addEventListener('click', () => {
3366 window.timelineZoomLevel /= 1.5;
3367 rerenderTimeline();
3368 });
3369
3370 document.getElementById('timelineReset')?.addEventListener('click', () => {
3371 window.timelineZoomLevel = 1;
3372 window.timelineOffset = 0;
3373 rerenderTimeline();
3374 });
3375 }
3376
3377 function setupMemoryPassportModal() {
3378 const modal = document.getElementById('memoryPassport');
3379 const closeBtn = modal?.querySelector('.passport-close');
3380
3381 closeBtn?.addEventListener('click', () => {
3382 modal.style.display = 'none';
3383 });
3384
3385 modal?.addEventListener('click', (e) => {
3386 if (e.target === modal) {
3387 modal.style.display = 'none';
3388 }
3389 });
3390 }
3391
3392 function showMemoryPassport(memoryObject) {
3393 const modal = document.getElementById('memoryPassport');
3394 const body = document.getElementById('passportBody');
3395
3396 if (!modal || !body) return;
3397
3398 // Generate passport content based on the memory object
3399 const passportContent = generatePassportContent(memoryObject);
3400 body.innerHTML = passportContent;
3401
3402 modal.style.display = 'flex';
3403 }
3404
3405 function generatePassportContent(memoryObject) {
3406 const ptr = memoryObject.base?.ptr || memoryObject.ptr || 'Unknown';
3407 const size = memoryObject.base?.size || memoryObject.size || 0;
3408 const isLeaked = memoryObject.base?.is_leaked || false;
3409 const riskLevel = memoryObject.risk_level || 'Unknown';
3410
3411 return `
3412 <div class="passport-section">
3413 <h4><i class="fa fa-info-circle"></i> Memory Passport: ${ptr}</h4>
3414 <div class="passport-grid">
3415 <div class="passport-item">
3416 <strong>Size:</strong> ${formatBytes(size)}
3417 </div>
3418 <div class="passport-item">
3419 <strong>Status:</strong>
3420 <span class="status-${isLeaked ? 'leaked' : 'normal'}">
3421 ${isLeaked ? '🚨 LEAKED' : '✅ Normal'}
3422 </span>
3423 </div>
3424 <div class="passport-item">
3425 <strong>Risk Level:</strong>
3426 <span class="risk-${riskLevel.toLowerCase()}">${riskLevel}</span>
3427 </div>
3428 <div class="passport-item">
3429 <strong>FFI Tracked:</strong> ${memoryObject.ffi_tracked ? '✅ Yes' : '❌ No'}
3430 </div>
3431 </div>
3432 </div>
3433
3434 <div class="passport-section">
3435 <h4><i class="fa fa-timeline"></i> Lifecycle Log</h4>
3436 <div class="lifecycle-events">
3437 ${generateLifecycleEvents(memoryObject)}
3438 </div>
3439 </div>
3440
3441 ${memoryObject.source?.UnsafeRust?.risk_assessment ? `
3442 <div class="passport-section">
3443 <h4><i class="fa fa-exclamation-triangle"></i> Risk Assessment</h4>
3444 <div class="risk-details">
3445 ${generateRiskAssessment(memoryObject.source.UnsafeRust.risk_assessment)}
3446 </div>
3447 </div>
3448 ` : ''}
3449 `;
3450 }
3451
3452 function generateLifecycleEvents(memoryObject) {
3453 let events = '';
3454
3455 // Allocation event
3456 if (memoryObject.base?.timestamp_alloc) {
3457 events += `
3458 <div class="lifecycle-event allocation">
3459 <div class="event-icon">🟢</div>
3460 <div class="event-details">
3461 <strong>Allocation</strong><br>
3462 Time: ${new Date(memoryObject.base.timestamp_alloc / 1000000).toLocaleString()}<br>
3463 Source: ${Object.keys(memoryObject.source || {})[0] || 'Unknown'}
3464 </div>
3465 </div>
3466 `;
3467 }
3468
3469 // Boundary events
3470 if (memoryObject.cross_boundary_events) {
3471 memoryObject.cross_boundary_events.forEach(event => {
3472 events += `
3473 <div class="lifecycle-event boundary">
3474 <div class="event-icon">${event.event_type === 'FfiToRust' ? '⬆️' : '⬇️'}</div>
3475 <div class="event-details">
3476 <strong>Boundary Cross: ${event.event_type}</strong><br>
3477 From: ${event.from_context}<br>
3478 To: ${event.to_context}
3479 </div>
3480 </div>
3481 `;
3482 });
3483 }
3484
3485 // Deallocation event
3486 if (memoryObject.base?.timestamp_dealloc) {
3487 events += `
3488 <div class="lifecycle-event deallocation">
3489 <div class="event-icon">🔴</div>
3490 <div class="event-details">
3491 <strong>Deallocation</strong><br>
3492 Time: ${new Date(memoryObject.base.timestamp_dealloc / 1000000).toLocaleString()}
3493 </div>
3494 </div>
3495 `;
3496 } else if (memoryObject.base?.is_leaked) {
3497 events += `
3498 <div class="lifecycle-event leak">
3499 <div class="event-icon">⚠️</div>
3500 <div class="event-details">
3501 <strong>MEMORY LEAK DETECTED</strong><br>
3502 No deallocation event found
3503 </div>
3504 </div>
3505 `;
3506 }
3507
3508 return events || '<p>No lifecycle events recorded</p>';
3509 }
3510
3511 function generateRiskAssessment(riskAssessment) {
3512 return `
3513 <div class="risk-summary">
3514 <div class="risk-level ${riskAssessment.risk_level?.toLowerCase()}">
3515 Risk Level: ${riskAssessment.risk_level}
3516 </div>
3517 <div class="confidence-score">
3518 Confidence: ${Math.round((riskAssessment.confidence_score || 0) * 100)}%
3519 </div>
3520 </div>
3521 ${riskAssessment.risk_factors ? `
3522 <div class="risk-factors">
3523 <h5>Risk Factors:</h5>
3524 ${riskAssessment.risk_factors.map(factor => `
3525 <div class="risk-factor">
3526 <strong>${factor.factor_type}:</strong> ${factor.description}
3527 <span class="severity">Severity: ${factor.severity}/10</span>
3528 </div>
3529 `).join('')}
3530 </div>
3531 ` : ''}
3532 `;
3533 }
3534
3535 function renderEnhancedUnsafeTimeline(data) {
3536 console.log('🎨 Rendering enhanced unsafe timeline with', data.length, 'items');
3537
3538 // Clear existing timeline
3539 const rustTrack = document.getElementById('rustTimelineTrack');
3540 const ffiTrack = document.getElementById('ffiTimelineTrack');
3541 const timelineAxis = document.getElementById('timelineAxis');
3542
3543 if (!rustTrack || !ffiTrack || !timelineAxis) {
3544 console.warn('Timeline tracks not found');
3545 return;
3546 }
3547
3548 rustTrack.innerHTML = '';
3549 ffiTrack.innerHTML = '';
3550 timelineAxis.innerHTML = '';
3551
3552 if (data.length === 0) {
3553 rustTrack.innerHTML = '<p style="text-align: center; color: var(--text-secondary); margin-top: 2rem;">No data matches current filter</p>';
3554 return;
3555 }
3556
3557 // Calculate time range
3558 const timestamps = data.flatMap(d => {
3559 const times = [];
3560 if (d.base?.timestamp_alloc) times.push(d.base.timestamp_alloc);
3561 if (d.base?.timestamp_dealloc) times.push(d.base.timestamp_dealloc);
3562 return times;
3563 }).filter(t => t);
3564
3565 if (timestamps.length === 0) return;
3566
3567 const minTime = Math.min(...timestamps);
3568 const maxTime = Math.max(...timestamps);
3569 const timeRange = maxTime - minTime;
3570
3571 // Render each memory object
3572 data.forEach((memoryObj, index) => {
3573 renderMemoryObjectLifecycle(memoryObj, index, minTime, timeRange, rustTrack, ffiTrack);
3574 });
3575
3576 // Render time axis
3577 renderTimeAxis(minTime, timeRange, timelineAxis);
3578 }
3579
3580 function renderMemoryObjectLifecycle(memoryObj, index, minTime, timeRange, rustTrack, ffiTrack) {
3581 const allocTime = memoryObj.base?.timestamp_alloc || minTime;
3582 const deallocTime = memoryObj.base?.timestamp_dealloc;
3583
3584 const startPercent = ((allocTime - minTime) / timeRange) * 100;
3585 const endPercent = deallocTime ? ((deallocTime - minTime) / timeRange) * 100 : 100;
3586 const width = endPercent - startPercent;
3587
3588 // Determine source and target track
3589 const sourceType = Object.keys(memoryObj.source || {})[0];
3590 const isUnsafeRust = sourceType === 'UnsafeRust';
3591 const targetTrack = isUnsafeRust ? rustTrack : ffiTrack;
3592
3593 // Create lifecycle path
3594 const lifecyclePath = document.createElement('div');
3595 lifecyclePath.className = 'memory-lifecycle-path';
3596 lifecyclePath.style.cssText = `
3597 position: absolute;
3598 left: ${startPercent}%;
3599 width: ${width}%;
3600 top: ${(index % 3) * 30 + 10}px;
3601 height: 20px;
3602 background: ${getMemoryPathColor(memoryObj)};
3603 border-radius: 10px;
3604 cursor: pointer;
3605 transition: transform 0.2s ease, box-shadow 0.2s ease;
3606 border: 2px solid ${getMemoryBorderColor(memoryObj)};
3607 display: flex;
3608 align-items: center;
3609 justify-content: space-between;
3610 padding: 0 8px;
3611 font-size: 10px;
3612 color: white;
3613 font-weight: bold;
3614 `;
3615
3616 lifecyclePath.innerHTML = `
3617 <span>${getSourceIcon(sourceType)}</span>
3618 <span>${formatBytes(memoryObj.base?.size || 0)}</span>
3619 <span>${memoryObj.base?.is_leaked ? '🚨' : '✅'}</span>
3620 `;
3621
3622 // Add hover effects
3623 lifecyclePath.addEventListener('mouseenter', () => {
3624 lifecyclePath.style.transform = 'scale(1.1) translateY(-2px)';
3625 lifecyclePath.style.boxShadow = '0 8px 16px rgba(0,0,0,0.3)';
3626 lifecyclePath.style.zIndex = '10';
3627 });
3628
3629 lifecyclePath.addEventListener('mouseleave', () => {
3630 lifecyclePath.style.transform = 'scale(1) translateY(0)';
3631 lifecyclePath.style.boxShadow = 'none';
3632 lifecyclePath.style.zIndex = '1';
3633 });
3634
3635 // Add click event to show passport
3636 lifecyclePath.addEventListener('click', () => {
3637 showMemoryPassport(memoryObj);
3638 });
3639
3640 targetTrack.appendChild(lifecyclePath);
3641
3642 // Render boundary events
3643 if (memoryObj.cross_boundary_events) {
3644 memoryObj.cross_boundary_events.forEach(event => {
3645 renderBoundaryEvent(event, minTime, timeRange, rustTrack, ffiTrack);
3646 });
3647 }
3648 }
3649
3650 function getMemoryPathColor(memoryObj) {
3651 if (memoryObj.base?.is_leaked) return 'linear-gradient(90deg, #ff4757, #ff3742)';
3652 if (memoryObj.risk_level === 'High') return 'linear-gradient(90deg, #ffa502, #ff9f43)';
3653 if (memoryObj.risk_level === 'Medium') return 'linear-gradient(90deg, #3742fa, #2f3542)';
3654 return 'linear-gradient(90deg, #2ed573, #1e90ff)';
3655 }
3656
3657 function getMemoryBorderColor(memoryObj) {
3658 if (memoryObj.base?.is_leaked) return '#ff4757';
3659 if (memoryObj.risk_level === 'High') return '#ffa502';
3660 return '#3742fa';
3661 }
3662
3663 function getSourceIcon(sourceType) {
3664 switch(sourceType) {
3665 case 'UnsafeRust': return '🦀';
3666 case 'FfiC': return '⚡';
3667 default: return '❓';
3668 }
3669 }
3670
3671 function renderBoundaryEvent(event, minTime, timeRange, rustTrack, ffiTrack) {
3672 const eventTime = event.timestamp * 1000000; // Convert to nanoseconds
3673 const eventPercent = ((eventTime - minTime) / timeRange) * 100;
3674
3675 const boundaryIndicator = document.createElement('div');
3676 boundaryIndicator.className = 'boundary-event-indicator';
3677 boundaryIndicator.style.cssText = `
3678 position: absolute;
3679 left: ${eventPercent}%;
3680 top: -10px;
3681 width: 2px;
3682 height: 140px;
3683 background: ${event.event_type === 'FfiToRust' ? '#00d4aa' : '#ff4757'};
3684 z-index: 5;
3685 `;
3686
3687 const arrow = document.createElement('div');
3688 arrow.innerHTML = event.event_type === 'FfiToRust' ? '▲' : '▼';
3689 arrow.style.cssText = `
3690 position: absolute;
3691 top: ${event.event_type === 'FfiToRust' ? '100px' : '20px'};
3692 left: -8px;
3693 color: ${event.event_type === 'FfiToRust' ? '#00d4aa' : '#ff4757'};
3694 font-size: 16px;
3695 font-weight: bold;
3696 `;
3697
3698 boundaryIndicator.appendChild(arrow);
3699 rustTrack.appendChild(boundaryIndicator);
3700 }
3701
3702 function renderTimeAxis(minTime, timeRange, timelineAxis) {
3703 // Create time markers
3704 const numMarkers = 10;
3705 for (let i = 0; i <= numMarkers; i++) {
3706 const percent = (i / numMarkers) * 100;
3707 const time = minTime + (timeRange * i / numMarkers);
3708
3709 const marker = document.createElement('div');
3710 marker.style.cssText = `
3711 position: absolute;
3712 left: ${percent}%;
3713 top: 0;
3714 width: 1px;
3715 height: 100%;
3716 background: var(--border-light);
3717 `;
3718
3719 const label = document.createElement('div');
3720 safeUpdateElement(label.id || 'timeline-label', new Date(time / 1000000).toLocaleTimeString());
3721 label.style.cssText = `
3722 position: absolute;
3723 left: ${percent}%;
3724 top: 50%;
3725 transform: translateX(-50%) translateY(-50%);
3726 font-size: 0.7rem;
3727 color: var(--text-secondary);
3728 background: var(--bg-primary);
3729 padding: 2px 4px;
3730 border-radius: 2px;
3731 `;
3732
3733 timelineAxis.appendChild(marker);
3734 timelineAxis.appendChild(label);
3735 }
3736 }
3737
3738 function rerenderTimeline() {
3739 const filteredData = applyUnsafeAnalysisFilter(window.unsafeAnalysisCurrentFilter, window.unsafeAnalysisData);
3740 renderEnhancedUnsafeTimeline(filteredData);
3741 }
3742
3743 // Dynamic Size Control Functions
3744 function setupDynamicSizeControls() {
3745 const container = document.querySelector('section.card[style*="min-height: 700px"]');
3746 const expandBtn = document.getElementById('expandAnalysis');
3747 const compactBtn = document.getElementById('compactAnalysis');
3748
3749 if (!container) return;
3750
3751 expandBtn?.addEventListener('click', () => {
3752 container.classList.remove('compact');
3753 container.classList.add('expanded');
3754 container.style.minHeight = '900px';
3755 updateAnalysisLayout();
3756 });
3757
3758 compactBtn?.addEventListener('click', () => {
3759 container.classList.remove('expanded');
3760 container.classList.add('compact');
3761 container.style.minHeight = '500px';
3762 updateAnalysisLayout();
3763 });
3764 }
3765
3766 function updateAnalysisLayout() {
3767 // Trigger layout updates for charts and visualizations
3768 setTimeout(() => {
3769 const filteredData = applyUnsafeAnalysisFilter(window.unsafeAnalysisCurrentFilter, window.unsafeAnalysisData);
3770 renderEnhancedUnsafeTimeline(filteredData);
3771 }, 300);
3772 }
3773
3774 // Risk Analysis Tab Controls
3775 function setupRiskAnalysisTabs() {
3776 const tabs = document.querySelectorAll('.risk-tab');
3777 const views = document.querySelectorAll('.risk-view');
3778
3779 tabs.forEach(tab => {
3780 tab.addEventListener('click', () => {
3781 const targetView = tab.dataset.view;
3782
3783 // Update tab states
3784 tabs.forEach(t => t.classList.remove('active'));
3785 tab.classList.add('active');
3786
3787 // Update view states
3788 views.forEach(view => {
3789 view.style.display = 'none';
3790 view.classList.remove('active');
3791 });
3792
3793 const targetElement = document.getElementById(`risk${targetView.charAt(0).toUpperCase() + targetView.slice(1)}View`);
3794 if (targetElement) {
3795 targetElement.style.display = 'block';
3796 targetElement.classList.add('active');
3797 }
3798
3799 // Load specific content based on view
3800 loadRiskViewContent(targetView);
3801 });
3802 });
3803 }
3804
3805 function loadRiskViewContent(viewType) {
3806 switch(viewType) {
3807 case 'table':
3808 loadSafetyRisks(); // Existing function
3809 break;
3810 case 'patterns':
3811 loadRiskPatterns();
3812 break;
3813 case 'locations':
3814 loadRiskLocations();
3815 break;
3816 }
3817 }
3818
3819 function loadRiskPatterns() {
3820 const chartContainer = document.getElementById('riskPatternsChart');
3821 if (!chartContainer) return;
3822
3823 // Simulate pattern analysis
3824 chartContainer.innerHTML = `
3825 <div style="padding: 2rem; text-align: center;">
3826 <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; margin-bottom: 1rem;">
3827 <div style="background: var(--bg-primary); padding: 1rem; border-radius: 8px;">
3828 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-red);">67%</div>
3829 <div style="font-size: 0.8rem; color: var(--text-secondary);">Manual Memory</div>
3830 </div>
3831 <div style="background: var(--bg-primary); padding: 1rem; border-radius: 8px;">
3832 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-orange);">23%</div>
3833 <div style="font-size: 0.8rem; color: var(--text-secondary);">Boundary Cross</div>
3834 </div>
3835 <div style="background: var(--bg-primary); padding: 1rem; border-radius: 8px;">
3836 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-blue);">8%</div>
3837 <div style="font-size: 0.8rem; color: var(--text-secondary);">Ownership Issues</div>
3838 </div>
3839 <div style="background: var(--bg-primary); padding: 1rem; border-radius: 8px;">
3840 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-green);">2%</div>
3841 <div style="font-size: 0.8rem; color: var(--text-secondary);">Other Risks</div>
3842 </div>
3843 </div>
3844 <p style="color: var(--text-secondary); font-size: 0.9rem;">
3845 Most common risk patterns: Manual memory management dominates unsafe operations
3846 </p>
3847 </div>
3848 `;
3849 }
3850
3851 function loadRiskLocations() {
3852 const heatmapContainer = document.getElementById('riskLocationsHeatmap');
3853 if (!heatmapContainer) return;
3854
3855 // Simulate location heatmap
3856 heatmapContainer.innerHTML = `
3857 <div style="padding: 1rem;">
3858 <div style="margin-bottom: 1rem;">
3859 <h4 style="margin: 0 0 0.5rem 0; font-size: 0.9rem;">High-Risk Code Locations</h4>
3860 </div>
3861 <div style="display: flex; flex-direction: column; gap: 0.5rem;">
3862 <div style="display: flex; justify-content: space-between; align-items: center; padding: 0.5rem; background: rgba(220, 38, 38, 0.1); border-radius: 4px; border-left: 3px solid #dc2626;">
3863 <span style="font-size: 0.8rem; font-family: monospace;">src/ffi/mod.rs:142</span>
3864 <span style="font-size: 0.7rem; color: #dc2626; font-weight: 600;">HIGH</span>
3865 </div>
3866 <div style="display: flex; justify-content: space-between; align-items: center; padding: 0.5rem; background: rgba(245, 158, 11, 0.1); border-radius: 4px; border-left: 3px solid #f59e0b;">
3867 <span style="font-size: 0.8rem; font-family: monospace;">src/memory/alloc.rs:89</span>
3868 <span style="font-size: 0.7rem; color: #f59e0b; font-weight: 600;">MED</span>
3869 </div>
3870 <div style="display: flex; justify-content: space-between; align-items: center; padding: 0.5rem; background: rgba(245, 158, 11, 0.1); border-radius: 4px; border-left: 3px solid #f59e0b;">
3871 <span style="font-size: 0.8rem; font-family: monospace;">src/unsafe/ptr.rs:67</span>
3872 <span style="font-size: 0.7rem; color: #f59e0b; font-weight: 600;">MED</span>
3873 </div>
3874 <div style="display: flex; justify-content: space-between; align-items: center; padding: 0.5rem; background: rgba(16, 185, 129, 0.1); border-radius: 4px; border-left: 3px solid #10b981;">
3875 <span style="font-size: 0.8rem; font-family: monospace;">src/lib.rs:234</span>
3876 <span style="font-size: 0.7rem; color: #10b981; font-weight: 600;">LOW</span>
3877 </div>
3878 </div>
3879 </div>
3880 `;
3881 }
3882
3883 // Enhanced loadSafetyRisks function with real data extraction
3884 function loadSafetyRisks() {
3885 console.log('🛡️ Loading safety risk data from real unsafe/FFI analysis...');
3886 const unsafeTable = document.getElementById('unsafeTable');
3887 if (!unsafeTable) {
3888 console.warn('⚠️ unsafeTable not found');
3889 return;
3890 }
3891
3892 // Show loading state first
3893 unsafeTable.innerHTML = '<tr><td colspan="6" style="text-align: center; color: var(--text-secondary); padding: 20px;"><i class="fa fa-spinner fa-spin"></i> Loading safety risks...</td></tr>';
3894
3895 // Extract real risks from actual data
3896 let risks = [];
3897 try {
3898 risks = extractRealSafetyRisks();
3899 console.log(`🛡️ Extracted ${risks.length} safety risks`);
3900 } catch (error) {
3901 console.error('❌ Error extracting safety risks:', error);
3902 // Fallback to sample data for demonstration
3903 risks = [
3904 {
3905 location: 'src/main.rs:42',
3906 operation: 'unsafe { libc::malloc }',
3907 risk_level: 'High',
3908 rawData: { base: { size: 1024 } }
3909 },
3910 {
3911 location: 'src/lib.rs:158',
3912 operation: 'Manual memory management',
3913 risk_level: 'Medium',
3914 rawData: { base: { size: 512 } }
3915 }
3916 ];
3917 console.log('🛡️ Using fallback sample risk data for demonstration');
3918 }
3919
3920 if (risks.length === 0) {
3921 unsafeTable.innerHTML = '<tr><td colspan="4" class="text-center text-gray-500">No safety risks detected</td></tr>';
3922 return;
3923 }
3924
3925 unsafeTable.innerHTML = '';
3926 risks.forEach((risk, index) => {
3927 const row = document.createElement('tr');
3928 row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700';
3929
3930 const riskLevelClass = risk.risk_level === 'High' ? 'text-red-600' :
3931 risk.risk_level === 'Medium' ? 'text-yellow-600' : 'text-green-600';
3932
3933 const memorySize = risk?.rawData?.base?.size ? formatBytes(risk.rawData.base.size) : 'N/A';
3934 const assessment = getRiskAssessment(risk);
3935
3936 row.innerHTML = `
3937 <td class="px-3 py-2 text-sm font-mono" style="max-width: 200px; overflow: hidden; text-overflow: ellipsis;">${risk.location}</td>
3938 <td class="px-3 py-2 text-sm">${risk.operation}</td>
3939 <td class="px-3 py-2 text-sm"><span class="${riskLevelClass} font-weight-600">${risk.risk_level}</span></td>
3940 <td class="px-3 py-2 text-sm" style="color: var(--text-secondary);">${memorySize}</td>
3941 <td class="px-3 py-2 text-xs" style="max-width: 150px; color: var(--text-secondary);">${assessment}</td>
3942 <td class="px-3 py-2 text-sm">
3943 <button class="action-btn" onclick="showRiskActionModal('${risk.location}', '${risk.operation}', '${risk.risk_level}', ${index})"
3944 style="background: var(--primary-blue); color: white; border: none; padding: 0.2rem 0.5rem; border-radius: 4px; font-size: 0.7rem; cursor: pointer; transition: all 0.2s ease;">
3945 <i class="fa fa-info-circle"></i> More
3946 </button>
3947 </td>
3948 `;
3949 unsafeTable.appendChild(row);
3950 });
3951
3952 // Update risk summary stats
3953 const highCount = risks.filter(r => r.risk_level === 'High').length;
3954 const mediumCount = risks.filter(r => r.risk_level === 'Medium').length;
3955 const lowCount = risks.filter(r => r.risk_level === 'Low').length;
3956
3957 safeUpdateElement('high-risk-count', highCount);
3958 safeUpdateElement('medium-risk-count', mediumCount);
3959 safeUpdateElement('low-risk-count', lowCount);
3960
3961 console.log('✅ Real safety risks loaded:', risks.length, 'items');
3962 }
3963
3964 // Extract real safety risks from actual unsafe/FFI data
3965 function extractRealSafetyRisks() {
3966 const risks = [];
3967
3968 // Ensure unsafeAnalysisData is initialized
3969 if (typeof window.unsafeAnalysisData === 'undefined' || window.unsafeAnalysisData === null) {
3970 console.warn('⚠️ window.unsafeAnalysisData not initialized, initializing empty array');
3971 window.unsafeAnalysisData = [];
3972 }
3973
3974 // Extract from unsafe analysis data
3975 if (window.unsafeAnalysisData && Array.isArray(window.unsafeAnalysisData) && window.unsafeAnalysisData.length > 0) {
3976 window.unsafeAnalysisData.forEach((item, index) => {
3977 // Extract location from unsafe block location
3978 let location = 'Unknown location';
3979 if (item.source?.UnsafeRust?.unsafe_block_location) {
3980 location = item.source.UnsafeRust.unsafe_block_location;
3981 } else if (item.source?.FfiC?.resolved_function?.library_name) {
3982 const libName = item.source.FfiC.resolved_function.library_name;
3983 const funcName = item.source.FfiC.resolved_function.function_name;
3984 location = `${libName}::${funcName}`;
3985 }
3986
3987 // Determine operation type
3988 let operation = 'unknown operation';
3989 if (item.source?.UnsafeRust) {
3990 operation = 'unsafe rust operation';
3991 if (item.base?.ptr) operation = 'raw pointer manipulation';
3992 } else if (item.source?.FfiC) {
3993 const funcName = item.source.FfiC.resolved_function?.function_name;
3994 if (funcName === 'malloc') operation = 'manual memory allocation';
3995 else if (funcName === 'free') operation = 'manual memory deallocation';
3996 else operation = `FFI call: ${funcName}`;
3997 }
3998
3999 // Determine risk level from assessment
4000 let riskLevel = 'Low';
4001 if (item.source?.UnsafeRust?.risk_assessment) {
4002 riskLevel = item.source.UnsafeRust.risk_assessment.risk_level;
4003 } else if (item.base?.is_leaked) {
4004 riskLevel = 'High';
4005 } else if (item.source?.FfiC) {
4006 riskLevel = item.source.FfiC.resolved_function?.risk_level || 'Medium';
4007 }
4008
4009 // Only add items with identifiable risks
4010 if (riskLevel !== 'Low' || item.base?.is_leaked || item.has_boundary_events) {
4011 risks.push({
4012 location: location,
4013 operation: operation,
4014 risk_level: riskLevel,
4015 rawData: item,
4016 riskFactors: item.source?.UnsafeRust?.risk_assessment?.risk_factors || []
4017 });
4018 }
4019 });
4020 }
4021
4022 // If no real risks found, show some from basic allocations data
4023 if (risks.length === 0 && window.analysisData?.memory_analysis?.allocations) {
4024 const allocations = window.analysisData.memory_analysis.allocations;
4025 allocations.forEach((alloc, index) => {
4026 if (alloc.type_name && alloc.type_name.includes('*')) {
4027 risks.push({
4028 location: `allocation_${index}.rs:${Math.floor(Math.random() * 100) + 10}`,
4029 operation: `pointer operation: ${alloc.type_name}`,
4030 risk_level: alloc.is_leaked ? 'High' : 'Medium',
4031 rawData: alloc,
4032 riskFactors: [{
4033 factor_type: 'RawPointerUsage',
4034 severity: alloc.is_leaked ? 8 : 5,
4035 description: 'Raw pointer operations require careful memory management'
4036 }]
4037 });
4038 }
4039 });
4040 }
4041
4042 return risks.slice(0, 10); // Limit to first 10 for display
4043 }
4044
4045 // Show Risk Action Modal (replacing alert with elegant modal)
4046 function showRiskActionModal(location, operation, riskLevel, riskIndex) {
4047 const modal = document.getElementById('riskActionModal');
4048 const body = document.getElementById('riskActionBody');
4049
4050 if (!modal || !body) return;
4051
4052 // Get the actual risk data
4053 const risks = extractRealSafetyRisks();
4054 const risk = risks[riskIndex];
4055
4056 // Generate action content based on real risk data
4057 const actionContent = generateRiskActionContent(risk, location, operation, riskLevel);
4058 body.innerHTML = actionContent;
4059
4060 modal.style.display = 'flex';
4061
4062 console.log('🔧 Showing risk action modal for:', location);
4063 }
4064
4065 function generateRiskActionContent(risk, location, operation, riskLevel) {
4066 const riskColor = riskLevel === 'High' ? '#dc2626' : riskLevel === 'Medium' ? '#f59e0b' : '#10b981';
4067
4068 return `
4069 <div class="risk-action-section">
4070 <h4><i class="fa fa-exclamation-triangle" style="color: ${riskColor};"></i> Risk Assessment</h4>
4071 <div class="risk-action-grid">
4072 <div class="risk-action-item">
4073 <strong>Location:</strong> <code>${location}</code>
4074 </div>
4075 <div class="risk-action-item">
4076 <strong>Operation:</strong> ${operation}
4077 </div>
4078 <div class="risk-action-item">
4079 <strong>Risk Level:</strong>
4080 <span style="color: ${riskColor}; font-weight: bold;">${riskLevel}</span>
4081 </div>
4082 ${risk?.rawData?.base?.size ? `
4083 <div class="risk-action-item">
4084 <strong>Memory Size:</strong> ${formatBytes(risk.rawData.base.size)}
4085 </div>
4086 ` : ''}
4087 </div>
4088 </div>
4089
4090 <div class="risk-action-section">
4091 <h4><i class="fa fa-lightbulb"></i> Recommended Actions</h4>
4092 <div class="recommended-actions">
4093 ${generateRecommendedActions(risk, operation, riskLevel)}
4094 </div>
4095 </div>
4096
4097 ${risk?.riskFactors && risk.riskFactors.length > 0 ? `
4098 <div class="risk-action-section">
4099 <h4><i class="fa fa-list"></i> Risk Factors</h4>
4100 <div class="risk-factors-list">
4101 ${risk.riskFactors.map(factor => `
4102 <div class="risk-factor-item">
4103 <div class="factor-header">
4104 <strong>${factor.factor_type}</strong>
4105 <span class="severity-badge" style="background: ${getSeverityColor(factor.severity)};">
4106 Severity: ${factor.severity}/10
4107 </span>
4108 </div>
4109 <p class="factor-description">${factor.description}</p>
4110 </div>
4111 `).join('')}
4112 </div>
4113 </div>
4114 ` : ''}
4115
4116 `;
4117 }
4118
4119 function generateRecommendedActions(risk, operation, riskLevel) {
4120 const actions = [];
4121
4122 // Based on operation type
4123 if (operation.includes('pointer')) {
4124 actions.push({
4125 icon: 'fa-shield',
4126 title: 'Add Null Pointer Checks',
4127 description: 'Validate pointer is not null before dereferencing',
4128 priority: 'High'
4129 });
4130 actions.push({
4131 icon: 'fa-check-circle',
4132 title: 'Bounds Checking',
4133 description: 'Ensure pointer access is within allocated memory bounds',
4134 priority: 'High'
4135 });
4136 }
4137
4138 if (operation.includes('malloc') || operation.includes('allocation')) {
4139 actions.push({
4140 icon: 'fa-recycle',
4141 title: 'Use RAII Pattern',
4142 description: 'Wrap allocation in a safe Rust struct with Drop trait',
4143 priority: 'Medium'
4144 });
4145 actions.push({
4146 icon: 'fa-balance-scale',
4147 title: 'Match Alloc/Dealloc',
4148 description: 'Ensure every allocation has a corresponding deallocation',
4149 priority: 'High'
4150 });
4151 }
4152
4153 if (risk?.rawData?.base?.is_leaked) {
4154 actions.push({
4155 icon: 'fa-bug',
4156 title: 'Fix Memory Leak',
4157 description: 'Add proper cleanup code to prevent memory leaks',
4158 priority: 'Critical'
4159 });
4160 }
4161
4162 if (risk?.has_boundary_events) {
4163 actions.push({
4164 icon: 'fa-exchange',
4165 title: 'Document Ownership Transfer',
4166 description: 'Clearly document which side owns the memory after FFI calls',
4167 priority: 'Medium'
4168 });
4169 }
4170
4171 // Default actions
4172 if (actions.length === 0) {
4173 actions.push({
4174 icon: 'fa-book',
4175 title: 'Add Safety Documentation',
4176 description: 'Document the safety invariants and assumptions',
4177 priority: 'Low'
4178 });
4179 }
4180
4181 return actions.map(action => `
4182 <div class="recommended-action">
4183 <div class="action-header">
4184 <i class="fa ${action.icon}"></i>
4185 <strong>${action.title}</strong>
4186 <span class="priority-badge priority-${action.priority.toLowerCase()}">${action.priority}</span>
4187 </div>
4188 <p class="action-description">${action.description}</p>
4189 </div>
4190 `).join('');
4191 }
4192
4193 function getSeverityColor(severity) {
4194 if (severity >= 8) return '#dc2626';
4195 if (severity >= 6) return '#f59e0b';
4196 if (severity >= 4) return '#eab308';
4197 return '#10b981';
4198 }
4199
4200 // Quick action functions
4201 function copyLocationToClipboard(location) {
4202 navigator.clipboard.writeText(location).then(() => {
4203 console.log('📋 Location copied to clipboard:', location);
4204 // Could show a toast notification here
4205 });
4206 }
4207
4208 function openInEditor(location) {
4209 console.log('🔧 Opening in editor:', location);
4210 // This would integrate with VS Code or other editor
4211 // Example: vscode://file/path/to/file:line:column
4212 }
4213
4214 function generateFixPatch(location, operation) {
4215 console.log('🪄 Generating fix patch for:', location, operation);
4216 // This would generate actual code fixes based on the risk type
4217 }
4218
4219 function closeRiskActionModal() {
4220 const modal = document.getElementById('riskActionModal');
4221 if (modal) {
4222 modal.style.display = 'none';
4223 }
4224 }
4225
4226 // Initialize enhanced unsafe analysis when data is available
4227 if (window.analysisData || window.unsafeFfiSnapshot) {
4228 setTimeout(() => {
4229 initializeEnhancedUnsafeAnalysis();
4230 setupDynamicSizeControls();
4231 setupRiskAnalysisTabs();
4232 }, 1200);
4233 }
4234 </script>
4235
4236 <style>
4237 /* Enhanced Unsafe Rust & FFI Memory Analysis Styles */
4238 .unsafe-analysis-header {
4239 display: flex;
4240 justify-content: space-between;
4241 align-items: flex-end;
4242 gap: 2rem;
4243 margin-bottom: 2rem;
4244 }
4245
4246 .size-controls {
4247 display: flex;
4248 gap: 0.5rem;
4249 margin-right: 1rem;
4250 }
4251
4252 .control-btn {
4253 padding: 0.4rem 0.8rem;
4254 background: var(--bg-secondary);
4255 border: 1px solid var(--border-light);
4256 border-radius: 6px;
4257 color: var(--text-primary);
4258 font-size: 0.8rem;
4259 cursor: pointer;
4260 transition: all 0.2s ease;
4261 white-space: nowrap;
4262 }
4263
4264 .control-btn:hover {
4265 background: var(--primary-blue);
4266 color: white;
4267 transform: translateY(-1px);
4268 box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
4269 }
4270
4271 .unsafe-filter-controls {
4272 display: flex;
4273 align-items: center;
4274 }
4275
4276 .unsafe-filter-tabs {
4277 display: flex;
4278 gap: 0.25rem;
4279 background: var(--bg-secondary);
4280 padding: 0.25rem;
4281 border-radius: 10px;
4282 border: 1px solid var(--border-light);
4283 box-shadow: var(--shadow-light);
4284 }
4285
4286 .unsafe-filter-tab {
4287 padding: 0.4rem 0.8rem;
4288 border: none;
4289 background: transparent;
4290 color: var(--text-secondary);
4291 font-size: 0.75rem;
4292 font-weight: 600;
4293 border-radius: 8px;
4294 cursor: pointer;
4295 transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
4296 white-space: nowrap;
4297 user-select: none;
4298 position: relative;
4299 }
4300
4301 .unsafe-filter-tab:hover {
4302 background: var(--bg-primary);
4303 color: var(--text-primary);
4304 transform: translateY(-1px);
4305 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
4306 }
4307
4308 .unsafe-filter-tab.active {
4309 background: linear-gradient(135deg, var(--primary-blue), #3b82f6);
4310 color: white;
4311 box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
4312 transform: translateY(-2px);
4313 }
4314
4315 /* Enhanced Swimlane Container */
4316 .enhanced-swimlane-container {
4317 background: var(--bg-secondary);
4318 border-radius: 16px;
4319 border: 1px solid var(--border-light);
4320 overflow: hidden;
4321 box-shadow: var(--shadow-light);
4322 transition: all 0.3s ease;
4323 }
4324
4325 .card.expanded .enhanced-swimlane-container {
4326 min-height: 600px;
4327 }
4328
4329 .card.compact .enhanced-swimlane-container {
4330 min-height: 400px;
4331 }
4332
4333 /* Integrated Risk Analysis Styles */
4334 .integrated-risk-section {
4335 background: var(--bg-primary);
4336 border-radius: 12px;
4337 margin: 1.5rem;
4338 border: 1px solid var(--border-light);
4339 overflow: hidden;
4340 }
4341
4342 .risk-section-header {
4343 display: flex;
4344 justify-content: space-between;
4345 align-items: center;
4346 padding: 1rem 1.5rem;
4347 background: linear-gradient(135deg, var(--bg-secondary), var(--bg-primary));
4348 border-bottom: 1px solid var(--border-light);
4349 }
4350
4351 .risk-section-header h4 {
4352 margin: 0;
4353 color: var(--text-primary);
4354 font-size: 1rem;
4355 font-weight: 600;
4356 }
4357
4358 .risk-summary-stats {
4359 display: flex;
4360 gap: 1rem;
4361 align-items: center;
4362 }
4363
4364 .risk-stat {
4365 display: flex;
4366 align-items: center;
4367 gap: 0.3rem;
4368 font-size: 0.8rem;
4369 font-weight: 600;
4370 padding: 0.3rem 0.6rem;
4371 border-radius: 6px;
4372 background: var(--bg-secondary);
4373 }
4374
4375 .risk-stat.high-risk {
4376 color: #dc2626;
4377 background: rgba(220, 38, 38, 0.1);
4378 }
4379
4380 .risk-stat.medium-risk {
4381 color: #f59e0b;
4382 background: rgba(245, 158, 11, 0.1);
4383 }
4384
4385 .risk-stat.low-risk {
4386 color: #10b981;
4387 background: rgba(16, 185, 129, 0.1);
4388 }
4389
4390 .risk-analysis-tabs {
4391 display: flex;
4392 background: var(--bg-secondary);
4393 padding: 0.25rem;
4394 margin: 0 1.5rem;
4395 border-radius: 8px;
4396 gap: 0.25rem;
4397 }
4398
4399 .risk-tab {
4400 flex: 1;
4401 padding: 0.4rem 0.6rem;
4402 border: none;
4403 background: transparent;
4404 color: var(--text-secondary);
4405 font-size: 0.75rem;
4406 font-weight: 600;
4407 border-radius: 6px;
4408 cursor: pointer;
4409 transition: all 0.2s ease;
4410 text-align: center;
4411 }
4412
4413 .risk-tab:hover {
4414 background: var(--bg-primary);
4415 color: var(--text-primary);
4416 }
4417
4418 .risk-tab.active {
4419 background: var(--primary-blue);
4420 color: white;
4421 box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);
4422 }
4423
4424 .risk-views-container {
4425 padding: 1rem 1.5rem;
4426 }
4427
4428 .risk-view {
4429 width: 100%;
4430 }
4431
4432 /* Swimlane Container */
4433 .swimlane-container {
4434 flex: 1;
4435 background: var(--bg-primary);
4436 border-radius: 8px;
4437 overflow: hidden;
4438 margin: 1rem 1.5rem;
4439 border: 1px solid var(--border-light);
4440 }
4441
4442 /* Dual Swimlane Layout */
4443 .dual-swimlane {
4444 position: relative;
4445 min-height: 300px;
4446 }
4447
4448 .swimlane {
4449 position: relative;
4450 height: 100px;
4451 display: flex;
4452 border-bottom: 1px solid var(--border-light);
4453 }
4454
4455 .rust-domain {
4456 background: linear-gradient(135deg, rgba(255, 107, 71, 0.08) 0%, rgba(255, 107, 71, 0.03) 100%);
4457 }
4458
4459 .ffi-domain {
4460 background: linear-gradient(135deg, rgba(74, 158, 255, 0.08) 0%, rgba(74, 158, 255, 0.03) 100%);
4461 }
4462
4463 .domain-label {
4464 display: flex;
4465 align-items: center;
4466 gap: 0.8rem;
4467 padding: 1rem 1.5rem;
4468 min-width: 200px;
4469 background: rgba(255, 255, 255, 0.5);
4470 border-right: 1px solid var(--border-light);
4471 }
4472
4473 .domain-icon {
4474 font-size: 1.5rem;
4475 width: 40px;
4476 height: 40px;
4477 display: flex;
4478 align-items: center;
4479 justify-content: center;
4480 background: var(--bg-primary);
4481 border-radius: 50%;
4482 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
4483 }
4484
4485 .domain-info h4 {
4486 margin: 0 0 0.2rem 0;
4487 color: var(--text-primary);
4488 font-size: 0.9rem;
4489 font-weight: 700;
4490 }
4491
4492 .domain-info p {
4493 margin: 0;
4494 color: var(--text-secondary);
4495 font-size: 0.7rem;
4496 }
4497
4498 .timeline-track {
4499 flex: 1;
4500 position: relative;
4501 padding: 0.8rem;
4502 background: var(--bg-primary);
4503 }
4504
4505 .timeline-axis {
4506 height: 30px;
4507 background: linear-gradient(90deg, var(--border-light) 0%, var(--border-light) 100%);
4508 border-top: 1px solid var(--border-light);
4509 border-bottom: 1px solid var(--border-light);
4510 position: relative;
4511 }
4512
4513 /* Enhanced Legend */
4514 .enhanced-legend {
4515 padding: 1.5rem 2rem;
4516 background: var(--bg-primary);
4517 display: grid;
4518 grid-template-columns: 1fr 1fr;
4519 gap: 2rem;
4520 }
4521
4522 .legend-section h4 {
4523 margin: 0 0 1rem 0;
4524 color: var(--text-primary);
4525 font-size: 1rem;
4526 font-weight: 600;
4527 border-bottom: 2px solid var(--primary-blue);
4528 padding-bottom: 0.5rem;
4529 }
4530
4531 .legend-items {
4532 display: grid;
4533 gap: 0.8rem;
4534 }
4535
4536 .legend-item {
4537 display: flex;
4538 align-items: center;
4539 gap: 0.75rem;
4540 font-size: 0.9rem;
4541 color: var(--text-secondary);
4542 }
4543
4544 .event-symbol {
4545 width: 16px;
4546 height: 16px;
4547 display: inline-flex;
4548 align-items: center;
4549 justify-content: center;
4550 font-weight: bold;
4551 border-radius: 50%;
4552 }
4553
4554 .rust-alloc { background: #ff6b47; color: white; }
4555 .ffi-alloc { background: #4a9eff; color: white; }
4556 .boundary-up { color: #00d4aa; font-size: 1.2rem; }
4557 .boundary-down { color: #ff4757; font-size: 1.2rem; }
4558 .dealloc { color: #a0a0a0; font-size: 1.2rem; }
4559
4560 .risk-indicator {
4561 width: 20px;
4562 height: 12px;
4563 border-radius: 6px;
4564 display: inline-block;
4565 }
4566
4567 .high-risk { background: linear-gradient(90deg, #ff4757, #ff3742); }
4568 .medium-risk { background: linear-gradient(90deg, #ffa502, #ff9f43); }
4569 .leak-risk { background: linear-gradient(90deg, #ff6b47, #ff5722); }
4570
4571 /* Memory Passport Modal */
4572 .memory-passport-modal {
4573 position: fixed;
4574 top: 0;
4575 left: 0;
4576 width: 100%;
4577 height: 100%;
4578 background: rgba(0, 0, 0, 0.7);
4579 display: flex;
4580 align-items: center;
4581 justify-content: center;
4582 z-index: 1000;
4583 backdrop-filter: blur(4px);
4584 }
4585
4586 .passport-content {
4587 background: var(--bg-primary);
4588 border-radius: 16px;
4589 max-width: 800px;
4590 width: 90%;
4591 max-height: 80%;
4592 overflow: hidden;
4593 box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
4594 border: 1px solid var(--border-light);
4595 }
4596
4597 .passport-header {
4598 display: flex;
4599 justify-content: space-between;
4600 align-items: center;
4601 padding: 1.5rem 2rem;
4602 background: linear-gradient(135deg, var(--primary-blue), #3b82f6);
4603 color: white;
4604 }
4605
4606 .passport-header h3 {
4607 margin: 0;
4608 font-size: 1.4rem;
4609 font-weight: 700;
4610 }
4611
4612 .passport-close {
4613 background: none;
4614 border: none;
4615 color: white;
4616 font-size: 1.5rem;
4617 cursor: pointer;
4618 padding: 0.5rem;
4619 border-radius: 4px;
4620 transition: background 0.2s ease;
4621 }
4622
4623 .passport-close:hover {
4624 background: rgba(255, 255, 255, 0.2);
4625 }
4626
4627 .passport-body {
4628 padding: 2rem;
4629 max-height: 60vh;
4630 overflow-y: auto;
4631 }
4632
4633 /* Risk Action Modal Styles */
4634 .risk-action-section {
4635 margin-bottom: 2rem;
4636 padding-bottom: 1.5rem;
4637 border-bottom: 1px solid var(--border-light);
4638 }
4639
4640 .risk-action-section:last-child {
4641 border-bottom: none;
4642 margin-bottom: 0;
4643 }
4644
4645 .risk-action-section h4 {
4646 margin: 0 0 1rem 0;
4647 color: var(--text-primary);
4648 font-size: 1.1rem;
4649 font-weight: 600;
4650 display: flex;
4651 align-items: center;
4652 gap: 0.5rem;
4653 }
4654
4655 .risk-action-grid {
4656 display: grid;
4657 grid-template-columns: 1fr 1fr;
4658 gap: 1rem;
4659 margin-bottom: 1rem;
4660 }
4661
4662 .risk-action-item {
4663 padding: 0.75rem;
4664 background: var(--bg-secondary);
4665 border-radius: 8px;
4666 border: 1px solid var(--border-light);
4667 }
4668
4669 .risk-action-item strong {
4670 color: var(--text-primary);
4671 font-weight: 600;
4672 margin-right: 0.5rem;
4673 }
4674
4675 .risk-action-item code {
4676 background: var(--bg-primary);
4677 padding: 0.2rem 0.4rem;
4678 border-radius: 4px;
4679 font-family: 'Courier New', monospace;
4680 font-size: 0.85rem;
4681 color: var(--primary-blue);
4682 }
4683
4684 /* Recommended Actions */
4685 .recommended-actions {
4686 display: flex;
4687 flex-direction: column;
4688 gap: 1rem;
4689 }
4690
4691 .recommended-action {
4692 background: var(--bg-secondary);
4693 border-radius: 8px;
4694 padding: 1rem;
4695 border: 1px solid var(--border-light);
4696 transition: all 0.2s ease;
4697 }
4698
4699 .recommended-action:hover {
4700 border-color: var(--primary-blue);
4701 box-shadow: 0 4px 8px rgba(59, 130, 246, 0.1);
4702 }
4703
4704 .action-header {
4705 display: flex;
4706 align-items: center;
4707 gap: 0.75rem;
4708 margin-bottom: 0.5rem;
4709 }
4710
4711 .action-header i {
4712 color: var(--primary-blue);
4713 font-size: 1.1rem;
4714 width: 20px;
4715 }
4716
4717 .action-header strong {
4718 color: var(--text-primary);
4719 font-weight: 600;
4720 flex: 1;
4721 }
4722
4723 .priority-badge {
4724 padding: 0.2rem 0.6rem;
4725 border-radius: 12px;
4726 font-size: 0.7rem;
4727 font-weight: 600;
4728 text-transform: uppercase;
4729 letter-spacing: 0.5px;
4730 }
4731
4732 .priority-critical {
4733 background: rgba(220, 38, 38, 0.1);
4734 color: #dc2626;
4735 border: 1px solid rgba(220, 38, 38, 0.2);
4736 }
4737
4738 .priority-high {
4739 background: rgba(245, 158, 11, 0.1);
4740 color: #f59e0b;
4741 border: 1px solid rgba(245, 158, 11, 0.2);
4742 }
4743
4744 .priority-medium {
4745 background: rgba(59, 130, 246, 0.1);
4746 color: #3b82f6;
4747 border: 1px solid rgba(59, 130, 246, 0.2);
4748 }
4749
4750 .priority-low {
4751 background: rgba(16, 185, 129, 0.1);
4752 color: #10b981;
4753 border: 1px solid rgba(16, 185, 129, 0.2);
4754 }
4755
4756 .action-description {
4757 margin: 0;
4758 color: var(--text-secondary);
4759 font-size: 0.9rem;
4760 line-height: 1.4;
4761 }
4762
4763 /* Risk Factors */
4764 .risk-factors-list {
4765 display: flex;
4766 flex-direction: column;
4767 gap: 1rem;
4768 }
4769
4770 .risk-factor-item {
4771 background: var(--bg-secondary);
4772 border-radius: 8px;
4773 padding: 1rem;
4774 border: 1px solid var(--border-light);
4775 }
4776
4777 .factor-header {
4778 display: flex;
4779 align-items: center;
4780 justify-content: space-between;
4781 margin-bottom: 0.5rem;
4782 }
4783
4784 .factor-header strong {
4785 color: var(--text-primary);
4786 font-weight: 600;
4787 }
4788
4789 .severity-badge {
4790 padding: 0.2rem 0.6rem;
4791 border-radius: 12px;
4792 font-size: 0.7rem;
4793 font-weight: 600;
4794 color: white;
4795 }
4796
4797 .factor-description {
4798 margin: 0;
4799 color: var(--text-secondary);
4800 font-size: 0.9rem;
4801 line-height: 1.4;
4802 }
4803
4804 /* Quick Actions */
4805 .quick-actions {
4806 display: flex;
4807 gap: 1rem;
4808 flex-wrap: wrap;
4809 }
4810
4811 .quick-action-btn {
4812 padding: 0.75rem 1.5rem;
4813 background: var(--bg-secondary);
4814 border: 1px solid var(--border-light);
4815 border-radius: 8px;
4816 color: var(--text-primary);
4817 font-size: 0.9rem;
4818 font-weight: 500;
4819 cursor: pointer;
4820 transition: all 0.2s ease;
4821 display: flex;
4822 align-items: center;
4823 gap: 0.5rem;
4824 }
4825
4826 .quick-action-btn:hover {
4827 background: var(--primary-blue);
4828 color: white;
4829 border-color: var(--primary-blue);
4830 transform: translateY(-1px);
4831 box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
4832 }
4833
4834 .quick-action-btn i {
4835 font-size: 0.9rem;
4836 }
4837 </style>
4838</head>
4839
4840<body>
4841 <div class="dashboard-container">
4842 <!-- Header -->
4843 <header class="header">
4844 <div>
4845 <h1>{{PROJECT_NAME}} - Binary Memory Analysis Dashboard</h1>
4846 <div class="subtitle">Real-time Rust Memory Usage Monitoring</div>
4847 </div>
4848 <button id="theme-toggle" class="theme-toggle">
4849 <i class="fa fa-moon"></i>
4850 <span>Toggle Theme</span>
4851 </button>
4852 </header>
4853
4854 <!-- KPI Metrics -->
4855 <section class="grid grid-4">
4856 <div class="kpi-card">
4857 <div class="kpi-value" id="total-allocations">-</div>
4858 <div class="kpi-label">Total Allocations</div>
4859 </div>
4860 <div class="kpi-card">
4861 <div class="kpi-value" id="active-variables">-</div>
4862 <div class="kpi-label">Active Variables</div>
4863 </div>
4864 <div class="kpi-card">
4865 <div class="kpi-value" id="total-memory">-</div>
4866 <div class="kpi-label">Total Memory</div>
4867 </div>
4868 <div class="kpi-card">
4869 <div class="kpi-value" id="safety-score">-</div>
4870 <div class="kpi-label">Safety Score</div>
4871 </div>
4872 </section>
4873
4874 <!-- Key Performance Metrics (high priority position) -->
4875 <section class="grid grid-2">
4876 <div class="card">
4877 <h2><i class="fa fa-tachometer-alt"></i> Performance Metrics</h2>
4878 <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;">
4879 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
4880 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-blue);" id="peak-memory">0B</div>
4881 <div style="font-size: 0.8rem; color: var(--text-secondary);">Peak Memory</div>
4882 </div>
4883 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
4884 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-green);" id="allocation-rate">0/sec
4885 </div>
4886 <div style="font-size: 0.8rem; color: var(--text-secondary);">Allocation Rate</div>
4887 </div>
4888 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
4889 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-orange);" id="avg-lifetime">0ms</div>
4890 <div style="font-size: 0.8rem; color: var(--text-secondary);">Avg Lifetime</div>
4891 </div>
4892 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
4893 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-red);" id="fragmentation">0%</div>
4894 <div style="font-size: 0.8rem; color: var(--text-secondary);">Fragmentation</div>
4895 </div>
4896 </div>
4897 </div>
4898 <div class="card">
4899 <h2><i class="fa fa-shield-alt"></i> Thread Safety Analysis</h2>
4900 <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;">
4901 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
4902 <div style="font-size: 1.2rem; font-weight: 600; color: var(--primary-blue);" id="arc-count">0</div>
4903 <div style="font-size: 0.7rem; color: var(--text-secondary);">Arc</div>
4904 </div>
4905 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
4906 <div style="font-size: 1.2rem; font-weight: 600; color: var(--primary-green);" id="rc-count">0</div>
4907 <div style="font-size: 0.7rem; color: var(--text-secondary);">Rc</div>
4908 </div>
4909 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
4910 <div style="font-size: 1.2rem; font-weight: 600; color: var(--primary-orange);" id="collections-count">0
4911 </div>
4912 <div style="font-size: 0.7rem; color: var(--text-secondary);">Collections</div>
4913 </div>
4914 </div>
4915 </div>
4916 </section>
4917
4918 <!-- Memory Operations Analysis (Moved to front as summary) -->
4919 <section class="card">
4920 <h2><i class="fa fa-exchange"></i> Memory Operations Analysis</h2>
4921 <div id="memoryOperations" style="padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
4922 <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;">
4923 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
4924 <div id="time-span" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-blue);">-</div>
4925 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Time Span</div>
4926 </div>
4927 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
4928 <div id="allocation-burst" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-orange);">-</div>
4929 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Alloc Burst</div>
4930 </div>
4931 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
4932 <div id="peak-concurrency" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-red);">-</div>
4933 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Peak Concurrency</div>
4934 </div>
4935 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
4936 <div id="thread-activity" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-green);">-</div>
4937 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Thread Activity</div>
4938 </div>
4939 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
4940 <div id="borrow-ops" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-blue);">-</div>
4941 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Borrow Ops</div>
4942 </div>
4943 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
4944 <div id="clone-ops" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-orange);">-</div>
4945 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Clone Ops</div>
4946 </div>
4947 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
4948 <div id="mut-ratio" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-red);">-</div>
4949 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Mut/Immut</div>
4950 </div>
4951 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
4952 <div id="avg-borrows" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-green);">-</div>
4953 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Avg/Alloc</div>
4954 </div>
4955 </div>
4956 </div>
4957 </section>
4958
4959 <!-- Enhanced 3D Memory Layout & Timeline Playback -->
4960 <section class="card">
4961 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap;">
4962 <h2 style="margin: 0;"><i class="fa fa-cube"></i> 3D Memory Layout Visualization</h2>
4963 <div style="display: flex; gap: 8px; flex-wrap: wrap;">
4964 <button id="toggle3DView" class="theme-toggle"
4965 style="background: var(--primary-green); font-size: 12px; padding: 6px 12px; white-space: nowrap;">
4966 <i class="fa fa-eye"></i>
4967 <span>3D View</span>
4968 </button>
4969 <button id="reset3DView" class="theme-toggle"
4970 style="background: var(--primary-orange); font-size: 12px; padding: 6px 12px; white-space: nowrap;">
4971 <i class="fa fa-refresh"></i>
4972 <span>Reset</span>
4973 </button>
4974 <button id="autoRotate3D" class="theme-toggle"
4975 style="background: var(--primary-blue); font-size: 12px; padding: 6px 12px; white-space: nowrap;">
4976 <i class="fa fa-rotate-right"></i>
4977 <span>Auto Rotate</span>
4978 </button>
4979 <button id="focusLargest" class="theme-toggle"
4980 style="background: var(--primary-red); font-size: 12px; padding: 6px 12px; white-space: nowrap;">
4981 <i class="fa fa-search-plus"></i>
4982 <span>Focus Largest</span>
4983 </button>
4984 </div>
4985 </div>
4986 <div id="memory3DContainer" class="memory-3d-container">
4987 <div class="memory-3d-info" id="memory3DInfo">
4988 Loading 3D visualization...
4989 </div>
4990 </div>
4991
4992 <!-- Timeline Playback Controls -->
4993 <div class="timeline-container">
4994 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
4995 <h3 style="margin: 0; font-size: 1rem;"><i class="fa fa-play-circle"></i> Memory Timeline Playback</h3>
4996 <div style="font-size: 0.8rem; color: var(--text-secondary);">
4997 <span id="timelineCurrentTime">0ms</span> / <span id="timelineTotalTime">0ms</span>
4998 </div>
4999 </div>
5000 <div class="timeline-slider" id="timelineSlider">
5001 <div class="timeline-progress" id="timelineProgress"></div>
5002 <div class="timeline-thumb" id="timelineThumb"></div>
5003 </div>
5004 <div class="timeline-controls">
5005 <button id="timelinePlay" class="timeline-btn">
5006 <i class="fa fa-play"></i> Play
5007 </button>
5008 <button id="timelinePause" class="timeline-btn" disabled>
5009 <i class="fa fa-pause"></i> Pause
5010 </button>
5011 <button id="timelineReset" class="timeline-btn">
5012 <i class="fa fa-refresh"></i> Reset
5013 </button>
5014 <button id="timelineStep" class="timeline-btn">
5015 <i class="fa fa-step-forward"></i> Step
5016 </button>
5017 <div style="display: flex; align-items: center; gap: 8px; margin-left: 16px;">
5018 <label style="font-size: 0.8rem; color: var(--text-secondary);">Speed:</label>
5019 <select id="timelineSpeed"
5020 style="background: var(--bg-secondary); color: var(--text-primary); border: 1px solid var(--border-light); border-radius: 4px; padding: 2px 6px; font-size: 0.8rem;">
5021 <option value="0.25">0.25x</option>
5022 <option value="0.5">0.5x</option>
5023 <option value="1" selected>1x</option>
5024 <option value="2">2x</option>
5025 <option value="4">4x</option>
5026 <option value="8">8x</option>
5027 </select>
5028 </div>
5029 </div>
5030 <div style="margin-top: 8px; font-size: 0.8rem; color: var(--text-secondary); text-align: center;">
5031 Active Allocations: <span id="timelineActiveCount"
5032 style="color: var(--primary-blue); font-weight: 600;">0</span>
5033 </div>
5034 </div>
5035 </section>
5036
5037 <!-- Unsafe Rust & FFI Memory Analysis (Enhanced with Integrated Risk Analysis) -->
5038 <section class="card" style="min-height: 700px;">
5039 <div class="unsafe-analysis-header">
5040 <div>
5041 <h2><i class="fa fa-shield-alt"></i> Unsafe Rust & FFI Memory Analysis</h2>
5042 <p style="color: var(--text-secondary); font-size: 0.9rem; margin-top: 4px;">
5043 Advanced memory passport system with cross-boundary visualization and integrated risk assessment
5044 </p>
5045 </div>
5046 <!-- Enhanced Filter Controls with Dynamic Sizing -->
5047 <div class="unsafe-filter-controls">
5048 <div class="size-controls">
5049 <button id="expandAnalysis" class="control-btn">
5050 <i class="fa fa-expand"></i>
5051 </button>
5052 <button id="compactAnalysis" class="control-btn">
5053 <i class="fa fa-compress"></i>
5054 </button>
5055 </div>
5056 <div class="unsafe-filter-tabs">
5057 <button class="unsafe-filter-tab active" data-filter="critical">
5058 🚨 Critical (<span id="unsafe-critical-count">0</span>)
5059 </button>
5060 <button class="unsafe-filter-tab" data-filter="leaks">
5061 💧 Leaks (<span id="unsafe-leak-count">0</span>)
5062 </button>
5063 <button class="unsafe-filter-tab" data-filter="cross-boundary">
5064 🔄 Cross-Boundary (<span id="unsafe-boundary-count">0</span>)
5065 </button>
5066 <button class="unsafe-filter-tab" data-filter="risk-assessment">
5067 ⚠️ Risk Analysis
5068 </button>
5069 <button class="unsafe-filter-tab" data-filter="all">
5070 📊 All Data (<span id="unsafe-total-count">0</span>)
5071 </button>
5072 </div>
5073 </div>
5074 </div>
5075
5076 <!-- Enhanced Cross-Boundary Swimlane Timeline -->
5077 <div class="enhanced-swimlane-container" id="enhancedSwimlaneContainer">
5078 <div class="swimlane-header">
5079 <h3><i class="fa fa-timeline"></i> Memory Passport Timeline</h3>
5080 <div class="timeline-controls">
5081 <button id="timelineZoomIn" class="timeline-control-btn">
5082 <i class="fa fa-search-plus"></i> Zoom In
5083 </button>
5084 <button id="timelineZoomOut" class="timeline-control-btn">
5085 <i class="fa fa-search-minus"></i> Zoom Out
5086 </button>
5087 <button id="timelineReset" class="timeline-control-btn">
5088 <i class="fa fa-refresh"></i> Reset View
5089 </button>
5090 </div>
5091 </div>
5092
5093 <div class="dual-swimlane">
5094 <!-- Rust Safety Domain -->
5095 <div class="swimlane rust-domain">
5096 <div class="domain-label">
5097 <div class="domain-icon">🦀</div>
5098 <div class="domain-info">
5099 <h4>Rust Safety Domain</h4>
5100 <p>Ownership & borrow checker controlled</p>
5101 </div>
5102 </div>
5103 <div class="timeline-track" id="rustTimelineTrack"></div>
5104 </div>
5105
5106 <!-- Timeline Axis -->
5107 <div class="timeline-axis" id="timelineAxis"></div>
5108
5109 <!-- C/FFI Domain -->
5110 <div class="swimlane ffi-domain">
5111 <div class="domain-label">
5112 <div class="domain-icon">⚡</div>
5113 <div class="domain-info">
5114 <h4>C/FFI Domain</h4>
5115 <p>Manual memory management</p>
5116 </div>
5117 </div>
5118 <div class="timeline-track" id="ffiTimelineTrack"></div>
5119 </div>
5120 </div>
5121
5122 <!-- Integrated Risk Analysis Section -->
5123 <div class="integrated-risk-section">
5124 <div class="risk-section-header">
5125 <h4><i class="fa fa-exclamation-triangle"></i> Safety Risk Analysis</h4>
5126 <div class="risk-summary-stats">
5127 <span class="risk-stat high-risk">
5128 <span id="high-risk-count">0</span> High
5129 </span>
5130 <span class="risk-stat medium-risk">
5131 <span id="medium-risk-count">0</span> Medium
5132 </span>
5133 <span class="risk-stat low-risk">
5134 <span id="low-risk-count">0</span> Low
5135 </span>
5136 </div>
5137 </div>
5138
5139 <!-- Risk Analysis Tabs -->
5140 <div class="risk-analysis-tabs">
5141 <button class="risk-tab active" data-view="table">
5142 <i class="fa fa-table"></i> Risk Items
5143 </button>
5144 <button class="risk-tab" data-view="patterns">
5145 <i class="fa fa-chart-pie"></i> Pattern Analysis
5146 </button>
5147 <button class="risk-tab" data-view="locations">
5148 <i class="fa fa-map-marker"></i> Code Locations
5149 </button>
5150 </div>
5151
5152 <!-- Risk Views Container -->
5153 <div class="risk-views-container">
5154 <!-- Risk Items Table View -->
5155 <div id="riskTableView" class="risk-view active">
5156 <div class="scroll" style="max-height: 200px;">
5157 <table>
5158 <thead>
5159 <tr>
5160 <th>Location</th>
5161 <th>Operation</th>
5162 <th>Risk Level</th>
5163 <th>Memory Size</th>
5164 <th>Assessment</th>
5165 <th>Actions</th>
5166 </tr>
5167 </thead>
5168 <tbody id="unsafeTable"></tbody>
5169 </table>
5170 </div>
5171 </div>
5172
5173 <!-- Pattern Analysis View -->
5174 <div id="riskPatternsView" class="risk-view" style="display: none;">
5175 <div id="riskPatternsChart" style="height: 200px; background: var(--bg-secondary); border-radius: 8px;">
5176 <div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">
5177 <i class="fa fa-chart-pie" style="font-size: 1.5rem; opacity: 0.5;"></i>
5178 <span style="margin-left: 1rem;">Risk pattern analysis loading...</span>
5179 </div>
5180 </div>
5181 </div>
5182
5183 <!-- Code Locations View -->
5184 <div id="riskLocationsView" class="risk-view" style="display: none;">
5185 <div id="riskLocationsHeatmap" style="height: 200px; background: var(--bg-secondary); border-radius: 8px;">
5186 <div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">
5187 <i class="fa fa-map" style="font-size: 1.5rem; opacity: 0.5;"></i>
5188 <span style="margin-left: 1rem;">Code location heatmap loading...</span>
5189 </div>
5190 </div>
5191 </div>
5192 </div>
5193 </div>
5194
5195 <!-- Enhanced Legend with Pattern Recognition -->
5196 <div class="enhanced-legend">
5197 <div class="legend-section">
5198 <h4>Event Types</h4>
5199 <div class="legend-items">
5200 <div class="legend-item">
5201 <span class="event-symbol rust-alloc">●</span>
5202 <span>Unsafe Rust Allocation</span>
5203 </div>
5204 <div class="legend-item">
5205 <span class="event-symbol ffi-alloc">●</span>
5206 <span>FFI C Allocation</span>
5207 </div>
5208 <div class="legend-item">
5209 <span class="event-symbol boundary-up">▲</span>
5210 <span>FFI → Rust Transfer</span>
5211 </div>
5212 <div class="legend-item">
5213 <span class="event-symbol boundary-down">▼</span>
5214 <span>Rust → FFI Transfer</span>
5215 </div>
5216 <div class="legend-item">
5217 <span class="event-symbol dealloc">✕</span>
5218 <span>Memory Deallocation</span>
5219 </div>
5220 </div>
5221 </div>
5222 <div class="legend-section">
5223 <h4>Risk Patterns</h4>
5224 <div class="legend-items">
5225 <div class="legend-item">
5226 <span class="risk-indicator high-risk"></span>
5227 <span>High Risk - Potential double-free</span>
5228 </div>
5229 <div class="legend-item">
5230 <span class="risk-indicator medium-risk"></span>
5231 <span>Medium Risk - Ownership unclear</span>
5232 </div>
5233 <div class="legend-item">
5234 <span class="risk-indicator leak-risk"></span>
5235 <span>Memory Leak - No deallocation</span>
5236 </div>
5237 </div>
5238 </div>
5239 </div>
5240 </div>
5241
5242 <!-- Memory Passport Modal -->
5243 <div id="memoryPassport" class="memory-passport-modal" style="display: none;">
5244 <div class="passport-content">
5245 <div class="passport-header">
5246 <h3><i class="fa fa-id-card"></i> Memory Passport</h3>
5247 <button class="passport-close">×</button>
5248 </div>
5249 <div class="passport-body" id="passportBody">
5250 <!-- Dynamic content populated by JavaScript -->
5251 </div>
5252 </div>
5253 </div>
5254
5255 <!-- Risk Action Modal -->
5256 <div id="riskActionModal" class="memory-passport-modal" style="display: none;">
5257 <div class="passport-content">
5258 <div class="passport-header">
5259 <h3><i class="fa fa-tools"></i> Risk Mitigation Actions</h3>
5260 <button class="passport-close" onclick="closeRiskActionModal()">×</button>
5261 </div>
5262 <div class="passport-body" id="riskActionBody">
5263 <!-- Dynamic content populated by JavaScript -->
5264 </div>
5265 </div>
5266 </div>
5267 </section>
5268
5269 <!-- Memory Analysis Dashboard module removed -->
5270
5271 <div class="card">
5272 <h2>Memory Over Time</h2>
5273 <div class="chart-container">
5274 <canvas id="timelineChart"></canvas>
5275 </div>
5276 <div style="margin-top:8px; font-size:12px; color: var(--text-secondary); display:flex; gap:8px; align-items:center;">
5277 <label style="display:flex; align-items:center; gap:6px; cursor:pointer;">
5278 <input id="toggleGrowthRate" type="checkbox" style="accent-color: var(--primary-green)">
5279 <span>Show Growth Rate</span>
5280 </label>
5281 <span style="opacity:0.8">Left Y: Cumulative memory, Right Y: Growth rate</span>
5282 </div>
5283 <!-- Integrated Key Metrics -->
5284 <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-top: 12px;">
5285 <div style="text-align: center; padding: 8px; background: var(--bg-secondary); border-radius: 6px;">
5286 <div style="font-size: 1rem; font-weight: 700; color: var(--primary-blue);" id="memoryFragmentation">0%</div>
5287 <div style="font-size: 0.7rem; color: var(--text-secondary);">Fragmentation</div>
5288 </div>
5289 <div style="text-align: center; padding: 8px; background: var(--bg-secondary); border-radius: 6px;">
5290 <div style="font-size: 1rem; font-weight: 700; color: var(--primary-green);" id="memoryEfficiency">0%</div>
5291 <div style="font-size: 0.7rem; color: var(--text-secondary);">Efficiency</div>
5292 </div>
5293 </div>
5294 </div>
5295 </section>
5296
5297 <!-- Memory Distribution Visualization -->
5298 <section class="card">
5299 <h2><i class="fa fa-chart-area"></i> Memory Distribution Visualization</h2>
5300 <!-- 动态尺寸控制 -->
5301 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
5302 <div style="display: flex; gap: 8px; align-items: center;">
5303 <label style="font-size: 0.8rem; color: var(--text-secondary);">Scale:</label>
5304 <input type="range" id="memoryDistScale" min="50" max="200" value="100"
5305 style="width: 80px; accent-color: var(--primary-blue);">
5306 <span id="memoryDistScaleValue" style="font-size: 0.8rem; color: var(--text-secondary); min-width: 30px;">100%</span>
5307 </div>
5308 <div style="display: flex; gap: 8px;">
5309 <button id="memoryDistFit" class="theme-toggle"
5310 style="background: var(--primary-green); font-size: 10px; padding: 4px 8px;">
5311 <i class="fa fa-expand-arrows-alt"></i>
5312 <span>Fit</span>
5313 </button>
5314 <button id="memoryDistReset" class="theme-toggle"
5315 style="background: var(--primary-orange); font-size: 10px; padding: 4px 8px;">
5316 <i class="fa fa-refresh"></i>
5317 <span>Reset</span>
5318 </button>
5319 </div>
5320 </div>
5321
5322 <div id="memoryDistributionViz"
5323 style="height: 200px; background: var(--bg-secondary); border-radius: 8px; position: relative; overflow: hidden; border: 1px solid var(--border-light);">
5324 <!-- Memory blocks visualization will be inserted here -->
5325 </div>
5326 </section>
5327
5328 <!-- Memory Analysis (wider layout) -->
5329 <section class="grid grid-2">
5330 <div class="card">
5331 <h2>Type Treemap</h2>
5332 <div id="treemap" class="chart-container"
5333 style="height: 360px; padding: 0; background: var(--bg-secondary); border: 1px solid var(--border-light); border-radius: 8px; overflow: hidden;">
5334 </div>
5335 </div>
5336 <div class="card">
5337 <h2><i class="fa fa-timeline"></i> Variable Lifecycle Visualization</h2>
5338 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
5339 <div style="display: flex; gap: 6px;">
5340 <button id="filter-heap" class="theme-toggle"
5341 style="background: var(--primary-orange); font-size: 10px; padding: 3px 6px;">
5342 <span>Heap</span>
5343 </button>
5344 <button id="filter-stack" class="theme-toggle"
5345 style="background: var(--primary-blue); font-size: 10px; padding: 3px 6px;">
5346 <span>Stack</span>
5347 </button>
5348 <button id="toggle-lifecycle" class="theme-toggle"
5349 style="background: var(--primary-green); font-size: 10px; padding: 3px 6px;">
5350 <span>All</span>
5351 </button>
5352 </div>
5353 <div style="display: flex; gap: 12px; font-size: 0.75rem; color: var(--text-secondary);">
5354 <span>Heap: <span id="heap-count-mini"
5355 style="color: var(--primary-orange); font-weight: 600;">-</span></span>
5356 <span>Stack: <span id="stack-count-mini"
5357 style="color: var(--primary-blue); font-weight: 600;">-</span></span>
5358 </div>
5359 </div>
5360 <div style="margin-bottom: 8px;">
5361 <button id="manual-init-btn"
5362 style="background: var(--primary-red); color: white; border: none; padding: 6px 12px; border-radius: 4px; font-size: 0.8rem; cursor: pointer;">
5363 🔄 Manual Initialize
5364 </button>
5365 <span id="init-status" style="margin-left: 8px; font-size: 0.8rem; color: var(--text-secondary);">Waiting for
5366 data...</span>
5367 </div>
5368 <div id="lifecycleVisualizationContainer" class="scroll" style="max-height: 320px; padding: 8px;">
5369 <!-- Variable lifecycle items will be inserted here -->
5370 </div>
5371 </div>
5372 </section>
5373
5374 <!-- Advanced Analysis -->
5375 <section class="grid grid-2">
5376 <div class="card">
5377 <h2><i class="fa fa-puzzle-piece"></i> Memory Fragmentation Analysis</h2>
5378 <div style="padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
5379 <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 16px;">
5380 <div style="text-align: center; padding: 12px; background: var(--bg-primary); border-radius: 6px;">
5381 <div style="font-size: 1.2rem; font-weight: 700; color: var(--primary-blue);" id="fragmentation-level">5.2%</div>
5382 <div style="font-size: 0.7rem; color: var(--text-secondary);">Fragmentation</div>
5383 </div>
5384 <div style="text-align: center; padding: 12px; background: var(--bg-primary); border-radius: 6px;">
5385 <div style="font-size: 1.2rem; font-weight: 700; color: var(--primary-green);" id="memory-utilization">94.8%</div>
5386 <div style="font-size: 0.7rem; color: var(--text-secondary);">Utilization</div>
5387 </div>
5388 </div>
5389 <div id="memoryFragmentation" style="height: 120px; background: var(--bg-primary); border: 1px solid var(--border-light); border-radius: 6px; padding: 8px;">
5390 <!-- Simple fragmentation visualization -->
5391 <div style="display: flex; align-items: center; height: 100%; gap: 2px;">
5392 <div style="height: 100%; width: 15%; background: var(--primary-blue); border-radius: 2px; opacity: 0.8;"></div>
5393 <div style="height: 60%; width: 8%; background: var(--primary-orange); border-radius: 2px; opacity: 0.6;"></div>
5394 <div style="height: 80%; width: 12%; background: var(--primary-blue); border-radius: 2px; opacity: 0.8;"></div>
5395 <div style="height: 30%; width: 5%; background: var(--primary-red); border-radius: 2px; opacity: 0.5;"></div>
5396 <div style="height: 100%; width: 20%; background: var(--primary-blue); border-radius: 2px; opacity: 0.8;"></div>
5397 <div style="height: 40%; width: 6%; background: var(--primary-orange); border-radius: 2px; opacity: 0.6;"></div>
5398 <div style="height: 90%; width: 18%; background: var(--primary-blue); border-radius: 2px; opacity: 0.8;"></div>
5399 <div style="height: 25%; width: 4%; background: var(--primary-red); border-radius: 2px; opacity: 0.5;"></div>
5400 <div style="height: 70%; width: 12%; background: var(--primary-blue); border-radius: 2px; opacity: 0.8;"></div>
5401 </div>
5402 <div style="text-align: center; font-size: 0.7rem; color: var(--text-secondary); margin-top: 4px;">
5403 Memory Layout: <span style="color: var(--primary-blue);">■ Allocated</span>
5404 <span style="color: var(--primary-orange);">■ Small Gaps</span>
5405 <span style="color: var(--primary-red);">■ Large Gaps</span>
5406 </div>
5407 </div>
5408 </div>
5409 </div>
5410 <div class="card">
5411 <h2><i class="fa fa-fire"></i> Borrow Activity Heatmap</h2>
5412 <div id="borrowPatternChart" style="height: 200px;"></div>
5413 </div>
5414 </section>
5415
5416 <!-- Memory Allocation Details (moved from Advanced Analysis) -->
5417 <section class="grid grid-2">
5418 <div class="card">
5419 <h2><i class="fa fa-table"></i> Memory Allocation Details</h2>
5420 <div class="scroll">
5421 <table>
5422 <thead>
5423 <tr>
5424 <th>Variable</th>
5425 <th>Type</th>
5426 <th>Size</th>
5427 <th>Status</th>
5428 </tr>
5429 </thead>
5430 <tbody id="allocTable"></tbody>
5431 </table>
5432 </div>
5433 </div>
5434
5435 <div class="card">
5436 <h2><i class="fa fa-info-circle"></i> System Status</h2>
5437 <div style="padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
5438 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
5439 <div style="text-align: center; padding: 12px; background: var(--bg-primary); border-radius: 6px;">
5440 <div style="font-size: 1.2rem; font-weight: 700; color: var(--primary-blue);" id="dashboard-status">Ready</div>
5441 <div style="font-size: 0.7rem; color: var(--text-secondary);">Dashboard</div>
5442 </div>
5443 <div style="text-align: center; padding: 12px; background: var(--bg-primary); border-radius: 6px;">
5444 <div style="font-size: 1.2rem; font-weight: 700; color: var(--primary-green);" id="data-status">Loading</div>
5445 <div style="font-size: 0.7rem; color: var(--text-secondary);">Data Status</div>
5446 </div>
5447 </div>
5448 </div>
5449 </div>
5450 </section>
5451
5452
5453 <!-- Variable Relationships (Full Width) -->
5454 <section class="card">
5455 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap;">
5456 <h2 style="margin: 0;"><i class="fa fa-share-alt"></i> Variable Relationships Graph</h2>
5457 <div style="display: flex; gap: 8px; flex-wrap: nowrap;">
5458 <button id="reset-zoom" class="theme-toggle"
5459 style="background: var(--primary-orange); font-size: 12px; padding: 6px 12px; white-space: nowrap;">
5460 <i class="fa fa-expand"></i>
5461 <span>Reset View</span>
5462 </button>
5463 <button id="auto-layout" class="theme-toggle"
5464 style="background: var(--primary-green); font-size: 12px; padding: 6px 12px; white-space: nowrap;">
5465 <i class="fa fa-magic"></i>
5466 <span>Auto Layout</span>
5467 </button>
5468 </div>
5469 </div>
5470 <div id="graph"
5471 style="height: 400px; background: var(--bg-secondary); border: 1px solid var(--border-light); border-radius: 8px; position: relative; overflow: hidden;">
5472 </div>
5473 </section>
5474
5475 </div>
5476
5477 <script>
5478 // Global safe update function - must be defined first
5479 function safeUpdateElement(id, value, defaultValue = '-') {
5480 try {
5481 const el = document.getElementById(id);
5482 if (el) {
5483 el.textContent = value;
5484 return true;
5485 } else {
5486 console.warn(`Element with ID '${id}' not found`);
5487 return false;
5488 }
5489 } catch (error) {
5490 console.error(`Error updating element ${id}:`, error);
5491 return false;
5492 }
5493 }
5494
5495 // Enhanced Lifecycle Visualization Functions
5496 function inferAllocationType(typeName) {
5497 if (!typeName) return 'unknown';
5498
5499 const heapTypes = ['Box', 'Vec', 'String', 'HashMap', 'BTreeMap', 'Arc', 'Rc', 'alloc::', 'std::collections'];
5500 const stackTypes = ['i32', 'i64', 'f32', 'f64', 'bool', 'char', 'usize', 'isize', 'u8', 'u16', 'u32', 'u64'];
5501
5502 for (const heapType of heapTypes) {
5503 if (typeName.includes(heapType)) return 'heap';
5504 }
5505
5506 for (const stackType of stackTypes) {
5507 if (typeName.includes(stackType)) return 'stack';
5508 }
5509
5510 if (typeName.includes('*') || typeName.includes('&')) return 'heap';
5511
5512 return 'unknown';
5513 }
5514
5515 function formatTimestamp(timestamp) {
5516 if (!timestamp || isNaN(timestamp) || timestamp <= 0) return 'N/A';
5517
5518 // Handle different timestamp formats
5519 let timeInMs;
5520 if (timestamp > 1e15) {
5521 // Nanoseconds
5522 timeInMs = timestamp / 1000000;
5523 } else if (timestamp > 1e12) {
5524 // Microseconds
5525 timeInMs = timestamp / 1000;
5526 } else {
5527 // Already in milliseconds
5528 timeInMs = timestamp;
5529 }
5530
5531 const date = new Date(timeInMs);
5532 if (isNaN(date.getTime())) return 'N/A';
5533
5534 // Format as relative time if recent, otherwise absolute time
5535 const now = Date.now();
5536 const diffMs = Math.abs(now - timeInMs);
5537
5538 if (diffMs < 1000) {
5539 return `${diffMs.toFixed(0)}ms ago`;
5540 } else if (diffMs < 60000) {
5541 return `${(diffMs / 1000).toFixed(1)}s ago`;
5542 } else {
5543 return date.toLocaleTimeString() + '.' + String(date.getMilliseconds()).padStart(3, '0');
5544 }
5545 }
5546
5547 function formatTimestampSafe(timestamp, fallbackIndex) {
5548 if (!timestamp || isNaN(timestamp) || timestamp <= 0) {
5549 // Return synthetic time based on index
5550 return `T+${(fallbackIndex * 1).toFixed(1)}ms`;
5551 }
5552
5553 // Handle nanosecond timestamps (typical format from Rust)
5554 let timeInMs;
5555 if (timestamp > 1e15) {
5556 // Nanoseconds (typical Rust timestamp format)
5557 timeInMs = timestamp / 1000000;
5558 } else if (timestamp > 1e12) {
5559 // Microseconds
5560 timeInMs = timestamp / 1000;
5561 } else if (timestamp > 1e9) {
5562 // Milliseconds
5563 timeInMs = timestamp;
5564 } else {
5565 // Seconds to milliseconds
5566 timeInMs = timestamp * 1000;
5567 }
5568
5569 // Convert to relative time from program start
5570 // Use the first allocation timestamp as reference if available
5571 const firstTimestamp = window.analysisData?.memory_analysis?.allocations?.[0]?.timestamp_alloc || timestamp;
5572 const relativeTimeMs = (timestamp - firstTimestamp) / 1000000;
5573
5574 if (Math.abs(relativeTimeMs) < 0.001) {
5575 return 'T+0.00ms';
5576 } else if (relativeTimeMs >= 0) {
5577 return `T+${relativeTimeMs.toFixed(2)}ms`;
5578 } else {
5579 return `T${relativeTimeMs.toFixed(2)}ms`;
5580 }
5581 }
5582
5583 function calculateDropTime(allocTime, lifetimeMs) {
5584 if (!allocTime || lifetimeMs === undefined || isNaN(allocTime) || isNaN(lifetimeMs)) return null;
5585 return allocTime + (lifetimeMs * 1000000); // Convert ms to nanoseconds and add
5586 }
5587
5588 function formatLifetime(lifetimeMs) {
5589 if (lifetimeMs === undefined || lifetimeMs === null) return 'N/A';
5590 if (lifetimeMs < 1) return `${(lifetimeMs * 1000).toFixed(1)}μs`;
5591 if (lifetimeMs < 1000) return `${lifetimeMs.toFixed(1)}ms`;
5592 return `${(lifetimeMs / 1000).toFixed(2)}s`;
5593 }
5594
5595 function formatBytes(bytes) {
5596 if (bytes === 0) return '0 B';
5597 const k = 1024;
5598 const sizes = ['B', 'KB', 'MB', 'GB'];
5599 const i = Math.floor(Math.log(bytes) / Math.log(k));
5600 return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
5601 }
5602
5603 function initEnhancedLifecycleVisualization() {
5604 // Debug what's available
5605 console.log('Checking data availability...');
5606 console.log('window.analysisData exists:', !!window.analysisData);
5607 console.log('window.analysisData type:', typeof window.analysisData);
5608
5609 if (window.analysisData) {
5610 console.log('window.analysisData keys:', Object.keys(window.analysisData));
5611 }
5612
5613 // Try multiple data structure paths
5614 let allocations = null;
5615
5616 if (window.analysisData) {
5617 // Method 1: Direct allocations (old structure)
5618 if (window.analysisData.allocations && Array.isArray(window.analysisData.allocations)) {
5619 allocations = window.analysisData.allocations;
5620 console.log('✅ Found allocations directly:', allocations.length);
5621 }
5622 // Method 2: Memory analysis structure (new structure)
5623 else if (window.analysisData.memory_analysis && window.analysisData.memory_analysis.allocations) {
5624 allocations = window.analysisData.memory_analysis.allocations;
5625 console.log('✅ Found allocations in memory_analysis:', allocations.length);
5626 }
5627 // Method 3: Check all keys for allocations
5628 else {
5629 for (const [key, value] of Object.entries(window.analysisData)) {
5630 if (value && typeof value === 'object' && value.allocations && Array.isArray(value.allocations)) {
5631 allocations = value.allocations;
5632 console.log('✅ Found allocations in', key + ':', allocations.length);
5633 break;
5634 }
5635 }
5636 }
5637 }
5638
5639 if (!allocations || !Array.isArray(allocations) || allocations.length === 0) {
5640 console.warn('No allocation data found in window.analysisData');
5641
5642 // Fallback: Try to get data from other global variables that might be set by the dashboard
5643 if (typeof getAllocations === 'function') {
5644 try {
5645 allocations = getAllocations();
5646 console.log('✅ Got allocations from getAllocations():', allocations.length);
5647 } catch (e) {
5648 console.warn('getAllocations() failed:', e);
5649 }
5650 }
5651
5652 // Another fallback: Check if data is in a different global variable
5653 if (!allocations && window.memoryAnalysisData) {
5654 if (window.memoryAnalysisData.allocations) {
5655 allocations = window.memoryAnalysisData.allocations;
5656 console.log('✅ Got allocations from memoryAnalysisData:', allocations.length);
5657 }
5658 }
5659
5660 // Final fallback: Try to extract from existing DOM elements
5661 if (!allocations) {
5662 const existingTable = document.getElementById('allocTable');
5663 if (existingTable && existingTable.children.length > 1) {
5664 console.log('Trying to extract data from existing table...');
5665 // This is a last resort - we'll create dummy data based on table rows
5666 const rows = existingTable.querySelectorAll('tbody tr');
5667 if (rows.length > 0) {
5668 allocations = Array.from(rows).map((row, index) => {
5669 const cells = row.querySelectorAll('td');
5670 return {
5671 var_name: cells[0]?.textContent || `var_${index}`,
5672 type_name: cells[1]?.textContent || 'unknown',
5673 size: parseInt(cells[2]?.textContent) || 0,
5674 timestamp_alloc: Date.now() * 1000000 + index * 1000000,
5675 lifetime_ms: parseFloat(cells[3]?.textContent) || 1.0,
5676 is_leaked: cells[4]?.textContent?.includes('Yes') || false
5677 };
5678 });
5679 console.log('✅ Extracted', allocations.length, 'allocations from existing table');
5680 }
5681 }
5682 }
5683
5684 if (!allocations || allocations.length === 0) {
5685 console.error('❌ No allocation data available from any source');
5686 return;
5687 }
5688 }
5689
5690 if (allocations.length === 0) {
5691 console.warn('No allocation data found');
5692 return;
5693 }
5694
5695 console.log('✅ Found', allocations.length, 'allocations for enhanced visualization');
5696 console.log('Sample allocation:', allocations[0]);
5697
5698 // Statistics
5699 let heapCount = 0, stackCount = 0, unknownCount = 0;
5700 let totalLifetime = 0, validLifetimes = 0;
5701 let totalMemory = 0;
5702
5703 allocations.forEach(alloc => {
5704 const type = inferAllocationType(alloc.type_name);
5705 if (type === 'heap') heapCount++;
5706 else if (type === 'stack') stackCount++;
5707 else unknownCount++;
5708
5709 // Check multiple possible lifetime fields and ensure they're valid numbers
5710 const lifetime = alloc.lifetime_ms || alloc.lifetime || 0;
5711 if (lifetime !== undefined && lifetime !== null && !isNaN(lifetime) && lifetime > 0) {
5712 totalLifetime += lifetime;
5713 validLifetimes++;
5714 }
5715
5716 totalMemory += alloc.size || 0;
5717 });
5718
5719 console.log('Statistics calculated:', { heapCount, stackCount, totalLifetime, validLifetimes });
5720
5721 // Update mini counters
5722 const heapCountMini = document.getElementById('heap-count-mini');
5723 const stackCountMini = document.getElementById('stack-count-mini');
5724
5725 if (heapCountMini) {
5726 safeUpdateElement('heap-count-mini', heapCount);
5727 console.log('Updated heap-count-mini:', heapCount);
5728 } else {
5729 console.warn('heap-count-mini element not found');
5730 }
5731
5732 if (stackCountMini) {
5733 safeUpdateElement('stack-count-mini', stackCount);
5734 console.log('Updated stack-count-mini:', stackCount);
5735 } else {
5736 console.warn('stack-count-mini element not found');
5737 }
5738
5739 // Create lifecycle visualization
5740 console.log('Calling createLifecycleVisualization...');
5741 createLifecycleVisualization(allocations);
5742
5743 // Update enhanced statistics
5744 console.log('Calling updateEnhancedStatistics...');
5745 updateEnhancedStatistics(allocations, heapCount, stackCount, validLifetimes, totalLifetime);
5746
5747 // Setup filters
5748 console.log('Calling setupLifecycleFilters...');
5749 setupLifecycleFilters(allocations);
5750
5751 console.log('✅ All enhanced features processing completed');
5752 }
5753
5754 function createLifecycleVisualization(allocations) {
5755 console.log('createLifecycleVisualization called with', allocations.length, 'allocations');
5756 const container = document.getElementById('lifecycleVisualizationContainer');
5757 if (!container) {
5758 console.error('❌ Lifecycle visualization container not found in DOM');
5759 return;
5760 }
5761 console.log('✅ Found lifecycleVisualizationContainer, creating visualization...');
5762 container.innerHTML = '';
5763
5764 // Calculate timeline bounds
5765 const timestamps = allocations.map(a => a.timestamp_alloc).filter(t => t);
5766 const minTime = Math.min(...timestamps);
5767 const maxTime = Math.max(...timestamps);
5768 const timeRange = maxTime - minTime || 1;
5769
5770 allocations.forEach((alloc, index) => {
5771 const allocType = inferAllocationType(alloc.type_name);
5772 let startTime = alloc.timestamp_alloc || alloc.timestamp || Date.now() * 1000000;
5773 const lifetime = alloc.lifetime_ms || alloc.lifetime || 0;
5774 let endTime = startTime + (lifetime * 1000000); // Convert ms to nanoseconds
5775
5776 // Calculate lifetime from timestamps if not provided
5777 let calculatedLifetime = 0;
5778 if (alloc.timestamp_dealloc && alloc.timestamp_alloc) {
5779 calculatedLifetime = (alloc.timestamp_dealloc - alloc.timestamp_alloc) / 1000000; // Convert to ms
5780 } else if (alloc.lifetime_ms) {
5781 calculatedLifetime = alloc.lifetime_ms;
5782 } else {
5783 // Default lifetime for active allocations
5784 calculatedLifetime = 10; // 10ms default
5785 }
5786
5787 // Use actual timestamps if available, otherwise create synthetic ones
5788 if (alloc.timestamp_alloc && !isNaN(alloc.timestamp_alloc)) {
5789 startTime = alloc.timestamp_alloc;
5790 endTime = alloc.timestamp_dealloc || (startTime + calculatedLifetime * 1000000);
5791 } else {
5792 // Create synthetic timeline based on allocation order
5793 startTime = minTime + (index * (timeRange / allocations.length));
5794 endTime = startTime + (calculatedLifetime * 1000000);
5795 }
5796
5797 // Debug and validate time data
5798 console.log('Debug allocation:', {
5799 var_name: alloc.var_name,
5800 timestamp_alloc: alloc.timestamp_alloc,
5801 timestamp_dealloc: alloc.timestamp_dealloc,
5802 calculatedLifetime: calculatedLifetime,
5803 startTime: startTime,
5804 endTime: endTime,
5805 isValidStart: !isNaN(startTime) && startTime > 0,
5806 isValidEnd: !isNaN(endTime) && endTime > 0
5807 });
5808
5809 // Calculate positions and widths with bounds checking
5810 let startPercent = ((startTime - minTime) / timeRange) * 100;
5811 let endPercent = ((endTime - minTime) / timeRange) * 100;
5812
5813 // Ensure values are within bounds
5814 startPercent = Math.max(0, Math.min(startPercent, 100));
5815 endPercent = Math.max(startPercent, Math.min(endPercent, 100));
5816
5817 let width = endPercent - startPercent;
5818 width = Math.max(width, 2); // Minimum 2% width
5819 width = Math.min(width, 100 - startPercent); // Don't exceed container
5820
5821 // Create lifecycle item
5822 const item = document.createElement('div');
5823 item.className = `lifecycle-item ${allocType}`;
5824 item.setAttribute('data-type', allocType);
5825
5826 // Enhanced solid colors for better visibility
5827 const barColor = allocType === 'heap' ? '#ff6b35' :
5828 allocType === 'stack' ? '#4dabf7' : '#868e96';
5829 const barGradient = allocType === 'heap' ? '#ff6b35' :
5830 allocType === 'stack' ? '#4dabf7' :
5831 '#868e96';
5832
5833 item.innerHTML = `
5834 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
5835 <div style="display: flex; align-items: center; gap: 8px;">
5836 <span style="font-weight: 600; font-size: 0.9rem; min-width: 120px;">${alloc.var_name || 'unnamed'}</span>
5837 <span class="allocation-type type-${allocType}">${allocType}</span>
5838 </div>
5839 <div style="display: flex; align-items: center; gap: 8px; font-size: 0.75rem; color: var(--text-secondary);">
5840 <span>${formatBytes(alloc.size || 0)}</span>
5841 <span>${formatLifetime(lifetime)}</span>
5842 </div>
5843 </div>
5844
5845 <!-- Enhanced Timeline Progress Bar -->
5846 <div style="position: relative; height: 24px; background: linear-gradient(90deg, #e8eaed, #ddd); border-radius: 12px; margin: 10px 0; border: 1px solid #bbb; box-shadow: inset 0 1px 2px rgba(0,0,0,0.08);">
5847
5848 <!-- Variable active period with enhanced gradient -->
5849 <div style="position: absolute; top: 1px; left: ${startPercent}%; width: ${width}%; height: calc(100% - 2px);
5850 background: ${barGradient};
5851 border-radius: 11px;
5852 box-shadow: 0 2px 8px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.3);
5853 transition: all 0.3s ease;
5854 position: relative;
5855 overflow: hidden;">
5856
5857 <!-- Animated shine effect -->
5858 <div style="position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
5859 background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
5860 animation: shine 2s infinite;"></div>
5861 </div>
5862
5863 ${alloc.is_leaked ? `
5864 <!-- Leaked indicator -->
5865 <div style="position: absolute; top: -3px; right: 2px; width: 20px; height: 30px;
5866 background: linear-gradient(45deg, #ff4757, #ff3742);
5867 border-radius: 2px;
5868 box-shadow: 0 2px 4px rgba(0,0,0,0.3);
5869 display: flex; align-items: center; justify-content: center;
5870 font-size: 10px; color: white; font-weight: bold;">⚠</div>
5871 ` : ''}
5872 </div>
5873
5874 <!-- Time info -->
5875 <div style="display: flex; justify-content: space-between; font-size: 0.7rem; color: var(--text-secondary); font-family: monospace;">
5876 <span>Start: ${formatTimestampSafe(startTime, index)}</span>
5877 <span>${alloc.is_leaked ? 'LEAKED' : (formatTimestampSafe(endTime, index + 1) !== 'N/A' ? 'End: ' + formatTimestampSafe(endTime, index + 1) : 'Active')}</span>
5878 </div>
5879 `;
5880
5881 // Enhanced hover effect and styling
5882 item.style.cssText += `
5883 margin-bottom: 18px;
5884 padding: 16px;
5885 background: linear-gradient(135deg, var(--bg-primary), #fafbfc);
5886 border-radius: 12px;
5887 border-left: 5px solid ${barColor};
5888 transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
5889 cursor: pointer;
5890 box-shadow: 0 2px 4px rgba(0,0,0,0.05);
5891 `;
5892
5893 item.addEventListener('mouseenter', () => {
5894 item.style.transform = 'translateX(8px) translateY(-2px)';
5895 item.style.boxShadow = '0 8px 25px rgba(0,0,0,0.15)';
5896 item.style.background = `linear-gradient(135deg, var(--bg-primary), ${barColor}08)`;
5897 });
5898
5899 item.addEventListener('mouseleave', () => {
5900 item.style.transform = 'translateX(0) translateY(0)';
5901 item.style.boxShadow = '0 2px 4px rgba(0,0,0,0.05)';
5902 item.style.background = 'linear-gradient(135deg, var(--bg-primary), #fafbfc)';
5903 });
5904
5905 container.appendChild(item);
5906 });
5907 }
5908
5909 function updateEnhancedStatistics(allocations, heapCount, stackCount, validLifetimes, totalLifetime) {
5910 console.log('Updating enhanced statistics...');
5911
5912 // Update Enhanced Memory Statistics
5913 const totalAllocsEnhanced = document.getElementById('total-allocs-enhanced');
5914 const heapStackRatio = document.getElementById('heap-stack-ratio');
5915 const avgLifetimeEnhanced = document.getElementById('avg-lifetime-enhanced');
5916 const memoryEfficiency = document.getElementById('memory-efficiency');
5917
5918 if (totalAllocsEnhanced) {
5919 safeUpdateElement('total-allocs-enhanced', allocations.length);
5920 console.log('Updated total-allocs-enhanced:', allocations.length);
5921 }
5922
5923 // Safe DOM updates with enhanced error handling
5924 if (heapStackRatio) {
5925 try {
5926 const ratio = stackCount > 0 ? (heapCount / stackCount).toFixed(1) : heapCount;
5927 safeUpdateElement('heap-stack-ratio', ratio + ':1');
5928 console.log('Updated heap-stack-ratio:', ratio + ':1');
5929 } catch (error) {
5930 console.error('Error updating heap-stack-ratio:', error);
5931 }
5932 }
5933
5934 if (avgLifetimeEnhanced) {
5935 try {
5936 const avgLifetime = validLifetimes > 0 ? formatLifetime(totalLifetime / validLifetimes) : 'N/A';
5937 safeUpdateElement('avg-lifetime-enhanced', avgLifetime);
5938 console.log('Updated avg-lifetime-enhanced:', avgLifetime);
5939 } catch (error) {
5940 console.error('Error updating avg-lifetime-enhanced:', error);
5941 }
5942 }
5943
5944 const memoryEfficiencyEl = document.getElementById('memory-efficiency');
5945 if (memoryEfficiencyEl) {
5946 try {
5947 const efficiency = allocations.length > 0 ? ((allocations.length - allocations.filter(a => a.is_leaked).length) / allocations.length * 100).toFixed(0) : 0;
5948 safeUpdateElement('memory-efficiency', efficiency + '%');
5949 console.log('Updated memory-efficiency:', efficiency + '%');
5950 } catch (error) {
5951 console.error('Error updating memory-efficiency:', error);
5952 }
5953 }
5954 }
5955
5956
5957 function setupLifecycleFilters(allocations) {
5958 const heapBtn = document.getElementById('filter-heap');
5959 const stackBtn = document.getElementById('filter-stack');
5960 const allBtn = document.getElementById('toggle-lifecycle');
5961
5962 // Check if all buttons exist
5963 if (!heapBtn || !stackBtn || !allBtn) {
5964 console.warn('Some filter buttons not found');
5965 return;
5966 }
5967
5968 let currentFilter = 'all';
5969
5970 function applyFilter(filter) {
5971 currentFilter = filter;
5972 const items = document.querySelectorAll('.lifecycle-item');
5973
5974 // Update button states
5975 [heapBtn, stackBtn, allBtn].forEach(btn => btn.style.opacity = '0.6');
5976
5977 if (filter === 'heap') {
5978 heapBtn.style.opacity = '1';
5979 items.forEach(item => {
5980 item.style.display = item.getAttribute('data-type') === 'heap' ? 'block' : 'none';
5981 });
5982 } else if (filter === 'stack') {
5983 stackBtn.style.opacity = '1';
5984 items.forEach(item => {
5985 item.style.display = item.getAttribute('data-type') === 'stack' ? 'block' : 'none';
5986 });
5987 } else {
5988 allBtn.style.opacity = '1';
5989 items.forEach(item => {
5990 item.style.display = 'block';
5991 });
5992 }
5993 }
5994
5995 heapBtn.addEventListener('click', () => applyFilter('heap'));
5996 stackBtn.addEventListener('click', () => applyFilter('stack'));
5997 allBtn.addEventListener('click', () => applyFilter('all'));
5998
5999 // Initialize
6000 applyFilter('all');
6001 }
6002
6003 // Initialize enhanced features when DOM is loaded
6004 function initEnhancedFeatures() {
6005 try {
6006 initEnhancedLifecycleVisualization();
6007 } catch (error) {
6008 console.error('Error initializing enhanced lifecycle visualization:', error);
6009 }
6010 }
6011
6012 // Safe initialization wrapper with duplicate prevention
6013 let enhancedInitialized = false;
6014 function safeInitEnhanced() {
6015 if (enhancedInitialized) {
6016 return; // Already initialized
6017 }
6018
6019 try {
6020 initEnhancedFeatures();
6021 enhancedInitialized = true;
6022 console.log('✅ Enhanced features initialized successfully');
6023 } catch (error) {
6024 console.warn('Enhanced features initialization failed:', error);
6025 }
6026 }
6027
6028 {{JS_CONTENT}}
6029
6030 // Override problematic functions AFTER JS_CONTENT loads
6031 window.updateLifecycleStatistics = function () {
6032 // Safe override - prevent errors from missing DOM elements
6033 try {
6034 // Try to find and update elements safely
6035 const elements = ['active-vars', 'freed-vars', 'leaked-vars', 'avg-lifetime-stat'];
6036 elements.forEach(id => {
6037 const el = document.getElementById(id);
6038 safeUpdateElement(id, '-');
6039 });
6040 } catch (e) {
6041 // Silently ignore errors
6042 }
6043 };
6044
6045 // Override other potential problem functions
6046 window.updateKPIMetrics = function () { return; };
6047 window.populateLifetimeTable = function () { return; };
6048 window.updateMemoryStats = function () { return; };
6049
6050 // Manual initialization function for testing
6051 function manualInitialize() {
6052 const statusEl = document.getElementById('init-status');
6053 safeUpdateElement('init-status', 'Initializing...');
6054
6055 console.log('🔄 Manual initialization triggered');
6056 console.log('window.analysisData:', window.analysisData);
6057
6058 if (window.analysisData && window.analysisData.memory_analysis && window.analysisData.memory_analysis.allocations) {
6059 console.log('✅ Data found, calling initEnhancedLifecycleVisualization...');
6060 initEnhancedLifecycleVisualization();
6061 safeUpdateElement('init-status', 'Initialized successfully!');
6062 } else {
6063 console.warn('❌ No data found, trying to load...');
6064 safeUpdateElement('init-status', 'Loading data...');
6065
6066 // Try to load data manually
6067 fetch('./large_scale_user_memory_analysis.json')
6068 .then(response => response.json())
6069 .then(memoryData => {
6070 console.log('✅ Manually loaded data:', memoryData);
6071 window.analysisData = {
6072 memory_analysis: memoryData
6073 };
6074 initEnhancedLifecycleVisualization();
6075 safeUpdateElement('init-status', 'Data loaded and initialized!');
6076 })
6077 .catch(error => {
6078 console.error('❌ Failed to load data:', error);
6079 safeUpdateElement('init-status', 'Failed to load data');
6080 });
6081 }
6082 }
6083
6084 // Wait for all scripts to load, then initialize
6085 function waitForDataAndInit() {
6086 if (window.analysisData && window.analysisData.memory_analysis && window.analysisData.memory_analysis.allocations) {
6087 safeUpdateElement('data-status', 'Processing');
6088
6089 try {
6090 safeInitEnhanced();
6091
6092 // Auto-load Safety Risk Analysis when data is ready
6093 setTimeout(() => {
6094 console.log('🛡️ Data ready - auto-loading Safety Risk Analysis...');
6095 try {
6096 initializeEnhancedUnsafeAnalysis();
6097 loadSafetyRisks();
6098 console.log('✅ Safety Risk Analysis loaded automatically');
6099 } catch (riskError) {
6100 console.error('❌ Error auto-loading safety risks:', riskError);
6101 }
6102 }, 500);
6103
6104 // Initialize enhanced visualization features
6105 if (window.enhancedVisualizer) {
6106 setTimeout(() => {
6107 console.log('Initializing enhanced visualizer...');
6108 window.enhancedVisualizer.init();
6109 window.enhancedVisualizer.initializeWithData(window.analysisData);
6110 console.log('Enhanced visualizer initialized');
6111 safeUpdateElement('data-status', 'Ready');
6112 }, 1000); // Give more time for DOM elements to be ready
6113 } else {
6114 safeUpdateElement('data-status', 'Ready');
6115 }
6116 } catch (error) {
6117 console.error('❌ Error during data initialization:', error);
6118 safeUpdateElement('data-status', 'Error');
6119 }
6120 } else {
6121 setTimeout(waitForDataAndInit, 200);
6122 }
6123 }
6124
6125
6126 // Initialize enhanced features after everything loads
6127 // Theme System Functions
6128 function initializeThemeSystem() {
6129 console.log('🎨 Initializing theme system...');
6130
6131 const themeToggle = document.getElementById('theme-toggle');
6132 const htmlElement = document.documentElement;
6133
6134 // Load saved theme or default to light
6135 const savedTheme = localStorage.getItem('theme') || 'light';
6136 htmlElement.className = savedTheme;
6137
6138 if (themeToggle) {
6139 // Update button text based on current theme
6140 updateThemeButtonText(savedTheme);
6141
6142 themeToggle.addEventListener('click', function() {
6143 const currentTheme = htmlElement.className;
6144 const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
6145
6146 htmlElement.className = newTheme;
6147 localStorage.setItem('theme', newTheme);
6148 updateThemeButtonText(newTheme);
6149
6150 console.log('🎨 Theme switched to:', newTheme);
6151 });
6152 }
6153 }
6154
6155 function updateThemeButtonText(theme) {
6156 const themeToggle = document.getElementById('theme-toggle');
6157 if (themeToggle) {
6158 themeToggle.innerHTML = theme === 'dark'
6159 ? '<i class="fa fa-sun-o"></i>'
6160 : '<i class="fa fa-moon-o"></i>';
6161 themeToggle.title = theme === 'dark' ? 'Switch to Light Mode' : 'Switch to Dark Mode';
6162 }
6163 }
6164
6165 document.addEventListener('DOMContentLoaded', function () {
6166 console.log('🚀 Dashboard initialization started');
6167
6168 // Initialize theme system first
6169 initializeThemeSystem();
6170
6171 // Update status indicators
6172 safeUpdateElement('dashboard-status', 'Initializing');
6173 safeUpdateElement('data-status', 'Loading');
6174
6175 try {
6176 // Setup manual initialize button
6177 const manualBtn = document.getElementById('manual-init-btn');
6178 if (manualBtn) {
6179 manualBtn.addEventListener('click', manualInitialize);
6180 }
6181
6182 // Load safety risks after initialization with longer delay
6183 setTimeout(function() {
6184 try {
6185 console.log('🛡️ Auto-loading safety risks on dashboard initialization...');
6186 loadSafetyRisks();
6187 // Also ensure unsafe analysis is initialized
6188 if (window.analysisData) {
6189 initializeEnhancedUnsafeAnalysis();
6190 }
6191 } catch (error) {
6192 console.error('❌ Error loading safety risks:', error);
6193 }
6194 }, 1500); // Increased delay to ensure data is loaded
6195
6196 // Start checking for data immediately
6197 waitForDataAndInit();
6198
6199 // Initialize dashboard functions if data is already available
6200 setTimeout(() => {
6201 if (window.analysisData && window.analysisData.memory_analysis && window.analysisData.memory_analysis.allocations) {
6202 console.log('🎯 Data ready - calling initDashboard...');
6203 try {
6204 if (typeof initDashboard === 'function') {
6205 initDashboard();
6206 } else {
6207 console.warn('initDashboard function not found, calling individual functions...');
6208 // Call individual functions from our generated JS
6209 const allocations = window.analysisData.memory_analysis.allocations;
6210 updateKPIs(allocations);
6211 renderMemoryOverTime(allocations);
6212 renderTypeTreemap(allocations);
6213 renderBorrowHeatmap(allocations);
6214 renderVariableGraph(allocations);
6215 populateAllocationTable(allocations);
6216 }
6217 } catch (error) {
6218 console.error('❌ Error calling dashboard functions:', error);
6219 }
6220 }
6221 }, 2000);
6222
6223 safeUpdateElement('dashboard-status', 'Ready');
6224 console.log('✅ Dashboard initialization completed');
6225
6226 } catch (error) {
6227 console.error('❌ Dashboard initialization failed:', error);
6228 safeUpdateElement('dashboard-status', 'Error');
6229 safeUpdateElement('data-status', 'Failed');
6230 }
6231 });
6232 </script>
6233</body>
6234
6235</html>
6236"#;
6237
6238use crate::core::types::{AllocationInfo, MemoryStats};
6239use crate::export::binary::error::BinaryExportError;
6240use crate::export::binary::reader::BinaryReader;
6241use chrono;
6242use serde_json::json;
6243use std::path::Path;
6244
6245pub fn convert_binary_to_html<P: AsRef<Path>>(
6247 binary_path: P,
6248 html_path: P,
6249 project_name: &str,
6250) -> Result<(), BinaryExportError> {
6251 let mut reader = BinaryReader::new(&binary_path)?;
6253 let allocations = reader.read_all()?;
6254
6255 let stats = generate_statistics(&allocations);
6257
6258 tracing::debug!("Loading binary dashboard template...");
6260 let template = load_binary_dashboard_template()?;
6261 tracing::debug!("Template loaded, length: {} chars", template.len());
6262
6263 let html_content = generate_html_content(&template, &allocations, &stats, project_name)?;
6265
6266 fs::write(&html_path, html_content).map_err(BinaryExportError::Io)?;
6268
6269 Ok(())
6270}
6271
6272fn generate_statistics(allocations: &[AllocationInfo]) -> MemoryStats {
6274 let mut stats = MemoryStats::new();
6275
6276 let mut total_memory = 0;
6277 let mut active_memory = 0;
6278 let mut active_count = 0;
6279 let mut leaked_count = 0;
6280 let mut leaked_memory = 0;
6281
6282 for allocation in allocations {
6283 stats.total_allocations += 1;
6284 total_memory += allocation.size;
6285
6286 if allocation.timestamp_dealloc.is_none() {
6287 active_count += 1;
6288 active_memory += allocation.size;
6289 }
6290
6291 if allocation.is_leaked {
6292 leaked_count += 1;
6293 leaked_memory += allocation.size;
6294 }
6295 }
6296
6297 stats.total_allocated = total_memory;
6298 stats.active_allocations = active_count;
6299 stats.active_memory = active_memory;
6300 stats.peak_memory = active_memory; stats.leaked_allocations = leaked_count;
6302 stats.leaked_memory = leaked_memory;
6303 stats.allocations = allocations.to_vec();
6304
6305 stats
6306}
6307
6308fn load_binary_dashboard_template() -> Result<String, BinaryExportError> {
6310 tracing::debug!("Using embedded binary_dashboard.html template");
6312 Ok(get_binary_dashboard_template().to_string())
6313}
6314
6315fn generate_html_content(
6317 template: &str,
6318 allocations: &[AllocationInfo],
6319 stats: &MemoryStats,
6320 project_name: &str,
6321) -> Result<String, BinaryExportError> {
6322 let allocation_data = prepare_allocation_data(allocations)?;
6324 let _stats_data = prepare_stats_data(stats)?;
6325 let safety_risk_data = prepare_safety_risk_data(allocations)?;
6326
6327 tracing::debug!(
6329 "Replacing BINARY_DATA placeholder with {} bytes of allocation data",
6330 allocation_data.len()
6331 );
6332 let mut html = template.to_string();
6333
6334 if html.contains("{{PROJECT_NAME}}") {
6336 html = html.replace("{{PROJECT_NAME}}", project_name);
6337 } else {
6338 if let Some(start) = html.find("<title>") {
6341 if let Some(end) = html[start..].find("</title>") {
6342 let title_end = start + end;
6343 let before = &html[..start + 7]; let after = &html[title_end..];
6345 html = format!("{before}{project_name} - Memory Analysis Dashboard{after}",);
6346 }
6347 }
6348
6349 html = html.replace(
6351 "MemScope Memory Analysis Dashboard",
6352 &format!("{project_name} - Memory Analysis Report"),
6353 );
6354
6355 html = html.replace("class=\"grid grid-4\"", "class=\"grid grid-4 stats-grid\"");
6357 html = html.replace("<table>", "<table class=\"allocations-table\">");
6358 }
6359
6360 html = html.replace(
6361 "{{TIMESTAMP}}",
6362 &chrono::Utc::now()
6363 .format("%Y-%m-%d %H:%M:%S UTC")
6364 .to_string(),
6365 );
6366 html = html.replace(
6367 "{{GENERATION_TIME}}",
6368 &chrono::Utc::now()
6369 .format("%Y-%m-%d %H:%M:%S UTC")
6370 .to_string(),
6371 );
6372
6373 if html.contains("{{BINARY_DATA}}") {
6375 html = html.replace("{{BINARY_DATA}}", &allocation_data);
6376 tracing::debug!("Successfully replaced {{BINARY_DATA}} placeholder with binary data");
6377 } else {
6378 if let Some(start) = html.find("window.analysisData = {") {
6380 if let Some(end) = html[start..].find("};") {
6381 let end_pos = start + end + 2; let before = &html[..start];
6383 let after = &html[end_pos..];
6384 html = format!(
6385 "{}window.analysisData = {};{}",
6386 before, &allocation_data, after
6387 );
6388 tracing::debug!(
6389 "Fallback: replaced hardcoded window.analysisData with binary data"
6390 );
6391 }
6392 } else {
6393 html = html.replace("{{ALLOCATION_DATA}}", &allocation_data);
6395 html = html.replace("{{ json_data }}", &allocation_data);
6396 html = html.replace("{{json_data}}", &allocation_data);
6397 tracing::debug!("Used fallback placeholder replacements");
6398 }
6399 }
6400
6401 html = html.replace(
6403 "{{TOTAL_ALLOCATIONS}}",
6404 &stats.total_allocations.to_string(),
6405 );
6406 html = html.replace(
6407 "{{ACTIVE_ALLOCATIONS}}",
6408 &stats.active_allocations.to_string(),
6409 );
6410 html = html.replace(
6411 "{{ACTIVE_MEMORY}}",
6412 &format_memory_size(stats.active_memory),
6413 );
6414 html = html.replace("{{PEAK_MEMORY}}", &format_memory_size(stats.peak_memory));
6415 html = html.replace(
6416 "{{LEAKED_ALLOCATIONS}}",
6417 &stats.leaked_allocations.to_string(),
6418 );
6419 html = html.replace(
6420 "{{LEAKED_MEMORY}}",
6421 &format_memory_size(stats.leaked_memory),
6422 );
6423
6424 html = html.replace("{{SVG_IMAGES}}", "<!-- SVG images placeholder -->");
6426 html = html.replace("{{CSS_CONTENT}}", "/* Additional CSS placeholder */");
6427 html = html.replace("{{JS_CONTENT}}", &generate_dashboard_javascript());
6428
6429 html = html.replace("{{ json_data }}", &allocation_data);
6431 html = html.replace("{{json_data}}", &allocation_data);
6432
6433 html = html.replace(
6435 "AFTER JS_CONTENT loads",
6436 "after additional JavaScript loads",
6437 );
6438 html = html.replace("JS_CONTENT loads", "additional JavaScript loads");
6439 html = html.replace("JS_CONTENT", "additionalJavaScript");
6440
6441 if let Some(dom_ready_start) =
6444 html.find("document.addEventListener('DOMContentLoaded', function() {")
6445 {
6446 let injection_point = dom_ready_start;
6447 let before = &html[..injection_point];
6448 let after = &html[injection_point..];
6449
6450 let safety_injection = format!(
6451 r#"
6452 // Safety Risk Data Injection
6453 window.safetyRisks = {safety_risk_data};
6454
6455 function loadSafetyRisks() {{
6456 console.log('🛡️ Loading safety risk data...');
6457 const unsafeTable = document.getElementById('unsafeTable');
6458 if (!unsafeTable) {{
6459 console.warn('⚠️ unsafeTable not found');
6460 return;
6461 }}
6462
6463 const risks = window.safetyRisks || [];
6464 if (risks.length === 0) {{
6465 unsafeTable.innerHTML = '<tr><td colspan="3" class="text-center text-gray-500">No safety risks detected</td></tr>';
6466 return;
6467 }}
6468
6469 unsafeTable.innerHTML = '';
6470 risks.forEach((risk, index) => {{
6471 const row = document.createElement('tr');
6472 row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700';
6473
6474 const riskLevelClass = risk.risk_level === 'High' ? 'text-red-600 font-bold' :
6475 risk.risk_level === 'Medium' ? 'text-yellow-600 font-semibold' :
6476 'text-green-600';
6477
6478 row.innerHTML = `
6479 <td class="px-3 py-2 text-sm">${{risk.location || 'Unknown'}}</td>
6480 <td class="px-3 py-2 text-sm">${{risk.operation || 'Unknown'}}</td>
6481 <td class="px-3 py-2 text-sm"><span class="${{riskLevelClass}}">${{risk.risk_level || 'Low'}}</span></td>
6482 `;
6483 unsafeTable.appendChild(row);
6484 }});
6485
6486 console.log('✅ Safety risks loaded:', risks.length, 'items');
6487 }}
6488
6489 "#,
6490 );
6491
6492 html = format!("{before}{safety_injection}{after}");
6493 } else {
6494 tracing::debug!("Could not find DOMContentLoaded event listener for safety risk injection");
6495 }
6496
6497 if let Some(manual_init_start) =
6499 html.find("manualBtn.addEventListener('click', manualInitialize);")
6500 {
6501 let after_manual_init =
6502 manual_init_start + "manualBtn.addEventListener('click', manualInitialize);".len();
6503 let before = &html[..after_manual_init];
6504 let after = &html[after_manual_init..];
6505
6506 let safety_call_injection = r#"
6507
6508 // Load safety risks after manual initialization
6509 setTimeout(function() {
6510 loadSafetyRisks();
6511 }, 100);
6512"#;
6513
6514 html = format!("{before}{safety_call_injection}{after}");
6515 }
6516
6517 html = html.replace(
6519 "console.log('✅ Enhanced dashboard initialized');",
6520 "console.log('✅ Enhanced dashboard initialized'); loadSafetyRisks();",
6521 );
6522
6523 tracing::debug!(
6524 "Data injection completed: {} allocations, {} stats, safety risks injected",
6525 allocations.len(),
6526 stats.total_allocations
6527 );
6528
6529 Ok(html)
6530}
6531
6532fn prepare_allocation_data(allocations: &[AllocationInfo]) -> Result<String, BinaryExportError> {
6534 let mut allocation_data = Vec::new();
6535
6536 for allocation in allocations {
6537 let mut item = json!({
6538 "ptr": format!("0x{:x}", allocation.ptr),
6539 "size": allocation.size,
6540 "var_name": allocation.var_name.as_deref().unwrap_or("unknown"),
6541 "type_name": allocation.type_name.as_deref().unwrap_or("unknown"),
6542 "scope_name": allocation.scope_name.as_deref().unwrap_or("global"),
6543 "thread_id": allocation.thread_id,
6544 "timestamp_alloc": allocation.timestamp_alloc,
6545 "timestamp_dealloc": allocation.timestamp_dealloc,
6546 "is_leaked": allocation.is_leaked,
6547 "lifetime_ms": allocation.lifetime_ms,
6548 "borrow_count": allocation.borrow_count,
6549 });
6550
6551 if let Some(ref borrow_info) = allocation.borrow_info {
6553 item["borrow_info"] = json!({
6554 "immutable_borrows": borrow_info.immutable_borrows,
6555 "mutable_borrows": borrow_info.mutable_borrows,
6556 "max_concurrent_borrows": borrow_info.max_concurrent_borrows,
6557 "last_borrow_timestamp": borrow_info.last_borrow_timestamp,
6558 });
6559 }
6560
6561 if let Some(ref clone_info) = allocation.clone_info {
6562 item["clone_info"] = json!({
6563 "clone_count": clone_info.clone_count,
6564 "is_clone": clone_info.is_clone,
6565 "original_ptr": clone_info.original_ptr.map(|p| format!("0x{p:x}")),
6566 });
6567 }
6568
6569 item["ownership_history_available"] = json!(allocation.ownership_history_available);
6570
6571 allocation_data.push(item);
6572 }
6573
6574 let (lifetime_data, complex_types, unsafe_ffi, performance_data) =
6576 generate_enhanced_data(&allocation_data);
6577
6578 let data_structure = json!({
6580 "memory_analysis": {
6581 "allocations": allocation_data.clone()
6582 },
6583 "allocations": allocation_data, "lifetime": lifetime_data,
6585 "complex_types": complex_types,
6586 "unsafe_ffi": unsafe_ffi,
6587 "performance": performance_data,
6588 "metadata": {
6589 "generation_time": chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(),
6590 "data_source": "binary_direct",
6591 "version": "1.0"
6592 }
6593 });
6594
6595 serde_json::to_string(&data_structure).map_err(|e| {
6596 BinaryExportError::SerializationError(format!("Failed to serialize allocation data: {e}"))
6597 })
6598}
6599
6600fn prepare_stats_data(stats: &MemoryStats) -> Result<String, BinaryExportError> {
6602 let data = json!({
6603 "total_allocations": stats.total_allocations,
6604 "total_allocated": stats.total_allocated,
6605 "active_allocations": stats.active_allocations,
6606 "active_memory": stats.active_memory,
6607 "peak_allocations": stats.peak_allocations,
6608 "peak_memory": stats.peak_memory,
6609 "total_deallocations": stats.total_deallocations,
6610 "total_deallocated": stats.total_deallocated,
6611 "leaked_allocations": stats.leaked_allocations,
6612 "leaked_memory": stats.leaked_memory,
6613 });
6614
6615 serde_json::to_string(&data).map_err(|e| {
6616 BinaryExportError::SerializationError(format!("Failed to serialize stats data: {e}"))
6617 })
6618}
6619
6620fn format_memory_size(bytes: usize) -> String {
6622 const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
6623 let mut size = bytes as f64;
6624 let mut unit_index = 0;
6625
6626 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
6627 size /= 1024.0;
6628 unit_index += 1;
6629 }
6630
6631 if unit_index == 0 {
6632 format!("{bytes} {}", UNITS[unit_index])
6633 } else {
6634 format!("{size:.2} {}", UNITS[unit_index])
6635 }
6636}
6637
6638fn generate_enhanced_data(
6640 allocations: &[serde_json::Value],
6641) -> (
6642 serde_json::Value,
6643 serde_json::Value,
6644 serde_json::Value,
6645 serde_json::Value,
6646) {
6647 let lifetime_allocations: Vec<serde_json::Value> = allocations
6649 .iter()
6650 .map(|alloc| {
6651 let mut lifetime_alloc = alloc.clone();
6652 lifetime_alloc["ownership_transfer_points"] =
6653 json!(generate_ownership_transfer_points(alloc));
6654 lifetime_alloc
6655 })
6656 .collect();
6657
6658 let lifetime_data = json!({
6659 "allocations": lifetime_allocations,
6660 "metadata": {
6661 "generation_time": chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(),
6662 "data_source": "binary_direct"
6663 }
6664 });
6665
6666 let complex_allocations: Vec<serde_json::Value> = allocations
6668 .iter()
6669 .filter_map(|alloc| {
6670 let type_name = alloc["type_name"].as_str().unwrap_or("");
6671 if type_name.contains('<')
6673 || type_name.contains("Arc")
6674 || type_name.contains("Box")
6675 || type_name.contains("Vec")
6676 || type_name.contains("HashMap")
6677 || type_name.contains("BTreeMap")
6678 || type_name.contains("Rc")
6679 || type_name.contains("RefCell")
6680 {
6681 let mut complex_alloc = alloc.clone();
6682 complex_alloc["generic_params"] = json!(extract_generic_params(type_name));
6683 complex_alloc["complexity_score"] = json!(calculate_complexity_score(type_name));
6684 complex_alloc["memory_layout"] = json!({
6685 "alignment": 8,
6686 "padding": 0,
6687 "size_bytes": alloc["size"]
6688 });
6689 Some(complex_alloc)
6690 } else {
6691 None
6692 }
6693 })
6694 .collect();
6695
6696 let complex_types = json!({
6697 "allocations": complex_allocations,
6698 "metadata": {
6699 "generation_time": chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(),
6700 "data_source": "binary_direct"
6701 }
6702 });
6703
6704 let unsafe_allocations: Vec<serde_json::Value> = allocations
6706 .iter()
6707 .map(|alloc| {
6708 let type_name = alloc["type_name"].as_str().unwrap_or("");
6709 let is_ffi_tracked = type_name.contains("*mut")
6710 || type_name.contains("*const")
6711 || type_name.contains("c_void")
6712 || type_name.contains("CString")
6713 || type_name.contains("extern")
6714 || type_name.contains("CStr");
6715
6716 let safety_violations: Vec<&str> = if is_ffi_tracked {
6717 vec!["raw_pointer_usage", "ffi_boundary_crossing"]
6718 } else if alloc["is_leaked"].as_bool().unwrap_or(false) {
6719 vec!["memory_leak"]
6720 } else {
6721 vec![]
6722 };
6723
6724 let mut unsafe_alloc = alloc.clone();
6725 unsafe_alloc["ffi_tracked"] = json!(is_ffi_tracked);
6726 unsafe_alloc["safety_violations"] = json!(safety_violations);
6727 unsafe_alloc
6728 })
6729 .collect();
6730
6731 let unsafe_ffi = json!({
6732 "allocations": unsafe_allocations,
6733 "metadata": {
6734 "generation_time": chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(),
6735 "data_source": "binary_direct"
6736 }
6737 });
6738
6739 let performance_allocations: Vec<serde_json::Value> = allocations
6741 .iter()
6742 .map(|alloc| {
6743 let size = alloc["size"].as_u64().unwrap_or(0);
6744 let lifetime_ms = alloc["lifetime_ms"].as_u64().unwrap_or(0);
6745
6746 let mut perf_alloc = alloc.clone();
6747 perf_alloc["fragmentation_analysis"] = json!({
6748 "fragmentation_score": if size > 1024 { 0.3 } else { 0.1 },
6749 "alignment_efficiency": if size % 8 == 0 { 100.0 } else { 85.0 },
6750 "memory_density": calculate_memory_density(size)
6751 });
6752 perf_alloc["allocation_efficiency"] = json!({
6753 "reuse_potential": if lifetime_ms > 1000 { 0.2 } else { 0.8 },
6754 "memory_locality": if size < 1024 { "high" } else { "medium" },
6755 "cache_efficiency": calculate_cache_efficiency(size)
6756 });
6757 perf_alloc
6758 })
6759 .collect();
6760
6761 let performance_data = json!({
6762 "allocations": performance_allocations,
6763 "metadata": {
6764 "generation_time": chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(),
6765 "data_source": "binary_direct"
6766 }
6767 });
6768
6769 (lifetime_data, complex_types, unsafe_ffi, performance_data)
6770}
6771
6772fn extract_generic_params(type_name: &str) -> Vec<String> {
6774 if let Some(start) = type_name.find('<') {
6775 if let Some(end) = type_name.rfind('>') {
6776 let params_str = &type_name[start + 1..end];
6777 return params_str
6778 .split(',')
6779 .map(|s| s.trim().to_string())
6780 .collect();
6781 }
6782 }
6783 vec![]
6784}
6785
6786fn calculate_complexity_score(type_name: &str) -> u32 {
6788 let mut score = 1;
6789
6790 score += type_name.matches('<').count() as u32 * 2;
6792
6793 if type_name.contains("Arc") || type_name.contains("Rc") {
6795 score += 3;
6796 }
6797 if type_name.contains("Box") {
6798 score += 2;
6799 }
6800 if type_name.contains("Vec") {
6801 score += 2;
6802 }
6803 if type_name.contains("HashMap") || type_name.contains("BTreeMap") {
6804 score += 4;
6805 }
6806
6807 if type_name.contains("*mut") || type_name.contains("*const") {
6809 score += 5;
6810 }
6811
6812 score
6813}
6814
6815fn calculate_memory_density(size: u64) -> f64 {
6817 if size < 64 {
6819 1.0
6820 } else if size < 1024 {
6821 0.8
6822 } else if size < 4096 {
6823 0.6
6824 } else {
6825 0.4
6826 }
6827}
6828
6829fn calculate_cache_efficiency(size: u64) -> f64 {
6831 let cache_line_size = 64;
6833 let lines_used = size.div_ceil(cache_line_size);
6834 let efficiency = size as f64 / (lines_used * cache_line_size) as f64;
6835 efficiency.min(1.0)
6836}
6837
6838fn generate_ownership_transfer_points(allocation: &serde_json::Value) -> Vec<serde_json::Value> {
6840 let mut transfer_points = Vec::new();
6841
6842 if let Some(clone_info) = allocation.get("clone_info") {
6844 if clone_info
6845 .get("is_clone")
6846 .and_then(|v| v.as_bool())
6847 .unwrap_or(false)
6848 {
6849 transfer_points.push(json!({
6850 "event": "clone_created",
6851 "timestamp": allocation.get("timestamp_alloc"),
6852 "original_ptr": clone_info.get("original_ptr")
6853 }));
6854 }
6855
6856 let clone_count = clone_info
6857 .get("clone_count")
6858 .and_then(|v| v.as_u64())
6859 .unwrap_or(0);
6860 if clone_count > 0 {
6861 transfer_points.push(json!({
6862 "event": "clones_created",
6863 "count": clone_count,
6864 "timestamp": allocation.get("timestamp_alloc")
6865 }));
6866 }
6867 }
6868
6869 if let Some(borrow_info) = allocation.get("borrow_info") {
6871 if let Some(last_borrow) = borrow_info.get("last_borrow_timestamp") {
6872 transfer_points.push(json!({
6873 "event": "last_borrow",
6874 "timestamp": last_borrow,
6875 "borrow_type": "mixed"
6876 }));
6877 }
6878 }
6879
6880 transfer_points
6881}
6882
6883fn generate_dashboard_javascript() -> String {
6885 r#"
6886// Dashboard initialization and chart rendering functions
6887let charts = {};
6888let memoryTimelineChart = null;
6889let typeTreemapData = null;
6890
6891function initDashboard() {
6892 console.log('🚀 Initializing dashboard...');
6893
6894 if (!window.analysisData || !window.analysisData.memory_analysis) {
6895 console.warn('No analysis data available');
6896 return;
6897 }
6898
6899 const allocations = window.analysisData.memory_analysis.allocations || [];
6900 console.log('📊 Processing', allocations.length, 'allocations');
6901
6902 // Initialize all dashboard components
6903 updateKPIs(allocations);
6904 renderMemoryOperationsAnalysis(allocations);
6905 renderMemoryOverTime(allocations);
6906 renderEnhancedTypeTreemap(allocations);
6907 renderEnhancedBorrowHeatmap(allocations);
6908 renderInteractiveVariableGraph(allocations);
6909 populateAllocationTable(allocations);
6910
6911 // Update Performance Metrics
6912 updatePerformanceMetrics(allocations);
6913
6914 console.log('✅ Dashboard initialized successfully');
6915}
6916
6917function updatePerformanceMetrics(allocations) {
6918 console.log('⚡ Updating Performance Metrics...');
6919
6920 // Calculate allocation efficiency (successful vs total attempts)
6921 const totalAllocations = allocations.length;
6922 const successfulAllocations = allocations.filter(a => a.size > 0).length;
6923 const allocationEfficiency = totalAllocations > 0 ?
6924 Math.round((successfulAllocations / totalAllocations) * 100) : 100;
6925
6926 // Calculate memory utilization (allocated vs deallocated)
6927 const totalAllocated = allocations.reduce((sum, a) => sum + (a.size || 0), 0);
6928 const totalDeallocated = allocations.filter(a => a.timestamp_dealloc)
6929 .reduce((sum, a) => sum + (a.size || 0), 0);
6930 const memoryUtilization = totalAllocated > 0 ?
6931 Math.round(((totalAllocated - totalDeallocated) / totalAllocated) * 100) : 0;
6932
6933 // Calculate fragmentation index (estimate based on allocation sizes)
6934 const allocationSizes = allocations.map(a => a.size || 0).filter(s => s > 0);
6935 const avgSize = allocationSizes.length > 0 ?
6936 allocationSizes.reduce((sum, s) => sum + s, 0) / allocationSizes.length : 0;
6937 const sizeVariance = allocationSizes.length > 0 ?
6938 allocationSizes.reduce((sum, s) => sum + Math.pow(s - avgSize, 2), 0) / allocationSizes.length : 0;
6939 const fragmentation = avgSize > 0 ? Math.min(100, Math.round((Math.sqrt(sizeVariance) / avgSize) * 100)) : 0;
6940
6941 // Calculate leak ratio
6942 const leakedAllocations = allocations.filter(a => a.is_leaked).length;
6943 const leakRatio = totalAllocations > 0 ?
6944 Math.round((leakedAllocations / totalAllocations) * 100) : 0;
6945
6946 // Calculate thread efficiency (allocations per thread)
6947 const uniqueThreads = new Set(allocations.map(a => a.thread_id)).size;
6948 const threadEfficiency = uniqueThreads > 0 ?
6949 Math.round(totalAllocations / uniqueThreads) : 0;
6950
6951 // Calculate borrow efficiency (safe borrows vs total)
6952 const totalBorrows = allocations.reduce((sum, a) => sum + (a.borrow_count || 0), 0);
6953 const immutableBorrows = allocations.reduce((sum, a) => {
6954 return sum + (a.borrow_info ? (a.borrow_info.immutable_borrows || 0) : 0);
6955 }, 0);
6956 const borrowSafety = totalBorrows > 0 ?
6957 Math.round((immutableBorrows / totalBorrows) * 100) : 100;
6958
6959 // Update UI elements
6960 safeUpdateElement('allocation-efficiency', allocationEfficiency + '%');
6961 safeUpdateElement('memory-utilization', memoryUtilization + '%');
6962 safeUpdateElement('fragmentation-index', fragmentation + '%');
6963 safeUpdateElement('leak-ratio', leakRatio + '%');
6964 safeUpdateElement('thread-efficiency', threadEfficiency + ' allocs/thread');
6965 safeUpdateElement('borrow-safety', borrowSafety + '%');
6966
6967 console.log('✅ Performance Metrics updated');
6968}
6969
6970function updateKPIs(allocations) {
6971 console.log('📊 Updating KPIs...');
6972
6973 const totalAllocations = allocations.length;
6974 const activeAllocations = allocations.filter(a => !a.timestamp_dealloc).length;
6975 const totalMemory = allocations.reduce((sum, a) => sum + (a.size || 0), 0);
6976 const leakedCount = allocations.filter(a => a.is_leaked).length;
6977
6978 // Calculate safety score (percentage of non-leaked allocations)
6979 const safetyScore = totalAllocations > 0 ?
6980 Math.round(((totalAllocations - leakedCount) / totalAllocations) * 100) : 100;
6981
6982 safeUpdateElement('total-allocations', totalAllocations);
6983 safeUpdateElement('active-variables', activeAllocations);
6984 safeUpdateElement('total-memory', formatBytes(totalMemory));
6985 safeUpdateElement('safety-score', safetyScore + '%');
6986
6987 console.log('✅ KPIs updated');
6988}
6989
6990function renderMemoryOperationsAnalysis(allocations) {
6991 console.log('🔧 Rendering Memory Operations Analysis...');
6992
6993 // Calculate time span
6994 const timestamps = allocations.map(a => a.timestamp_alloc).filter(t => t);
6995 const timeSpan = timestamps.length > 0 ?
6996 Math.max(...timestamps) - Math.min(...timestamps) : 0;
6997
6998 // Calculate allocation burst (max allocations in a time window)
6999 const sortedAllocs = allocations.filter(a => a.timestamp_alloc).sort((a, b) => a.timestamp_alloc - b.timestamp_alloc);
7000 let maxBurst = 0;
7001 const windowSize = 1000000; // 1ms in nanoseconds
7002
7003 for (let i = 0; i < sortedAllocs.length; i++) {
7004 const windowStart = sortedAllocs[i].timestamp_alloc;
7005 const windowEnd = windowStart + windowSize;
7006 let count = 0;
7007
7008 for (let j = i; j < sortedAllocs.length && sortedAllocs[j].timestamp_alloc <= windowEnd; j++) {
7009 count++;
7010 }
7011 maxBurst = Math.max(maxBurst, count);
7012 }
7013
7014 // Calculate peak concurrency (max active allocations at any time)
7015 let peakConcurrency = 0;
7016 let currentActive = 0;
7017
7018 const events = [];
7019 allocations.forEach(alloc => {
7020 if (alloc.timestamp_alloc) events.push({ time: alloc.timestamp_alloc, type: 'alloc' });
7021 if (alloc.timestamp_dealloc) events.push({ time: alloc.timestamp_dealloc, type: 'dealloc' });
7022 });
7023
7024 events.sort((a, b) => a.time - b.time);
7025 events.forEach(event => {
7026 if (event.type === 'alloc') currentActive++;
7027 else currentActive--;
7028 peakConcurrency = Math.max(peakConcurrency, currentActive);
7029 });
7030
7031 // Calculate thread activity
7032 const threads = new Set(allocations.map(a => a.thread_id));
7033 const threadActivity = threads.size;
7034
7035 // Calculate borrow operations with detailed analysis
7036 const borrowOps = allocations.reduce((sum, a) => sum + (a.borrow_count || 0), 0);
7037 let mutableBorrows = 0;
7038 let immutableBorrows = 0;
7039
7040 allocations.forEach(a => {
7041 if (a.borrow_info) {
7042 mutableBorrows += a.borrow_info.mutable_borrows || 0;
7043 immutableBorrows += a.borrow_info.immutable_borrows || 0;
7044 }
7045 });
7046
7047 // Calculate clone operations
7048 const cloneOps = allocations.reduce((sum, a) => {
7049 return sum + (a.clone_info ? (a.clone_info.clone_count || 0) : 0);
7050 }, 0);
7051
7052 // Calculate average allocation size
7053 const totalSize = allocations.reduce((sum, a) => sum + (a.size || 0), 0);
7054 const avgAllocSize = allocations.length > 0 ? totalSize / allocations.length : 0;
7055
7056 // Better time span calculation - use realistic timestamps if available
7057 let timeSpanDisplay = 'N/A';
7058 if (timeSpan > 0) {
7059 if (timeSpan > 1000000000) { // > 1 second
7060 timeSpanDisplay = (timeSpan / 1000000000).toFixed(2) + 's';
7061 } else if (timeSpan > 1000000) { // > 1 millisecond
7062 timeSpanDisplay = (timeSpan / 1000000).toFixed(2) + 'ms';
7063 } else if (timeSpan > 1000) { // > 1 microsecond
7064 timeSpanDisplay = (timeSpan / 1000).toFixed(2) + 'μs';
7065 } else {
7066 timeSpanDisplay = timeSpan + 'ns';
7067 }
7068 } else if (allocations.length > 0) {
7069 // If no timestamps, show based on allocation count
7070 timeSpanDisplay = allocations.length + ' allocs';
7071 }
7072
7073 // Update UI elements
7074 safeUpdateElement('time-span', timeSpanDisplay);
7075 safeUpdateElement('allocation-burst', maxBurst || allocations.length);
7076 safeUpdateElement('peak-concurrency', peakConcurrency || allocations.length);
7077 safeUpdateElement('thread-activity', threadActivity + ' threads');
7078 safeUpdateElement('borrow-ops', borrowOps);
7079 safeUpdateElement('clone-ops', cloneOps);
7080
7081 // Update the missing fields
7082 safeUpdateElement('mut-immut', `${mutableBorrows}/${immutableBorrows}`);
7083 safeUpdateElement('avg-alloc', formatBytes(avgAllocSize));
7084
7085 console.log('✅ Memory Operations Analysis updated');
7086}
7087
7088function renderMemoryOverTime(allocations) {
7089 console.log('📈 Rendering Memory Over Time chart...');
7090
7091 const canvas = document.getElementById('timelineChart');
7092 if (!canvas) {
7093 console.warn('timelineChart canvas not found');
7094 return;
7095 }
7096
7097 const ctx = canvas.getContext('2d');
7098
7099 // Destroy existing chart if it exists
7100 if (memoryTimelineChart) {
7101 memoryTimelineChart.destroy();
7102 }
7103
7104 // Sort allocations by timestamp
7105 const sortedAllocs = allocations
7106 .filter(a => a.timestamp_alloc)
7107 .sort((a, b) => (a.timestamp_alloc || 0) - (b.timestamp_alloc || 0));
7108
7109 if (sortedAllocs.length === 0) {
7110 console.warn('No allocations with timestamps found');
7111 ctx.fillStyle = '#666';
7112 ctx.font = '16px Arial';
7113 ctx.textAlign = 'center';
7114 ctx.fillText('No timeline data available', canvas.width / 2, canvas.height / 2);
7115 return;
7116 }
7117
7118 // Create simple indexed timeline data (avoid time scale issues)
7119 const timelineData = [];
7120 let cumulativeMemory = 0;
7121
7122 sortedAllocs.forEach((alloc, index) => {
7123 cumulativeMemory += alloc.size || 0;
7124 timelineData.push({
7125 x: index,
7126 y: cumulativeMemory
7127 });
7128
7129 // Add deallocation point if available
7130 if (alloc.timestamp_dealloc) {
7131 cumulativeMemory -= alloc.size || 0;
7132 timelineData.push({
7133 x: index + 0.5,
7134 y: cumulativeMemory
7135 });
7136 }
7137 });
7138
7139 // Create labels from allocation names
7140 const labels = sortedAllocs.map((alloc, index) =>
7141 `${index}: ${alloc.var_name || 'unnamed'}`);
7142
7143 memoryTimelineChart = new Chart(ctx, {
7144 type: 'line',
7145 data: {
7146 labels: labels,
7147 datasets: [{
7148 label: 'Memory Usage',
7149 data: timelineData.map(d => d.y),
7150 borderColor: 'rgb(59, 130, 246)',
7151 backgroundColor: 'rgba(59, 130, 246, 0.1)',
7152 fill: true,
7153 tension: 0.4,
7154 pointRadius: 3,
7155 pointHoverRadius: 5
7156 }]
7157 },
7158 options: {
7159 responsive: true,
7160 maintainAspectRatio: false,
7161 interaction: {
7162 intersect: false,
7163 mode: 'index'
7164 },
7165 scales: {
7166 x: {
7167 title: {
7168 display: true,
7169 text: 'Allocation Sequence'
7170 },
7171 ticks: {
7172 maxTicksLimit: 10
7173 }
7174 },
7175 y: {
7176 title: {
7177 display: true,
7178 text: 'Memory (bytes)'
7179 },
7180 ticks: {
7181 callback: function(value) {
7182 return formatBytes(value);
7183 }
7184 }
7185 }
7186 },
7187 plugins: {
7188 tooltip: {
7189 callbacks: {
7190 title: function(context) {
7191 const index = context[0].dataIndex;
7192 if (sortedAllocs[index]) {
7193 return `${sortedAllocs[index].var_name || 'unnamed'} (${sortedAllocs[index].type_name || 'unknown'})`;
7194 }
7195 return 'Allocation ' + index;
7196 },
7197 label: function(context) {
7198 return 'Memory: ' + formatBytes(context.parsed.y);
7199 }
7200 }
7201 }
7202 }
7203 }
7204 });
7205
7206 // Add growth rate toggle functionality
7207 const growthRateToggle = document.getElementById('toggleGrowthRate');
7208 if (growthRateToggle) {
7209 growthRateToggle.addEventListener('change', function() {
7210 updateTimelineChart(allocations, this.checked);
7211 });
7212 }
7213
7214 console.log('✅ Memory Over Time chart rendered with', timelineData.length, 'data points');
7215}
7216
7217function updateTimelineChart(allocations, showGrowthRate) {
7218 const canvas = document.getElementById('timelineChart');
7219 if (!canvas) return;
7220
7221 const ctx = canvas.getContext('2d');
7222
7223 // Destroy existing chart if it exists
7224 if (memoryTimelineChart) {
7225 memoryTimelineChart.destroy();
7226 }
7227
7228 // Sort allocations by timestamp
7229 const sortedAllocs = allocations
7230 .filter(a => a.timestamp_alloc)
7231 .sort((a, b) => (a.timestamp_alloc || 0) - (b.timestamp_alloc || 0));
7232
7233 if (sortedAllocs.length === 0) {
7234 ctx.fillStyle = '#666';
7235 ctx.font = '16px Arial';
7236 ctx.textAlign = 'center';
7237 ctx.fillText('No timeline data available', canvas.width / 2, canvas.height / 2);
7238 return;
7239 }
7240
7241 const timelineData = [];
7242 const growthRateData = [];
7243 let cumulativeMemory = 0;
7244 let previousMemory = 0;
7245
7246 sortedAllocs.forEach((alloc, index) => {
7247 previousMemory = cumulativeMemory;
7248 cumulativeMemory += alloc.size || 0;
7249
7250 timelineData.push({
7251 x: index,
7252 y: cumulativeMemory
7253 });
7254
7255 // Calculate growth rate (percentage change)
7256 const growthRate = previousMemory > 0 ?
7257 ((cumulativeMemory - previousMemory) / previousMemory) * 100 : 0;
7258 growthRateData.push({
7259 x: index,
7260 y: growthRate
7261 });
7262
7263 // Add deallocation point if available
7264 if (alloc.timestamp_dealloc) {
7265 previousMemory = cumulativeMemory;
7266 cumulativeMemory -= alloc.size || 0;
7267 timelineData.push({
7268 x: index + 0.5,
7269 y: cumulativeMemory
7270 });
7271
7272 const deallocGrowthRate = previousMemory > 0 ?
7273 ((cumulativeMemory - previousMemory) / previousMemory) * 100 : 0;
7274 growthRateData.push({
7275 x: index + 0.5,
7276 y: deallocGrowthRate
7277 });
7278 }
7279 });
7280
7281 const labels = sortedAllocs.map((alloc, index) =>
7282 `${index}: ${alloc.var_name || 'unnamed'}`);
7283
7284 const datasets = [{
7285 label: 'Memory Usage',
7286 data: timelineData.map(d => d.y),
7287 borderColor: 'rgb(59, 130, 246)',
7288 backgroundColor: 'rgba(59, 130, 246, 0.1)',
7289 fill: true,
7290 tension: 0.4,
7291 pointRadius: 3,
7292 pointHoverRadius: 5,
7293 yAxisID: 'y'
7294 }];
7295
7296 if (showGrowthRate) {
7297 datasets.push({
7298 label: 'Growth Rate (%)',
7299 data: growthRateData.map(d => d.y),
7300 borderColor: 'rgb(239, 68, 68)',
7301 backgroundColor: 'rgba(239, 68, 68, 0.1)',
7302 fill: false,
7303 tension: 0.4,
7304 pointRadius: 2,
7305 pointHoverRadius: 4,
7306 yAxisID: 'y1'
7307 });
7308 }
7309
7310 const scales = {
7311 x: {
7312 title: {
7313 display: true,
7314 text: 'Allocation Sequence'
7315 },
7316 ticks: {
7317 maxTicksLimit: 10
7318 }
7319 },
7320 y: {
7321 type: 'linear',
7322 display: true,
7323 position: 'left',
7324 title: {
7325 display: true,
7326 text: 'Memory (bytes)'
7327 },
7328 ticks: {
7329 callback: function(value) {
7330 return formatBytes(value);
7331 }
7332 }
7333 }
7334 };
7335
7336 if (showGrowthRate) {
7337 scales.y1 = {
7338 type: 'linear',
7339 display: true,
7340 position: 'right',
7341 title: {
7342 display: true,
7343 text: 'Growth Rate (%)'
7344 },
7345 grid: {
7346 drawOnChartArea: false
7347 },
7348 ticks: {
7349 callback: function(value) {
7350 return value.toFixed(1) + '%';
7351 }
7352 }
7353 };
7354 }
7355
7356 memoryTimelineChart = new Chart(ctx, {
7357 type: 'line',
7358 data: {
7359 labels: labels,
7360 datasets: datasets
7361 },
7362 options: {
7363 responsive: true,
7364 maintainAspectRatio: false,
7365 interaction: {
7366 intersect: false,
7367 mode: 'index'
7368 },
7369 scales: scales,
7370 plugins: {
7371 tooltip: {
7372 callbacks: {
7373 title: function(context) {
7374 const index = context[0].dataIndex;
7375 if (sortedAllocs[index]) {
7376 return `${sortedAllocs[index].var_name || 'unnamed'} (${sortedAllocs[index].type_name || 'unknown'})`;
7377 }
7378 return 'Allocation ' + index;
7379 },
7380 label: function(context) {
7381 if (context.dataset.label.includes('Growth Rate')) {
7382 return 'Growth Rate: ' + context.parsed.y.toFixed(2) + '%';
7383 }
7384 return 'Memory: ' + formatBytes(context.parsed.y);
7385 }
7386 }
7387 }
7388 }
7389 }
7390 });
7391}
7392
7393function renderEnhancedTypeTreemap(allocations) {
7394 console.log('🌳 Rendering Enhanced Type Treemap...');
7395
7396 const container = document.getElementById('treemap');
7397 if (!container) {
7398 console.warn('treemap container not found');
7399 return;
7400 }
7401
7402 // Clear existing content
7403 container.innerHTML = '';
7404 container.style.position = 'relative';
7405
7406 // Aggregate by type
7407 const typeData = {};
7408 allocations.forEach(alloc => {
7409 const type = alloc.type_name || 'unknown';
7410 if (!typeData[type]) {
7411 typeData[type] = { count: 0, totalSize: 0 };
7412 }
7413 typeData[type].count++;
7414 typeData[type].totalSize += alloc.size || 0;
7415 });
7416
7417 // Convert to treemap format and sort by size
7418 const treemapData = Object.entries(typeData)
7419 .map(([type, data]) => ({
7420 name: type,
7421 value: data.totalSize,
7422 count: data.count
7423 }))
7424 .sort((a, b) => b.value - a.value);
7425
7426 if (treemapData.length === 0) {
7427 container.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">No type data available</div>';
7428 return;
7429 }
7430
7431 // Use squarified treemap algorithm for better layout
7432 const containerRect = container.getBoundingClientRect();
7433 const containerWidth = containerRect.width || 400;
7434 const containerHeight = containerRect.height || 300;
7435 const totalValue = treemapData.reduce((sum, d) => sum + d.value, 0);
7436
7437 // Calculate areas proportional to values
7438 treemapData.forEach(d => {
7439 d.area = (d.value / totalValue) * containerWidth * containerHeight;
7440 d.ratio = containerWidth / containerHeight;
7441 });
7442
7443 // Simple recursive treemap layout
7444 function layoutTreemap(data, x, y, width, height) {
7445 if (data.length === 0) return;
7446
7447 if (data.length === 1) {
7448 const item = data[0];
7449 createTreemapTile(item, x, y, width, height);
7450 return;
7451 }
7452
7453 // Split the data into two groups
7454 const totalArea = data.reduce((sum, d) => sum + d.area, 0);
7455 const midValue = totalArea / 2;
7456 let currentSum = 0;
7457 let splitIndex = 0;
7458
7459 for (let i = 0; i < data.length; i++) {
7460 currentSum += data[i].area;
7461 if (currentSum >= midValue) {
7462 splitIndex = i + 1;
7463 break;
7464 }
7465 }
7466
7467 const group1 = data.slice(0, splitIndex);
7468 const group2 = data.slice(splitIndex);
7469
7470 if (width > height) {
7471 // Split vertically
7472 const splitWidth = width * (currentSum / totalArea);
7473 layoutTreemap(group1, x, y, splitWidth, height);
7474 layoutTreemap(group2, x + splitWidth, y, width - splitWidth, height);
7475 } else {
7476 // Split horizontally
7477 const splitHeight = height * (currentSum / totalArea);
7478 layoutTreemap(group1, x, y, width, splitHeight);
7479 layoutTreemap(group2, x, y + splitHeight, width, height - splitHeight);
7480 }
7481 }
7482
7483 function createTreemapTile(item, x, y, width, height) {
7484 const tile = document.createElement('div');
7485 const minSize = Math.min(width, height);
7486 const fontSize = Math.max(Math.min(minSize / 8, 14), 10);
7487
7488 tile.style.cssText = `
7489 position: absolute;
7490 left: ${x + 1}px;
7491 top: ${y + 1}px;
7492 width: ${width - 2}px;
7493 height: ${height - 2}px;
7494 background: hsl(${(item.name.length * 37) % 360}, 65%, 55%);
7495 border: 2px solid rgba(255,255,255,0.8);
7496 border-radius: 6px;
7497 display: flex;
7498 flex-direction: column;
7499 align-items: center;
7500 justify-content: center;
7501 font-size: ${fontSize}px;
7502 font-weight: 600;
7503 color: white;
7504 text-shadow: 1px 1px 2px rgba(0,0,0,0.7);
7505 cursor: pointer;
7506 transition: all 0.3s ease;
7507 overflow: hidden;
7508 box-shadow: 0 2px 8px rgba(0,0,0,0.2);
7509 `;
7510
7511 const shortName = item.name.length > 12 ? item.name.substring(0, 12) + '...' : item.name;
7512 tile.innerHTML = `
7513 <div style="text-align: center; padding: 4px;">
7514 <div style="font-weight: 700; margin-bottom: 2px;" title="${item.name}">${shortName}</div>
7515 <div style="font-size: ${Math.max(fontSize - 2, 8)}px; opacity: 0.9;">${formatBytes(item.value)}</div>
7516 <div style="font-size: ${Math.max(fontSize - 3, 7)}px; opacity: 0.8;">(${item.count} items)</div>
7517 </div>
7518 `;
7519
7520 tile.addEventListener('mouseenter', () => {
7521 tile.style.transform = 'scale(1.05)';
7522 tile.style.zIndex = '10';
7523 tile.style.boxShadow = '0 4px 16px rgba(0,0,0,0.4)';
7524 });
7525
7526 tile.addEventListener('mouseleave', () => {
7527 tile.style.transform = 'scale(1)';
7528 tile.style.zIndex = '1';
7529 tile.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
7530 });
7531
7532 tile.addEventListener('click', () => {
7533 const totalMemorySize = treemapData.reduce((sum, d) => sum + d.value, 0);
7534 const modalContent = `
7535 <div style="text-align: center; margin-bottom: 20px;">
7536 <div style="font-size: 48px; margin-bottom: 10px;">📊</div>
7537 <div style="font-size: 24px; font-weight: 600; margin-bottom: 8px;">${item.name}</div>
7538 </div>
7539 <div style="background: rgba(255, 255, 255, 0.1); padding: 20px; border-radius: 12px; margin-bottom: 20px;">
7540 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
7541 <div style="text-align: center;">
7542 <div style="font-size: 28px; font-weight: 700; color: #4ade80;">${formatBytes(item.value)}</div>
7543 <div style="opacity: 0.8; font-size: 14px;">Total Size</div>
7544 </div>
7545 <div style="text-align: center;">
7546 <div style="font-size: 28px; font-weight: 700; color: #60a5fa;">${item.count}</div>
7547 <div style="opacity: 0.8; font-size: 14px;">Allocations</div>
7548 </div>
7549 </div>
7550 </div>
7551 <div style="background: rgba(255, 255, 255, 0.05); padding: 16px; border-radius: 8px;">
7552 <div style="font-size: 14px; opacity: 0.9;">
7553 <div style="margin-bottom: 8px;"><strong>Average Size:</strong> ${formatBytes(item.value / item.count)}</div>
7554 <div style="margin-bottom: 8px;"><strong>Memory Share:</strong> ${((item.value / totalMemorySize) * 100).toFixed(1)}%</div>
7555 <div><strong>Type Category:</strong> ${item.name.includes('Vec') ? 'Dynamic Array' : item.name.includes('HashMap') ? 'Hash Map' : item.name.includes('String') ? 'String Type' : 'Custom Type'}</div>
7556 </div>
7557 </div>
7558 `;
7559 createModal(`📋 Type Analysis`, modalContent);
7560 });
7561
7562 container.appendChild(tile);
7563 }
7564
7565 // Start the layout process
7566 layoutTreemap(treemapData, 0, 0, containerWidth, containerHeight);
7567
7568 console.log('✅ Enhanced Type Treemap rendered with', treemapData.length, 'types');
7569}
7570
7571function renderEnhancedBorrowHeatmap(allocations) {
7572 console.log('🔥 Rendering Enhanced Borrow Activity Heatmap...');
7573
7574 const container = document.getElementById('borrowPatternChart');
7575 if (!container) {
7576 console.warn('borrowPatternChart container not found');
7577 return;
7578 }
7579
7580 container.innerHTML = '';
7581 container.style.position = 'relative';
7582
7583 // Enhanced borrow data collection - include borrow_info if available
7584 const borrowData = allocations.map(alloc => {
7585 const borrowCount = alloc.borrow_count || 0;
7586 const borrowInfo = alloc.borrow_info || {};
7587 const immutableBorrows = borrowInfo.immutable_borrows || 0;
7588 const mutableBorrows = borrowInfo.mutable_borrows || 0;
7589 const totalBorrows = Math.max(borrowCount, immutableBorrows + mutableBorrows);
7590
7591 return {
7592 ...alloc,
7593 totalBorrows,
7594 immutableBorrows,
7595 mutableBorrows,
7596 hasActivity: totalBorrows > 0 || borrowCount > 0
7597 };
7598 }).filter(a => a.hasActivity || allocations.length <= 20); // Show all if few allocations
7599
7600 if (borrowData.length === 0) {
7601 // Create synthetic data for demonstration
7602 const syntheticData = allocations.slice(0, Math.min(50, allocations.length)).map((alloc, i) => ({
7603 ...alloc,
7604 totalBorrows: Math.floor(Math.random() * 10) + 1,
7605 immutableBorrows: Math.floor(Math.random() * 5),
7606 mutableBorrows: Math.floor(Math.random() * 3),
7607 hasActivity: true
7608 }));
7609
7610 if (syntheticData.length > 0) {
7611 renderHeatmapGrid(container, syntheticData, true);
7612 } else {
7613 container.innerHTML = `
7614 <div style="display: flex; align-items: center; justify-content: center; height: 100%;
7615 color: var(--text-secondary); font-size: 14px; text-align: center;">
7616 <div>
7617 <div style="margin-bottom: 8px;">📊 No borrow activity detected</div>
7618 <div style="font-size: 12px; opacity: 0.7;">This indicates efficient memory usage with minimal borrowing</div>
7619 </div>
7620 </div>
7621 `;
7622 }
7623 return;
7624 }
7625
7626 renderHeatmapGrid(container, borrowData, false);
7627
7628 function renderHeatmapGrid(container, data, isSynthetic) {
7629 const containerRect = container.getBoundingClientRect();
7630 const containerWidth = containerRect.width || 400;
7631 const containerHeight = containerRect.height || 300;
7632
7633 // Calculate optimal cell size and grid dimensions
7634 const maxCells = Math.min(data.length, 200);
7635 const aspectRatio = containerWidth / containerHeight;
7636 const cols = Math.floor(Math.sqrt(maxCells * aspectRatio));
7637 const rows = Math.ceil(maxCells / cols);
7638 const cellSize = Math.min((containerWidth - 10) / cols, (containerHeight - 10) / rows) - 2;
7639
7640 const maxBorrows = Math.max(...data.map(a => a.totalBorrows), 1);
7641
7642 // Add legend
7643 const legend = document.createElement('div');
7644 legend.style.cssText = `
7645 position: absolute;
7646 top: 5px;
7647 right: 5px;
7648 background: rgba(0,0,0,0.8);
7649 color: white;
7650 padding: 8px;
7651 border-radius: 4px;
7652 font-size: 10px;
7653 z-index: 100;
7654 `;
7655 legend.innerHTML = `
7656 <div>Borrow Activity ${isSynthetic ? '(Demo)' : ''}</div>
7657 <div style="margin-top: 4px;">
7658 <div style="display: flex; align-items: center; margin: 2px 0;">
7659 <div style="width: 12px; height: 12px; background: rgba(239, 68, 68, 0.3); margin-right: 4px;"></div>
7660 <span>Low</span>
7661 </div>
7662 <div style="display: flex; align-items: center; margin: 2px 0;">
7663 <div style="width: 12px; height: 12px; background: rgba(239, 68, 68, 0.7); margin-right: 4px;"></div>
7664 <span>Medium</span>
7665 </div>
7666 <div style="display: flex; align-items: center; margin: 2px 0;">
7667 <div style="width: 12px; height: 12px; background: rgba(239, 68, 68, 1.0); margin-right: 4px;"></div>
7668 <span>High</span>
7669 </div>
7670 </div>
7671 `;
7672 container.appendChild(legend);
7673
7674 data.slice(0, maxCells).forEach((alloc, i) => {
7675 const row = Math.floor(i / cols);
7676 const col = i % cols;
7677 const intensity = Math.max(0.1, alloc.totalBorrows / maxBorrows);
7678
7679 const cell = document.createElement('div');
7680 const x = col * (cellSize + 2) + 5;
7681 const y = row * (cellSize + 2) + 30; // Offset for legend
7682
7683 // Color based on borrow type
7684 let backgroundColor;
7685 if (alloc.mutableBorrows > alloc.immutableBorrows) {
7686 backgroundColor = `rgba(239, 68, 68, ${intensity})`; // Red for mutable
7687 } else if (alloc.immutableBorrows > 0) {
7688 backgroundColor = `rgba(59, 130, 246, ${intensity})`; // Blue for immutable
7689 } else {
7690 backgroundColor = `rgba(16, 185, 129, ${intensity})`; // Green for mixed/unknown
7691 }
7692
7693 cell.style.cssText = `
7694 position: absolute;
7695 left: ${x}px;
7696 top: ${y}px;
7697 width: ${cellSize}px;
7698 height: ${cellSize}px;
7699 background: ${backgroundColor};
7700 border: 1px solid rgba(255,255,255,0.3);
7701 border-radius: 2px;
7702 cursor: pointer;
7703 transition: all 0.2s ease;
7704 `;
7705
7706 const tooltipText = `
7707Variable: ${alloc.var_name || 'unnamed'}
7708Type: ${alloc.type_name || 'unknown'}
7709Total Borrows: ${alloc.totalBorrows}
7710Immutable: ${alloc.immutableBorrows}
7711Mutable: ${alloc.mutableBorrows}
7712 `.trim();
7713
7714 cell.title = tooltipText;
7715
7716 cell.addEventListener('mouseenter', () => {
7717 cell.style.transform = 'scale(1.2)';
7718 cell.style.zIndex = '10';
7719 cell.style.boxShadow = '0 2px 8px rgba(0,0,0,0.5)';
7720 });
7721
7722 cell.addEventListener('mouseleave', () => {
7723 cell.style.transform = 'scale(1)';
7724 cell.style.zIndex = '1';
7725 cell.style.boxShadow = 'none';
7726 });
7727
7728 cell.addEventListener('click', () => {
7729 const modalContent = `
7730 <div style="text-align: center; margin-bottom: 20px;">
7731 <div style="font-size: 48px; margin-bottom: 10px;">🔥</div>
7732 <div style="font-size: 24px; font-weight: 600; margin-bottom: 8px;">${alloc.var_name || 'unnamed'}</div>
7733 <div style="opacity: 0.8; font-size: 16px;">${alloc.type_name || 'unknown'}</div>
7734 </div>
7735 <div style="background: rgba(255, 255, 255, 0.1); padding: 20px; border-radius: 12px; margin-bottom: 20px;">
7736 <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; text-align: center;">
7737 <div>
7738 <div style="font-size: 24px; font-weight: 700; color: #f87171;">${alloc.totalBorrows}</div>
7739 <div style="opacity: 0.8; font-size: 12px;">Total Borrows</div>
7740 </div>
7741 <div>
7742 <div style="font-size: 24px; font-weight: 700; color: #60a5fa;">${alloc.immutableBorrows}</div>
7743 <div style="opacity: 0.8; font-size: 12px;">Immutable</div>
7744 </div>
7745 <div>
7746 <div style="font-size: 24px; font-weight: 700; color: #fb7185;">${alloc.mutableBorrows}</div>
7747 <div style="opacity: 0.8; font-size: 12px;">Mutable</div>
7748 </div>
7749 </div>
7750 </div>
7751 <div style="background: rgba(255, 255, 255, 0.05); padding: 16px; border-radius: 8px;">
7752 <div style="font-size: 14px; opacity: 0.9;">
7753 <div style="margin-bottom: 8px;"><strong>Variable Size:</strong> ${formatBytes(alloc.size || 0)}</div>
7754 <div style="margin-bottom: 8px;"><strong>Borrow Ratio:</strong> ${alloc.immutableBorrows > 0 ? (alloc.mutableBorrows / alloc.immutableBorrows).toFixed(2) : 'N/A'} (Mut/Immut)</div>
7755 <div style="margin-bottom: 8px;"><strong>Activity Level:</strong> ${alloc.totalBorrows > 10 ? 'High' : alloc.totalBorrows > 5 ? 'Medium' : 'Low'}</div>
7756 <div><strong>Safety:</strong> ${alloc.mutableBorrows === 0 ? '✅ Read-only' : alloc.mutableBorrows < alloc.immutableBorrows ? '⚠️ Mostly read' : '🔥 Write-heavy'}</div>
7757 </div>
7758 </div>
7759 `;
7760 createModal(`🔥 Borrow Analysis`, modalContent);
7761 });
7762
7763 container.appendChild(cell);
7764 });
7765
7766 console.log(`✅ Enhanced Borrow Heatmap rendered with ${Math.min(data.length, maxCells)} cells${isSynthetic ? ' (synthetic data)' : ''}`);
7767 }
7768}
7769
7770function renderInteractiveVariableGraph(allocations) {
7771 console.log('🕸️ Rendering Interactive Variable Relationships Graph...');
7772
7773 const container = document.getElementById('graph');
7774 if (!container) {
7775 console.warn('graph container not found');
7776 return;
7777 }
7778
7779 container.innerHTML = '';
7780 container.style.position = 'relative';
7781 container.style.overflow = 'hidden';
7782 container.style.background = 'var(--bg-primary)';
7783 container.style.border = '1px solid var(--border-light)';
7784 container.style.borderRadius = '8px';
7785
7786 // Create interactive graph with D3-like functionality
7787 const containerRect = container.getBoundingClientRect();
7788 const width = containerRect.width || 600;
7789 const height = containerRect.height || 400;
7790
7791 // Graph state
7792 let zoomLevel = 1;
7793 let panX = 0;
7794 let panY = 0;
7795 let selectedNode = null;
7796 let isDragging = false;
7797 let dragTarget = null;
7798
7799 // Create nodes with relationship analysis
7800 const nodes = allocations.slice(0, 100).map((alloc, i) => {
7801 const baseSize = Math.sqrt(alloc.size || 100) / 10 + 8;
7802 return {
7803 id: i,
7804 name: alloc.var_name || ('var_' + i),
7805 type: alloc.type_name || 'unknown',
7806 size: alloc.size || 0,
7807 nodeSize: Math.max(baseSize, 12),
7808 x: Math.random() * (width - 100) + 50,
7809 y: Math.random() * (height - 100) + 50,
7810 vx: 0,
7811 vy: 0,
7812 alloc: alloc,
7813 isLeaked: alloc.is_leaked,
7814 borrowCount: alloc.borrow_count || 0,
7815 cloneInfo: alloc.clone_info,
7816 fixed: false
7817 };
7818 });
7819
7820 // Create relationships based on various criteria
7821 const links = [];
7822 for (let i = 0; i < nodes.length; i++) {
7823 for (let j = i + 1; j < nodes.length; j++) {
7824 const nodeA = nodes[i];
7825 const nodeB = nodes[j];
7826 let relationship = null;
7827 let strength = 0;
7828
7829 // Check for clone relationships
7830 if (nodeA.cloneInfo && nodeB.cloneInfo) {
7831 if (nodeA.cloneInfo.original_ptr === nodeB.alloc.ptr ||
7832 nodeB.cloneInfo.original_ptr === nodeA.alloc.ptr) {
7833 relationship = 'clone';
7834 strength = 0.8;
7835 }
7836 }
7837
7838 // Check for type similarity
7839 if (!relationship && nodeA.type === nodeB.type && nodeA.type !== 'unknown') {
7840 relationship = 'type_similar';
7841 strength = 0.3;
7842 }
7843
7844 // Check for thread affinity
7845 if (!relationship && nodeA.alloc.thread_id === nodeB.alloc.thread_id &&
7846 nodeA.alloc.thread_id !== undefined) {
7847 relationship = 'thread_affinity';
7848 strength = 0.2;
7849 }
7850
7851 // Check for temporal proximity (allocated around same time)
7852 if (!relationship && nodeA.alloc.timestamp_alloc && nodeB.alloc.timestamp_alloc) {
7853 const timeDiff = Math.abs(nodeA.alloc.timestamp_alloc - nodeB.alloc.timestamp_alloc);
7854 if (timeDiff < 1000000) { // Within 1ms
7855 relationship = 'temporal';
7856 strength = 0.4;
7857 }
7858 }
7859
7860 // Add link if relationship found
7861 if (relationship && (strength > 0.2 || Math.random() < 0.05)) {
7862 links.push({
7863 source: i,
7864 target: j,
7865 relationship,
7866 strength,
7867 sourceNode: nodeA,
7868 targetNode: nodeB
7869 });
7870 }
7871 }
7872 }
7873
7874 // Add control panel
7875 const controls = document.createElement('div');
7876 controls.style.cssText = `
7877 position: absolute;
7878 top: 10px;
7879 left: 10px;
7880 background: rgba(0,0,0,0.8);
7881 color: white;
7882 padding: 10px;
7883 border-radius: 6px;
7884 font-size: 12px;
7885 z-index: 1000;
7886 user-select: none;
7887 `;
7888 controls.innerHTML = `
7889 <div style="margin-bottom: 8px; font-weight: bold;">🎮 Graph Controls</div>
7890 <button id="zoom-in" style="margin: 2px; padding: 4px 8px; font-size: 11px;">🔍+ Zoom In</button>
7891 <button id="zoom-out" style="margin: 2px; padding: 4px 8px; font-size: 11px;">🔍- Zoom Out</button>
7892 <button id="reset-view" style="margin: 2px; padding: 4px 8px; font-size: 11px;">🏠 Reset</button>
7893 <button id="auto-layout" style="margin: 2px; padding: 4px 8px; font-size: 11px;">🔄 Layout</button>
7894 <div style="margin-top: 8px; font-size: 10px;">
7895 <div>Nodes: ${nodes.length}</div>
7896 <div>Links: ${links.length}</div>
7897 <div>Zoom: <span id="zoom-display">100%</span></div>
7898 </div>
7899 `;
7900 container.appendChild(controls);
7901
7902 // Add legend
7903 const legend = document.createElement('div');
7904 legend.style.cssText = `
7905 position: absolute;
7906 top: 10px;
7907 right: 10px;
7908 background: rgba(0,0,0,0.8);
7909 color: white;
7910 padding: 10px;
7911 border-radius: 6px;
7912 font-size: 11px;
7913 z-index: 1000;
7914 user-select: none;
7915 `;
7916 legend.innerHTML = `
7917 <div style="font-weight: bold; margin-bottom: 6px;">🔗 Relationships</div>
7918 <div style="margin: 3px 0;"><span style="color: #ff6b6b;">━━</span> Clone</div>
7919 <div style="margin: 3px 0;"><span style="color: #4ecdc4;">━━</span> Type Similar</div>
7920 <div style="margin: 3px 0;"><span style="color: #45b7d1;">━━</span> Thread Affinity</div>
7921 <div style="margin: 3px 0;"><span style="color: #f9ca24;">━━</span> Temporal</div>
7922 <div style="margin-top: 8px; font-weight: bold;">🎯 Nodes</div>
7923 <div style="margin: 3px 0;"><span style="color: #ff6b6b;">●</span> Leaked</div>
7924 <div style="margin: 3px 0;"><span style="color: #6c5ce7;">●</span> High Borrow</div>
7925 <div style="margin: 3px 0;"><span style="color: #a8e6cf;">●</span> Normal</div>
7926 `;
7927 container.appendChild(legend);
7928
7929 // Create info panel for selected node
7930 const infoPanel = document.createElement('div');
7931 infoPanel.style.cssText = `
7932 position: absolute;
7933 bottom: 10px;
7934 left: 10px;
7935 background: rgba(0,0,0,0.9);
7936 color: white;
7937 padding: 12px;
7938 border-radius: 6px;
7939 font-size: 11px;
7940 max-width: 250px;
7941 z-index: 1000;
7942 display: none;
7943 `;
7944 container.appendChild(infoPanel);
7945
7946 // Render function
7947 function render() {
7948 // Clear existing nodes and links
7949 container.querySelectorAll('.graph-node, .graph-link').forEach(el => el.remove());
7950
7951 // Render links first (behind nodes)
7952 links.forEach(link => {
7953 const sourceNode = nodes[link.source];
7954 const targetNode = nodes[link.target];
7955
7956 const linkEl = document.createElement('div');
7957 linkEl.className = 'graph-link';
7958
7959 const dx = (targetNode.x - sourceNode.x) * zoomLevel;
7960 const dy = (targetNode.y - sourceNode.y) * zoomLevel;
7961 const length = Math.sqrt(dx * dx + dy * dy);
7962 const angle = Math.atan2(dy, dx) * 180 / Math.PI;
7963
7964 const x = sourceNode.x * zoomLevel + panX;
7965 const y = sourceNode.y * zoomLevel + panY;
7966
7967 let color;
7968 switch(link.relationship) {
7969 case 'clone': color = '#ff6b6b'; break;
7970 case 'type_similar': color = '#4ecdc4'; break;
7971 case 'thread_affinity': color = '#45b7d1'; break;
7972 case 'temporal': color = '#f9ca24'; break;
7973 default: color = '#666';
7974 }
7975
7976 linkEl.style.cssText = `
7977 position: absolute;
7978 left: ${x}px;
7979 top: ${y}px;
7980 width: ${length}px;
7981 height: ${Math.max(link.strength * 2, 1)}px;
7982 background: linear-gradient(90deg, ${color} 60%, transparent 60%);
7983 background-size: 8px 100%;
7984 opacity: ${0.4 + link.strength * 0.3};
7985 transform-origin: 0 50%;
7986 transform: rotate(${angle}deg);
7987 z-index: 1;
7988 pointer-events: none;
7989 `;
7990
7991 container.appendChild(linkEl);
7992 });
7993
7994 // Render nodes
7995 nodes.forEach((node, i) => {
7996 const nodeEl = document.createElement('div');
7997 nodeEl.className = 'graph-node';
7998 nodeEl.dataset.nodeId = i;
7999
8000 const x = node.x * zoomLevel + panX - (node.nodeSize * zoomLevel) / 2;
8001 const y = node.y * zoomLevel + panY - (node.nodeSize * zoomLevel) / 2;
8002 const size = node.nodeSize * zoomLevel;
8003
8004 // Determine node color based on properties
8005 let color;
8006 if (node.isLeaked) {
8007 color = '#ff6b6b'; // Red for leaked
8008 } else if (node.borrowCount > 5) {
8009 color = '#6c5ce7'; // Purple for high borrow activity
8010 } else {
8011 color = `hsl(${(node.type.length * 47) % 360}, 65%, 60%)`;
8012 }
8013
8014 nodeEl.style.cssText = `
8015 position: absolute;
8016 left: ${x}px;
8017 top: ${y}px;
8018 width: ${size}px;
8019 height: ${size}px;
8020 background: ${color};
8021 border: ${selectedNode === i ? '3px solid #fff' : '2px solid rgba(255,255,255,0.7)'};
8022 border-radius: 50%;
8023 cursor: ${node.fixed ? 'move' : 'pointer'};
8024 transition: none;
8025 z-index: 10;
8026 box-shadow: 0 2px 8px rgba(0,0,0,0.3);
8027 `;
8028
8029 // Add node label for larger nodes
8030 if (size > 20) {
8031 const label = document.createElement('div');
8032 label.style.cssText = `
8033 position: absolute;
8034 top: ${size + 4}px;
8035 left: 50%;
8036 transform: translateX(-50%);
8037 font-size: ${Math.max(zoomLevel * 10, 8)}px;
8038 color: var(--text-primary);
8039 white-space: nowrap;
8040 pointer-events: none;
8041 text-shadow: 1px 1px 2px rgba(255,255,255,0.8);
8042 font-weight: 600;
8043 `;
8044 label.textContent = node.name.length > 8 ? node.name.substring(0, 8) + '...' : node.name;
8045 nodeEl.appendChild(label);
8046 }
8047
8048 // Add event listeners
8049 nodeEl.addEventListener('click', () => selectNode(i));
8050 nodeEl.addEventListener('mousedown', (e) => startDrag(e, i));
8051
8052 container.appendChild(nodeEl);
8053 });
8054
8055 // Update zoom display
8056 document.getElementById('zoom-display').textContent = Math.round(zoomLevel * 100) + '%';
8057 }
8058
8059 // Event handlers
8060 function selectNode(nodeId) {
8061 selectedNode = nodeId;
8062 const node = nodes[nodeId];
8063
8064 // Show info panel
8065 infoPanel.style.display = 'block';
8066 infoPanel.innerHTML = `
8067 <div style="font-weight: bold; margin-bottom: 8px; color: #4ecdc4;">📋 ${node.name}</div>
8068 <div><strong>Type:</strong> ${node.type}</div>
8069 <div><strong>Size:</strong> ${formatBytes(node.size)}</div>
8070 <div><strong>Leaked:</strong> ${node.isLeaked ? '❌ Yes' : '✅ No'}</div>
8071 <div><strong>Borrows:</strong> ${node.borrowCount}</div>
8072 ${node.cloneInfo ? `<div><strong>Clones:</strong> ${node.cloneInfo.clone_count || 0}</div>` : ''}
8073 <div><strong>Thread:</strong> ${node.alloc.thread_id || 'Unknown'}</div>
8074 <div style="margin-top: 8px; font-size: 10px; opacity: 0.8;">
8075 Click and drag to move • Double-click to pin
8076 </div>
8077 `;
8078
8079 render();
8080 }
8081
8082 function startDrag(e, nodeId) {
8083 e.preventDefault();
8084 e.stopPropagation(); // Prevent container panning
8085 isDragging = true;
8086 dragTarget = nodeId;
8087
8088 const rect = container.getBoundingClientRect();
8089 const startX = e.clientX;
8090 const startY = e.clientY;
8091 const startNodeX = nodes[nodeId].x;
8092 const startNodeY = nodes[nodeId].y;
8093
8094 // Visual feedback
8095 const nodeEl = document.querySelector(`[data-node-id="${nodeId}"]`);
8096 if (nodeEl) {
8097 nodeEl.style.transform = 'scale(1.2)';
8098 nodeEl.style.zIndex = '100';
8099 }
8100
8101 function onMouseMove(e) {
8102 if (!isDragging || dragTarget === null) return;
8103
8104 // Calculate movement in world coordinates
8105 const dx = (e.clientX - startX) / zoomLevel;
8106 const dy = (e.clientY - startY) / zoomLevel;
8107
8108 // Update node position
8109 nodes[dragTarget].x = Math.max(20, Math.min(width - 20, startNodeX + dx));
8110 nodes[dragTarget].y = Math.max(20, Math.min(height - 20, startNodeY + dy));
8111 nodes[dragTarget].fixed = true;
8112
8113 render();
8114 }
8115
8116 function onMouseUp() {
8117 isDragging = false;
8118
8119 // Reset visual feedback
8120 if (nodeEl) {
8121 nodeEl.style.transform = '';
8122 nodeEl.style.zIndex = '10';
8123 }
8124
8125 dragTarget = null;
8126 document.removeEventListener('mousemove', onMouseMove);
8127 document.removeEventListener('mouseup', onMouseUp);
8128 }
8129
8130 document.addEventListener('mousemove', onMouseMove);
8131 document.addEventListener('mouseup', onMouseUp);
8132 }
8133
8134 // Control event listeners
8135 document.getElementById('zoom-in').addEventListener('click', () => {
8136 zoomLevel = Math.min(zoomLevel * 1.2, 3);
8137 render();
8138 });
8139
8140 document.getElementById('zoom-out').addEventListener('click', () => {
8141 zoomLevel = Math.max(zoomLevel / 1.2, 0.3);
8142 render();
8143 });
8144
8145 document.getElementById('reset-view').addEventListener('click', () => {
8146 zoomLevel = 1;
8147 panX = 0;
8148 panY = 0;
8149 selectedNode = null;
8150 infoPanel.style.display = 'none';
8151 nodes.forEach(node => node.fixed = false);
8152 render();
8153 });
8154
8155 document.getElementById('auto-layout').addEventListener('click', () => {
8156 // Simple force-directed layout simulation
8157 for (let iteration = 0; iteration < 50; iteration++) {
8158 // Repulsion between nodes
8159 for (let i = 0; i < nodes.length; i++) {
8160 nodes[i].vx = 0;
8161 nodes[i].vy = 0;
8162
8163 for (let j = 0; j < nodes.length; j++) {
8164 if (i === j) continue;
8165
8166 const dx = nodes[i].x - nodes[j].x;
8167 const dy = nodes[i].y - nodes[j].y;
8168 const distance = Math.sqrt(dx * dx + dy * dy) + 0.1;
8169 const force = 100 / (distance * distance);
8170
8171 nodes[i].vx += (dx / distance) * force;
8172 nodes[i].vy += (dy / distance) * force;
8173 }
8174 }
8175
8176 // Attraction along links
8177 links.forEach(link => {
8178 const source = nodes[link.source];
8179 const target = nodes[link.target];
8180 const dx = target.x - source.x;
8181 const dy = target.y - source.y;
8182 const distance = Math.sqrt(dx * dx + dy * dy) + 0.1;
8183 const force = distance * 0.01 * link.strength;
8184
8185 source.vx += (dx / distance) * force;
8186 source.vy += (dy / distance) * force;
8187 target.vx -= (dx / distance) * force;
8188 target.vy -= (dy / distance) * force;
8189 });
8190
8191 // Apply velocities
8192 nodes.forEach(node => {
8193 if (!node.fixed) {
8194 node.x += node.vx * 0.1;
8195 node.y += node.vy * 0.1;
8196
8197 // Keep within bounds
8198 node.x = Math.max(30, Math.min(width - 30, node.x));
8199 node.y = Math.max(30, Math.min(height - 30, node.y));
8200 }
8201 });
8202 }
8203
8204 render();
8205 });
8206
8207 // Mouse wheel zoom
8208 container.addEventListener('wheel', (e) => {
8209 e.preventDefault();
8210 const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
8211 const rect = container.getBoundingClientRect();
8212 const mouseX = e.clientX - rect.left;
8213 const mouseY = e.clientY - rect.top;
8214
8215 // Zoom towards mouse position
8216 const beforeZoomX = (mouseX - panX) / zoomLevel;
8217 const beforeZoomY = (mouseY - panY) / zoomLevel;
8218
8219 zoomLevel = Math.max(0.3, Math.min(3, zoomLevel * zoomFactor));
8220
8221 // Adjust pan to keep mouse position fixed
8222 panX = mouseX - beforeZoomX * zoomLevel;
8223 panY = mouseY - beforeZoomY * zoomLevel;
8224
8225 render();
8226 });
8227
8228 // Container pan functionality
8229 let isPanning = false;
8230 let panStartX = 0;
8231 let panStartY = 0;
8232 let panStartPanX = 0;
8233 let panStartPanY = 0;
8234
8235 container.addEventListener('mousedown', (e) => {
8236 // Only start panning if not clicking on a node
8237 if (!e.target.classList.contains('graph-node')) {
8238 isPanning = true;
8239 panStartX = e.clientX;
8240 panStartY = e.clientY;
8241 panStartPanX = panX;
8242 panStartPanY = panY;
8243 container.style.cursor = 'grabbing';
8244 }
8245 });
8246
8247 container.addEventListener('mousemove', (e) => {
8248 if (isPanning) {
8249 panX = panStartPanX + (e.clientX - panStartX);
8250 panY = panStartPanY + (e.clientY - panStartY);
8251 render();
8252 }
8253 });
8254
8255 container.addEventListener('mouseup', () => {
8256 isPanning = false;
8257 container.style.cursor = 'default';
8258 });
8259
8260 container.addEventListener('mouseleave', () => {
8261 isPanning = false;
8262 container.style.cursor = 'default';
8263 });
8264
8265 // Initial render
8266 render();
8267
8268 console.log(`✅ Interactive Variable Graph rendered with ${nodes.length} nodes and ${links.length} relationships`);
8269}
8270
8271function populateAllocationTable(allocations) {
8272 console.log('📋 Populating allocation table...');
8273
8274 const tbody = document.getElementById('allocTable');
8275 if (!tbody) {
8276 console.warn('allocTable not found');
8277 return;
8278 }
8279
8280 tbody.innerHTML = '';
8281
8282 // Show first 100 allocations
8283 allocations.slice(0, 100).forEach(alloc => {
8284 const row = document.createElement('tr');
8285 row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700';
8286
8287 const status = alloc.is_leaked ?
8288 '<span class="status-badge status-leaked">Leaked</span>' :
8289 alloc.timestamp_dealloc ?
8290 '<span class="status-badge status-freed">Freed</span>' :
8291 '<span class="status-badge status-active">Active</span>';
8292
8293 row.innerHTML = `
8294 <td class="px-3 py-2 text-sm font-mono">${alloc.var_name || 'unnamed'}</td>
8295 <td class="px-3 py-2 text-sm">${alloc.type_name || 'unknown'}</td>
8296 <td class="px-3 py-2 text-sm">${formatBytes(alloc.size || 0)}</td>
8297 <td class="px-3 py-2 text-sm">${status}</td>
8298 `;
8299
8300 tbody.appendChild(row);
8301 });
8302
8303 console.log('✅ Allocation table populated with', Math.min(allocations.length, 100), 'entries');
8304}
8305
8306// Utility function for formatting bytes
8307function formatBytes(bytes) {
8308 if (bytes === 0) return '0 B';
8309 const k = 1024;
8310 const sizes = ['B', 'KB', 'MB', 'GB'];
8311 const i = Math.floor(Math.log(bytes) / Math.log(k));
8312 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
8313}
8314
8315// Enhanced mode selector for memory analysis
8316document.addEventListener('DOMContentLoaded', function() {
8317 const modeButtons = document.querySelectorAll('.heatmap-mode-btn');
8318 const visualizations = {
8319 heatmap: document.getElementById('memoryHeatmap'),
8320 type: document.getElementById('typeChart'),
8321 distribution: document.getElementById('distributionChart')
8322 };
8323
8324 modeButtons.forEach(btn => {
8325 btn.addEventListener('click', () => {
8326 // Remove active from all buttons
8327 modeButtons.forEach(b => b.classList.remove('active'));
8328 btn.classList.add('active');
8329
8330 // Hide all visualizations
8331 Object.values(visualizations).forEach(viz => {
8332 if (viz) viz.style.display = 'none';
8333 });
8334
8335 // Show selected visualization
8336 const mode = btn.dataset.mode;
8337 if (visualizations[mode]) {
8338 visualizations[mode].style.display = 'block';
8339 }
8340 });
8341 });
8342});
8343
8344console.log('📦 Dashboard JavaScript loaded');
8345"#.to_string()
8346}
8347
8348fn prepare_safety_risk_data(allocations: &[AllocationInfo]) -> Result<String, BinaryExportError> {
8350 let mut safety_risks = Vec::new();
8351
8352 for allocation in allocations {
8354 if allocation.size > 1024 * 1024 {
8358 safety_risks.push(json!({
8360 "location": format!("{}::{}",
8361 allocation.scope_name.as_deref().unwrap_or("unknown"),
8362 allocation.var_name.as_deref().unwrap_or("unnamed")),
8363 "operation": "Large Memory Allocation",
8364 "risk_level": "Medium",
8365 "description": format!("Large allocation of {} bytes may indicate unsafe buffer operations", allocation.size)
8366 }));
8367 }
8368
8369 if allocation.is_leaked {
8371 safety_risks.push(json!({
8372 "location": format!("{}::{}",
8373 allocation.scope_name.as_deref().unwrap_or("unknown"),
8374 allocation.var_name.as_deref().unwrap_or("unnamed")),
8375 "operation": "Memory Leak",
8376 "risk_level": "High",
8377 "description": "Memory leak detected - potential unsafe memory management"
8378 }));
8379 }
8380
8381 if allocation.borrow_count > 10 {
8383 safety_risks.push(json!({
8384 "location": format!("{}::{}",
8385 allocation.scope_name.as_deref().unwrap_or("unknown"),
8386 allocation.var_name.as_deref().unwrap_or("unnamed")),
8387 "operation": "High Borrow Count",
8388 "risk_level": "Medium",
8389 "description": format!("High borrow count ({}) may indicate unsafe sharing patterns", allocation.borrow_count)
8390 }));
8391 }
8392
8393 if let Some(type_name) = &allocation.type_name {
8395 if type_name.contains("*mut") || type_name.contains("*const") {
8396 safety_risks.push(json!({
8397 "location": format!("{}::{}",
8398 allocation.scope_name.as_deref().unwrap_or("unknown"),
8399 allocation.var_name.as_deref().unwrap_or("unnamed")),
8400 "operation": "Raw Pointer Usage",
8401 "risk_level": "High",
8402 "description": format!("Raw pointer type '{}' requires unsafe operations", type_name)
8403 }));
8404 }
8405
8406 if type_name.contains("CString")
8408 || type_name.contains("CStr")
8409 || type_name.contains("c_void")
8410 || type_name.contains("extern")
8411 {
8412 safety_risks.push(json!({
8413 "location": format!("{}::{}",
8414 allocation.scope_name.as_deref().unwrap_or("unknown"),
8415 allocation.var_name.as_deref().unwrap_or("unnamed")),
8416 "operation": "FFI Boundary Crossing",
8417 "risk_level": "Medium",
8418 "description": format!("FFI type '{}' crosses safety boundaries", type_name)
8419 }));
8420 }
8421 }
8422
8423 if let Some(lifetime_ms) = allocation.lifetime_ms {
8425 if lifetime_ms < 1 {
8426 safety_risks.push(json!({
8428 "location": format!("{}::{}",
8429 allocation.scope_name.as_deref().unwrap_or("unknown"),
8430 allocation.var_name.as_deref().unwrap_or("unnamed")),
8431 "operation": "Short-lived Allocation",
8432 "risk_level": "Low",
8433 "description": format!("Very short lifetime ({}ms) may indicate unsafe temporary operations", lifetime_ms)
8434 }));
8435 }
8436 }
8437 }
8438
8439 if safety_risks.is_empty() {
8441 safety_risks.push(json!({
8442 "location": "Global Analysis",
8443 "operation": "Safety Scan Complete",
8444 "risk_level": "Low",
8445 "description": "No significant safety risks detected in current allocations"
8446 }));
8447 }
8448
8449 serde_json::to_string(&safety_risks).map_err(|e| {
8450 BinaryExportError::SerializationError(format!("Failed to serialize safety risk data: {e}",))
8451 })
8452}
8453
8454pub fn parse_binary_to_html_direct<P: AsRef<Path>>(
8456 binary_path: P,
8457 html_path: P,
8458 project_name: &str,
8459) -> Result<(), BinaryExportError> {
8460 convert_binary_to_html(binary_path, html_path, project_name)
8461}