use crate::cfg::{BasicBlock, BlockKind, Cfg, Path, PathKind, Terminator};
pub struct PathSummarizer;
impl PathSummarizer {
pub fn summarize(blocks: &[BasicBlock]) -> String {
if blocks.is_empty() {
return "Empty path".to_string();
}
let mut flow = Vec::new();
for block in blocks {
let summary = Self::summarize_block(block);
if !summary.is_empty() {
flow.push(summary);
}
}
if flow.is_empty() {
return "no logical effects".to_string();
}
flow.join(" -> ")
}
pub fn summarize_block(block: &BasicBlock) -> String {
let mut effects = Vec::new();
match block.kind {
BlockKind::Entry => effects.push("[Entry]".to_string()),
BlockKind::Exit => {}
BlockKind::Normal => {}
}
for stmt in &block.statements {
if let Some(distilled) = Self::distill_statement(stmt) {
effects.push(distilled);
}
}
match &block.terminator {
Terminator::Return => effects.push("[Return]".to_string()),
Terminator::Abort(msg) => effects.push(format!("[Abort: {}]", msg)),
Terminator::Unreachable => effects.push("[Unreachable]".to_string()),
_ => {}
}
effects.join(" -> ")
}
fn distill_statement(stmt: &str) -> Option<String> {
if stmt.contains("StorageLive")
|| stmt.contains("StorageDead")
|| stmt.contains("Nop")
|| stmt.contains("FakeRead")
{
return None;
}
let stmt = stmt.trim();
if let Some(call_start) = stmt.find("Call(") {
let inner = &stmt[call_start + 5..];
let mut depth = 0;
let mut end = None;
for (i, c) in inner.char_indices() {
match c {
'(' => depth += 1,
')' => {
if depth == 0 {
end = Some(i);
break;
}
depth -= 1;
}
',' if depth == 0 => {
end = Some(i);
break;
}
_ => {}
}
}
if let Some(e) = end {
return Some(format!("[Call: {}]", inner[..e].trim()));
}
}
if stmt.starts_with("Assign(") && stmt.ends_with(')') {
let inner = &stmt[7..stmt.len() - 1];
let mut depth = 0;
let mut comma_pos = None;
for (i, c) in inner.char_indices() {
match c {
'(' | '[' => depth += 1,
')' | ']' => depth -= 1,
',' if depth == 0 => {
comma_pos = Some(i);
break;
}
_ => {}
}
}
if let Some(pos) = comma_pos {
let lhs = inner[..pos].trim();
let rhs = inner[pos + 1..].trim();
let rhs_display = if rhs.starts_with("Constant(") && rhs.ends_with(')') {
&rhs[9..rhs.len() - 1].trim()
} else {
rhs
};
return Some(format!("[State: {} = {}]", lhs, rhs_display));
}
}
None
}
}
pub fn summarize_path(cfg: &Cfg, path: &Path) -> String {
let mut blocks = Vec::new();
for &bid in &path.blocks {
if let Some(node_idx) = cfg.node_indices().find(|&n| cfg[n].id == bid) {
blocks.push(cfg[node_idx].clone());
}
}
let summary = PathSummarizer::summarize(&blocks);
match path.kind {
PathKind::Normal => format!("{} ({} blocks)", summary, path.len()),
PathKind::Error => format!("{} -> error ({} blocks)", summary, path.len()),
PathKind::Degenerate => format!("{} -> dead end ({} blocks)", summary, path.len()),
PathKind::Unreachable => format!("Unreachable: {} ({} blocks)", summary, path.len()),
}
}