1use crate::cfg::{BasicBlock, BlockKind, Cfg, Path, PathKind, Terminator};
6
7pub struct PathSummarizer;
8
9impl PathSummarizer {
10 pub fn summarize(blocks: &[BasicBlock]) -> String {
12 if blocks.is_empty() {
13 return "Empty path".to_string();
14 }
15
16 let mut flow = Vec::new();
17 for block in blocks {
18 let summary = Self::summarize_block(block);
19 if !summary.is_empty() {
20 flow.push(summary);
21 }
22 }
23
24 if flow.is_empty() {
25 return "no logical effects".to_string();
26 }
27
28 flow.join(" -> ")
29 }
30
31 pub fn summarize_block(block: &BasicBlock) -> String {
33 let mut effects = Vec::new();
34
35 match block.kind {
37 BlockKind::Entry => effects.push("[Entry]".to_string()),
38 BlockKind::Exit => {}
39 BlockKind::Normal => {}
40 }
41
42 for stmt in &block.statements {
44 if let Some(distilled) = Self::distill_statement(stmt) {
45 effects.push(distilled);
46 }
47 }
48
49 match &block.terminator {
51 Terminator::Return => effects.push("[Return]".to_string()),
52 Terminator::Abort(msg) => effects.push(format!("[Abort: {}]", msg)),
53 Terminator::Unreachable => effects.push("[Unreachable]".to_string()),
54 _ => {}
55 }
56
57 effects.join(" -> ")
58 }
59
60 fn distill_statement(stmt: &str) -> Option<String> {
61 if stmt.contains("StorageLive")
63 || stmt.contains("StorageDead")
64 || stmt.contains("Nop")
65 || stmt.contains("FakeRead")
66 {
67 return None;
68 }
69
70 let stmt = stmt.trim();
71
72 if let Some(call_start) = stmt.find("Call(") {
75 let inner = &stmt[call_start + 5..];
76 let mut depth = 0;
77 let mut end = None;
78 for (i, c) in inner.char_indices() {
79 match c {
80 '(' => depth += 1,
81 ')' => {
82 if depth == 0 {
83 end = Some(i);
84 break;
85 }
86 depth -= 1;
87 }
88 ',' if depth == 0 => {
89 end = Some(i);
90 break;
91 }
92 _ => {}
93 }
94 }
95 if let Some(e) = end {
96 return Some(format!("[Call: {}]", inner[..e].trim()));
97 }
98 }
99
100 if stmt.starts_with("Assign(") && stmt.ends_with(')') {
102 let inner = &stmt[7..stmt.len() - 1];
103 let mut depth = 0;
104 let mut comma_pos = None;
105
106 for (i, c) in inner.char_indices() {
107 match c {
108 '(' | '[' => depth += 1,
109 ')' | ']' => depth -= 1,
110 ',' if depth == 0 => {
111 comma_pos = Some(i);
112 break;
113 }
114 _ => {}
115 }
116 }
117
118 if let Some(pos) = comma_pos {
119 let lhs = inner[..pos].trim();
120 let rhs = inner[pos + 1..].trim();
121
122 let rhs_display = if rhs.starts_with("Constant(") && rhs.ends_with(')') {
124 &rhs[9..rhs.len() - 1].trim()
125 } else {
126 rhs
127 };
128
129 return Some(format!("[State: {} = {}]", lhs, rhs_display));
130 }
131 }
132
133 None
134 }
135}
136
137pub fn summarize_path(cfg: &Cfg, path: &Path) -> String {
139 let mut blocks = Vec::new();
140 for &bid in &path.blocks {
141 if let Some(node_idx) = cfg.node_indices().find(|&n| cfg[n].id == bid) {
142 blocks.push(cfg[node_idx].clone());
143 }
144 }
145
146 let summary = PathSummarizer::summarize(&blocks);
147
148 match path.kind {
149 PathKind::Normal => format!("{} ({} blocks)", summary, path.len()),
150 PathKind::Error => format!("{} -> error ({} blocks)", summary, path.len()),
151 PathKind::Degenerate => format!("{} -> dead end ({} blocks)", summary, path.len()),
152 PathKind::Unreachable => format!("Unreachable: {} ({} blocks)", summary, path.len()),
153 }
154}