use crate::cli::responses::*;
use crate::cli::{resolve_db_path, Cli, OutputFormat, UnreachableArgs};
use crate::output;
use anyhow::Result;
pub fn unreachable(args: &UnreachableArgs, cli: &Cli) -> Result<()> {
use crate::analysis::DeadSymbolJson;
use crate::analysis::MagellanBridge;
use crate::cfg::load_cfg_from_db;
use crate::cfg::reachability::find_unreachable;
use crate::storage::MirageDb;
use petgraph::visit::EdgeRef;
let db_path = resolve_db_path(cli.db.clone())?;
let uncalled_functions: Option<Vec<DeadSymbolJson>> = if args.include_uncalled {
match MagellanBridge::open(&db_path) {
Ok(bridge) => {
match bridge.dead_symbols("main") {
Ok(dead) => {
let json_symbols: Vec<DeadSymbolJson> =
dead.iter().map(|d| d.into()).collect();
Some(json_symbols)
}
Err(e) => {
eprintln!("Warning: Failed to detect uncalled functions: {}", e);
None
}
}
}
Err(e) => {
eprintln!(
"Warning: Could not open Magellan database for --include-uncalled: {}",
e
);
eprintln!("Note: --include-uncalled requires a Magellan code graph database");
None
}
}
} else {
None
};
let db = match MirageDb::open(&db_path) {
Ok(db) => db,
Err(_e) => {
if matches!(cli.output, OutputFormat::Json | OutputFormat::Pretty) {
let error = output::JsonError::database_not_found(&db_path);
let wrapper = output::JsonResponse::new(error);
println!("{}", wrapper.to_json());
std::process::exit(output::EXIT_DATABASE);
} else {
output::error(&format!("Failed to open database: {}", db_path));
output::info("Hint: Run 'magellan watch' to create the database");
std::process::exit(output::EXIT_DATABASE);
}
}
};
struct FunctionUnreachable {
function_name: String,
function_id: i64,
blocks: Vec<UnreachableBlock>,
}
if !db.is_sqlite() {
output::error("The 'unreachable' command currently requires SQLite backend.");
output::info("Use SQLite backend or run with --help for alternatives.");
std::process::exit(output::EXIT_USAGE);
}
let mut function_rows: Vec<(String, i64)> = Vec::new();
let mut stmt = match db.conn()?.prepare(
"SELECT name, id FROM graph_entities WHERE kind = 'Symbol' AND json_extract(data, '$.kind') = 'Function'") {
Ok(stmt) => stmt,
Err(e) => {
if matches!(cli.output, OutputFormat::Json | OutputFormat::Pretty) {
let error = output::JsonError::new(
"QueryError",
&format!("Failed to query functions: {}", e),
output::E_DATABASE_NOT_FOUND,
);
let wrapper = output::JsonResponse::new(error);
println!("{}", wrapper.to_json());
std::process::exit(output::EXIT_DATABASE);
} else {
output::error(&format!("Failed to query functions: {}", e));
std::process::exit(output::EXIT_DATABASE);
}
}
};
let rows_result = stmt.query_map([], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?))
});
match rows_result {
Ok(rows) => {
for row in rows {
match row {
Ok((name, id)) => function_rows.push((name, id)),
Err(e) => {
if matches!(cli.output, OutputFormat::Json | OutputFormat::Pretty) {
let error = output::JsonError::new(
"QueryError",
&format!("Failed to read function row: {}", e),
output::E_DATABASE_NOT_FOUND,
);
let wrapper = output::JsonResponse::new(error);
println!("{}", wrapper.to_json());
std::process::exit(output::EXIT_DATABASE);
} else {
output::error(&format!("Failed to read function row: {}", e));
std::process::exit(output::EXIT_DATABASE);
}
}
}
}
}
Err(e) => {
if matches!(cli.output, OutputFormat::Json | OutputFormat::Pretty) {
let error = output::JsonError::new(
"QueryError",
&format!("Failed to execute query: {}", e),
output::E_DATABASE_NOT_FOUND,
);
let wrapper = output::JsonResponse::new(error);
println!("{}", wrapper.to_json());
std::process::exit(output::EXIT_DATABASE);
} else {
output::error(&format!("Failed to execute query: {}", e));
std::process::exit(output::EXIT_DATABASE);
}
}
}
let mut all_results = Vec::new();
for (function_name, function_id) in function_rows {
match load_cfg_from_db(&db, function_id) {
Ok(cfg) => {
let unreachable_indices = find_unreachable(&cfg);
if !unreachable_indices.is_empty() {
let blocks: Vec<UnreachableBlock> = unreachable_indices
.iter()
.map(|&idx| {
let block = &cfg[idx];
let kind_str = format!("{:?}", block.kind);
let terminator_str = format!("{:?}", block.terminator);
let incoming_edges = if args.show_branches {
cfg.edge_references()
.filter(|edge| edge.target() == idx)
.filter_map(|edge| {
let source_block = &cfg[edge.source()];
cfg.edge_weight(edge.id()).map(|edge_type| IncomingEdge {
from_block: source_block.id,
edge_type: format!("{:?}", edge_type),
})
})
.collect()
} else {
vec![]
};
UnreachableBlock {
block_id: block.id,
kind: kind_str,
statements: block.statements.clone(),
terminator: terminator_str,
incoming_edges,
}
})
.collect();
all_results.push(FunctionUnreachable {
function_name,
function_id,
blocks,
});
}
}
Err(_) => {
continue;
}
}
}
let total_functions = all_results.len();
let functions_with_unreachable = all_results.iter().filter(|r| !r.blocks.is_empty()).count();
let total_blocks: usize = all_results.iter().map(|r| r.blocks.len()).sum();
match cli.output {
OutputFormat::Human => {
if let Some(ref uncalled) = uncalled_functions {
println!("Uncalled Functions ({}):", uncalled.len());
for dead in uncalled {
let name = dead.fqn.as_deref().unwrap_or("?");
println!(" - {} ({})", name, dead.kind);
println!(" File: {}", dead.file_path);
println!(" Reason: {}", dead.reason);
}
println!();
}
if total_blocks == 0 {
if uncalled_functions.is_none()
|| uncalled_functions
.as_ref()
.map(|v| v.is_empty())
.unwrap_or(false)
{
output::info("No unreachable code found");
}
return Ok(());
}
println!("Unreachable Code Blocks:");
println!(" Total blocks: {}", total_blocks);
println!(
" Functions with unreachable: {}/{}",
functions_with_unreachable, total_functions
);
println!();
for result in &all_results {
if result.blocks.is_empty() {
continue;
}
println!("Function: {}", result.function_name);
for block in &result.blocks {
println!(" Block {} ({})", block.block_id, block.kind);
if !block.statements.is_empty() {
for stmt in &block.statements {
println!(" - {}", stmt);
}
}
println!(" Terminator: {}", block.terminator);
println!();
}
if args.show_branches {
println!(" Incoming Edges:");
for block in &result.blocks {
if block.incoming_edges.is_empty() {
println!(
" Block {} has no incoming edges (entry or isolated)",
block.block_id
);
} else {
println!(" Block {} incoming edges:", block.block_id);
for edge in &block.incoming_edges {
println!(
" from block {} ({})",
edge.from_block, edge.edge_type
);
}
}
}
println!();
}
}
}
OutputFormat::Json | OutputFormat::Pretty => {
let all_blocks: Vec<UnreachableBlock> =
all_results.iter().flat_map(|r| r.blocks.clone()).collect();
let response = UnreachableResponse {
function: "all".to_string(),
total_functions,
functions_with_unreachable,
unreachable_count: total_blocks,
blocks: all_blocks,
uncalled_functions,
};
let wrapper = output::JsonResponse::new(response);
match cli.output {
OutputFormat::Json => println!("{}", wrapper.to_json()),
OutputFormat::Pretty => println!("{}", wrapper.to_pretty_json()),
_ => {}
}
}
}
Ok(())
}