use crate::cfg::Cfg;
use rusqlite;
use super::{enumerate_paths, enumerate_paths_iterative, Path, PathKind, PathLimits};
#[derive(Debug, Clone)]
pub struct PathEnumerationResult {
pub paths: Vec<Path>,
pub limits_hit: LimitsHit,
pub stats: EnumerationStats,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LimitsHit {
None,
MaxLength,
MaxPaths,
LoopUnroll,
Multiple,
}
#[derive(Debug, Clone)]
pub struct EnumerationStats {
pub total_paths: usize,
pub normal_paths: usize,
pub error_paths: usize,
pub degenerate_paths: usize,
pub unreachable_paths: usize,
pub avg_path_length: f64,
pub max_path_length: usize,
pub loop_count: usize,
}
pub fn enumerate_paths_with_metadata(cfg: &Cfg, limits: &PathLimits) -> PathEnumerationResult {
let paths = enumerate_paths_iterative(cfg, limits);
let mut stats = EnumerationStats {
total_paths: paths.len(),
normal_paths: 0,
error_paths: 0,
degenerate_paths: 0,
unreachable_paths: 0,
avg_path_length: 0.0,
max_path_length: 0,
loop_count: 0,
};
if paths.is_empty() {
return PathEnumerationResult {
paths,
limits_hit: LimitsHit::None,
stats,
};
}
let mut total_length = 0;
for path in &paths {
match path.kind {
PathKind::Normal => stats.normal_paths += 1,
PathKind::Error => stats.error_paths += 1,
PathKind::Degenerate => stats.degenerate_paths += 1,
PathKind::Unreachable => stats.unreachable_paths += 1,
}
let len = path.len();
total_length += len;
if len > stats.max_path_length {
stats.max_path_length = len;
}
}
stats.avg_path_length = total_length as f64 / paths.len() as f64;
stats.loop_count = crate::cfg::loops::find_loop_headers(cfg).len();
let limits_hit = if paths.len() >= limits.max_paths {
LimitsHit::MaxPaths
} else if stats.max_path_length >= limits.max_length {
LimitsHit::MaxLength
} else {
LimitsHit::None
};
PathEnumerationResult {
paths,
limits_hit,
stats,
}
}
pub fn get_or_enumerate_paths(
cfg: &Cfg,
function_id: i64,
function_hash: &str,
limits: &PathLimits,
db_conn: &mut rusqlite::Connection,
) -> Result<Vec<Path>, String> {
use crate::storage::paths::{get_cached_paths, invalidate_function_paths, store_paths};
let current_hash: Option<String> = db_conn
.query_row(
"SELECT cfg_hash FROM cfg_blocks WHERE function_id = ?1 LIMIT 1",
rusqlite::params![function_id],
|row| row.get(0),
)
.unwrap_or(None);
if let Some(ref hash) = current_hash {
if hash == function_hash {
let paths = get_cached_paths(db_conn, function_id)
.map_err(|e| format!("Failed to retrieve cached paths: {}", e))?;
if !paths.is_empty() {
return Ok(paths);
}
}
}
let paths = enumerate_paths(cfg, limits);
let _ = invalidate_function_paths(db_conn, function_id);
store_paths(db_conn, function_id, &paths)
.map_err(|e| format!("Failed to store enumerated paths: {}", e))?;
Ok(paths)
}