use crate::cli::{SpatialAnalysisArgs, Cli};
use crate::cfg::{Cfg, export::calculate_coordinate_statistics};
use crate::storage::load_cfg_from_db;
use anyhow::Result;
pub fn spatial_analysis(args: &SpatialAnalysisArgs, cli: &Cli) -> Result<()> {
let cfg = load_cfg_from_db(&cli.backend, &args.function, None)?;
let stats = calculate_coordinate_statistics(&cfg);
match cli.output {
crate::cli::OutputFormat::Human => {
print_spatial_analysis_human(&cfg, &args.function, &stats, args.distribution, args.hot_paths)
}
crate::cli::OutputFormat::Json => {
print_spatial_analysis_json(&cfg, &args.function, &stats, args.distribution, args.hot_paths)
}
crate::cli::OutputFormat::Pretty => {
let json = print_spatial_analysis_json(&cfg, &args.function, &stats, args.distribution, args.hot_paths)?;
println!("{}", serde_json::to_string_pretty(&json)?);
Ok(())
}
}
}
fn print_spatial_analysis_human(
cfg: &Cfg,
function_name: &str,
stats: &crate::cfg::export::CoordinateStatistics,
show_distribution: bool,
show_hot_paths: bool,
) -> Result<()> {
println!("4D Spatial Analysis: {}", function_name);
println!("{}", "=".repeat(60));
println!("\n📊 Coordinate Statistics:");
println!(" Total Blocks: {}", stats.total_blocks);
println!(" Dominator Depth (X): max={}, avg={:.2}, min={}",
stats.max_coord_x, stats.avg_coord_x, find_min_coord_x(cfg));
println!(" Loop Nesting (Y): max={}, avg={:.2}, min={}",
stats.max_coord_y, stats.avg_coord_y, find_min_coord_y(cfg));
println!(" Branch Distance (Z): max={}, avg={:.2}, min={}",
stats.max_coord_z, stats.avg_coord_z, find_min_coord_z(cfg));
println!("\n🎯 Complexity Assessment:");
assess_complexity(stats);
if show_distribution {
println!("\n📈 Coordinate Distribution:");
show_coordinate_distribution(cfg);
}
if show_hot_paths {
println!("\n🔥 Hot Path Analysis:");
show_hot_path_analysis(cfg, stats);
}
println!("\n💡 Spatial Insights:");
generate_spatial_insights(cfg, stats);
Ok(())
}
fn find_min_coord_x(cfg: &Cfg) -> i64 {
cfg.node_indices()
.filter_map(|n| cfg.node_weight(n))
.map(|b| b.coord_x)
.min()
.unwrap_or(0)
}
fn find_min_coord_y(cfg: &Cfg) -> i64 {
cfg.node_indices()
.filter_map(|n| cfg.node_weight(n))
.map(|b| b.coord_y)
.min()
.unwrap_or(0)
}
fn find_min_coord_z(cfg: &Cfg) -> i64 {
cfg.node_indices()
.filter_map(|n| cfg.node_weight(n))
.map(|b| b.coord_z)
.min()
.unwrap_or(0)
}
fn assess_complexity(stats: &crate::cfg::export::CoordinateStatistics) {
let depth_score = stats.avg_coord_x;
let loop_score = stats.avg_coord_y;
let branch_score = stats.avg_coord_z;
let overall_complexity = (depth_score + loop_score * 2.0 + branch_score) / 4.0;
let (level, color) = if overall_complexity < 1.0 {
("Low", "🟢")
} else if overall_complexity < 2.5 {
("Medium", "🟡")
} else if overall_complexity < 4.0 {
("High", "🟠")
} else {
("Very High", "🔴")
};
println!(" Overall Complexity: {} ({})", level, color);
println!(" Score: {:.2}/5.0", overall_complexity);
if depth_score > 2.0 {
println!(" → Deep control flow nesting detected");
}
if loop_score > 0.5 {
println!(" → Significant loop complexity");
}
if branch_score > 2.0 {
println!(" → High conditional branching");
}
}
fn show_coordinate_distribution(cfg: &Cfg) {
let mut depth_dist = std::collections::HashMap::new();
let mut loop_dist = std::collections::HashMap::new();
for node_idx in cfg.node_indices() {
if let Some(block) = cfg.node_weight(node_idx) {
*depth_dist.entry(block.coord_x).or_insert(0) += 1;
*loop_dist.entry(block.coord_y).or_insert(0) += 1;
}
}
println!(" Dominator Depth Distribution:");
let mut depths: Vec<_> = depth_dist.iter().collect();
depths.sort_by_key(|&(k, _)| k);
for (depth, count) in depths {
let bar = "â–ˆ".repeat((*count as usize) * 2);
println!(" X={}: {} ({})", depth, bar, count);
}
println!(" Loop Nesting Distribution:");
let mut loops: Vec<_> = loop_dist.iter().collect();
loops.sort_by_key(|&(k, _)| k);
for (depth, count) in loops {
let bar = "â–ˆ".repeat((*count as usize) * 2);
println!(" Y={}: {} ({})", depth, bar, count);
}
}
fn show_hot_path_analysis(cfg: &Cfg, stats: &crate::cfg::export::CoordinateStatistics) {
let mut blocks: Vec<_> = cfg.node_indices()
.filter_map(|n| cfg.node_weight(n).map(|b| (n, b)))
.collect();
blocks.sort_by(|a, b| {
let score_a = a.1.coord_x + a.1.coord_y + a.1.coord_z;
let score_b = b.1.coord_x + b.1.coord_y + b.1.coord_z;
score_b.cmp(&score_a)
});
println!(" Top 5 Most Complex Blocks:");
for (i, (node_idx, block)) in blocks.iter().take(5).enumerate() {
let score = block.coord_x + block.coord_y + block.coord_z;
println!(" {}. Block {}: X={}, Y={}, Z={}, Score={}",
i + 1, block.id, block.coord_x, block.coord_y, block.coord_z, score);
}
}
fn generate_spatial_insights(cfg: &Cfg, stats: &crate::cfg::export::CoordinateStatistics) {
let avg_complexity = stats.avg_coord_x + stats.avg_coord_y + stats.avg_coord_z;
let mut outliers = Vec::new();
for node_idx in cfg.node_indices() {
if let Some(block) = cfg.node_weight(node_idx) {
let complexity = block.coord_x + block.coord_y + block.coord_z;
if complexity as f64 > avg_complexity * 1.5 {
outliers.push((block.id, complexity));
}
}
}
if !outliers.is_empty() {
println!(" Spatial Outliers (>1.5x avg complexity):");
outliers.sort_by_key(|&(_, score)| std::cmp::Reverse(score));
for (block_id, score) in outliers.iter().take(3) {
println!(" → Block {}: complexity {}", block_id, score);
}
}
if stats.max_coord_x > 3 {
let deep_blocks: Vec<_> = cfg.node_indices()
.filter_map(|n| cfg.node_weight(n))
.filter(|b| b.coord_x >= stats.max_coord_x - 1)
.map(|b| b.id)
.collect();
if !deep_blocks.is_empty() {
println!(" Deep Nesting Zones (depth={}):", stats.max_coord_x);
println!(" → Blocks: {:?}", deep_blocks);
}
}
if stats.max_coord_y > 1 {
let loop_blocks: Vec<_> = cfg.node_indices()
.filter_map(|n| cfg.node_weight(n))
.filter(|b| b.coord_y > 0)
.map(|b| b.id)
.collect();
if !loop_blocks.is_empty() {
println!(" Loop-Intensive Regions: {} blocks in loops", loop_blocks.len());
}
}
}
fn print_spatial_analysis_json(
cfg: &Cfg,
function_name: &str,
stats: &crate::cfg::export::CoordinateStatistics,
_show_distribution: bool,
_show_hot_paths: bool,
) -> Result<serde_json::Value> {
let mut blocks: Vec<_> = cfg.node_indices()
.filter_map(|n| cfg.node_weight(n).map(|b| (n, b)))
.collect();
blocks.sort_by(|a, b| {
let score_a = a.1.coord_x + a.1.coord_y + a.1.coord_z;
let score_b = b.1.coord_x + b.1.coord_y + b.1.coord_z;
score_b.cmp(&score_a)
});
let top_blocks: Vec<_> = blocks.iter().take(10).map(|(node_idx, block)| {
serde_json::json!({
"block_id": block.id,
"node_index": node_idx.index(),
"coordinates": {
"x": block.coord_x,
"y": block.coord_y,
"z": block.coord_z,
},
"complexity_score": block.coord_x + block.coord_y + block.coord_z,
})
}).collect();
Ok(serde_json::json!({
"function": function_name,
"statistics": {
"total_blocks": stats.total_blocks,
"dominator_depth": {
"max": stats.max_coord_x,
"avg": stats.avg_coord_x,
"min": find_min_coord_x(cfg),
},
"loop_nesting": {
"max": stats.max_coord_y,
"avg": stats.avg_coord_y,
"min": find_min_coord_y(cfg),
},
"branch_distance": {
"max": stats.max_coord_z,
"avg": stats.avg_coord_z,
"min": find_min_coord_z(cfg),
},
},
"top_complex_blocks": top_blocks,
"overall_complexity": (stats.avg_coord_x + stats.avg_coord_y * 2.0 + stats.avg_coord_z) / 4.0,
}))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cfg::{BasicBlock, Terminator, BlockKind};
use petgraph::graph::DiGraph;
fn create_complex_test_cfg() -> Cfg {
let mut g = DiGraph::new();
let b0 = g.add_node(BasicBlock {
id: 0,
db_id: None,
kind: BlockKind::Entry,
statements: vec![],
terminator: Terminator::Goto { target: 1 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b1 = g.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 3, coord_y: 2, coord_z: 4, });
let b2 = g.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Exit,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 1,
coord_y: 0,
coord_z: 1,
});
g.add_edge(b0, b1, crate::cfg::EdgeType::Fallthrough);
g.add_edge(b1, b2, crate::cfg::EdgeType::Fallthrough);
g
}
#[test]
fn test_find_min_coord_x() {
let cfg = create_complex_test_cfg();
assert_eq!(find_min_coord_x(&cfg), 0);
}
#[test]
fn test_find_min_coord_y() {
let cfg = create_complex_test_cfg();
assert_eq!(find_min_coord_y(&cfg), 0);
}
#[test]
fn test_find_min_coord_z() {
let cfg = create_complex_test_cfg();
assert_eq!(find_min_coord_z(&cfg), 0);
}
#[test]
fn test_spatial_analysis_json_structure() {
let cfg = create_complex_test_cfg();
let stats = calculate_coordinate_statistics(&cfg);
let json = print_spatial_analysis_json(&cfg, "test", &stats, true, true).unwrap();
assert!(json.is_object());
assert_eq!(json["function"], "test");
assert!(json["statistics"].is_object());
assert!(json["top_complex_blocks"].is_array());
assert!(json["overall_complexity"].is_f64());
}
}