use crate::analyzers::function_registry::FunctionSignatureRegistry;
use crate::analyzers::signature_extractor::SignatureExtractor;
use crate::analyzers::type_registry::GlobalTypeRegistry;
use crate::priority::call_graph::CallGraph;
use std::path::{Path, PathBuf};
use std::sync::Arc;
pub use crate::analyzers::call_graph::{
CallGraphExtractor,
MacroExpansionStats,
MacroHandlingConfig,
};
pub fn extract_call_graph(file: &syn::File, path: &Path) -> CallGraph {
let extractor = CallGraphExtractor::new(path.to_path_buf());
extractor.extract(file)
}
pub fn extract_call_graph_multi_file(files: &[(syn::File, PathBuf)]) -> CallGraph {
let start_time = std::time::Instant::now();
let mut combined_graph = CallGraph::new();
let mut all_unresolved_calls = Vec::new();
use crate::analyzers::call_graph::PathResolverBuilder;
let mut path_resolver_builder = PathResolverBuilder::new();
let phase1_start = std::time::Instant::now();
let total_files = files.len();
for (file, path) in files.iter() {
let mut extractor = CallGraphExtractor::new(path.clone());
extractor.extract_phase1(file);
combined_graph.merge(extractor.graph_builder.call_graph.clone());
all_unresolved_calls.extend(extractor.unresolved_calls.clone());
path_resolver_builder = path_resolver_builder.analyze_file(path.clone(), file);
let node_count = combined_graph.node_count();
crate::io::progress::AnalysisProgress::with_global(|p| {
p.update_progress(crate::io::progress::PhaseProgress::Progress {
current: node_count,
total: node_count, });
});
}
let phase1_duration = phase1_start.elapsed();
log::info!(
"Phase 1 completed in {:.2}s: {} files, {} functions, {} unresolved calls",
phase1_duration.as_secs_f64(),
total_files,
combined_graph.get_all_functions().count(),
all_unresolved_calls.len()
);
let resolver_build_start = std::time::Instant::now();
let path_resolver = path_resolver_builder
.build()
.with_function_index(&combined_graph);
let resolver_build_duration = resolver_build_start.elapsed();
log::info!(
"PathResolver built in {:.2}s",
resolver_build_duration.as_secs_f64()
);
let phase2_start = std::time::Instant::now();
let multi_file_path = PathBuf::from("multi-file");
let mut resolved_calls = Vec::new();
let total_unresolved = all_unresolved_calls.len();
let mut call_resolver_hits = 0;
let mut path_resolver_hits = 0;
let mut unresolved_count = 0;
{
let resolver_build_start = std::time::Instant::now();
let resolver =
crate::analyzers::call_graph::CallResolver::new(&combined_graph, &multi_file_path);
log::info!(
"CallResolver built in {:.2}s",
resolver_build_start.elapsed().as_secs_f64()
);
for (idx, unresolved) in all_unresolved_calls.iter().enumerate() {
if let Some(callee) = resolver.resolve_call(unresolved) {
resolved_calls.push(crate::priority::call_graph::FunctionCall {
caller: unresolved.caller.clone(),
callee,
call_type: unresolved.call_type.clone(),
});
call_resolver_hits += 1;
} else {
if let Some(callee) =
path_resolver.resolve_call(&unresolved.caller.file, &unresolved.callee_name)
{
resolved_calls.push(crate::priority::call_graph::FunctionCall {
caller: unresolved.caller.clone(),
callee,
call_type: unresolved.call_type.clone(),
});
path_resolver_hits += 1;
} else {
unresolved_count += 1;
}
}
if idx % 500 == 0 {
let total_nodes = combined_graph.node_count();
crate::io::progress::AnalysisProgress::with_global(|p| {
p.update_progress(crate::io::progress::PhaseProgress::Progress {
current: total_nodes,
total: total_nodes,
});
});
}
if idx > 0 && idx % 1000 == 0 {
log::info!(
"Phase 2 progress: {}/{} calls ({:.1}%), CallResolver: {}, PathResolver: {}, Unresolved: {}",
idx,
total_unresolved,
(idx as f64 / total_unresolved as f64) * 100.0,
call_resolver_hits,
path_resolver_hits,
unresolved_count
);
}
}
}
let phase2_duration = phase2_start.elapsed();
log::info!(
"Phase 2 completed in {:.2}s: CallResolver: {}, PathResolver: {}, Still unresolved: {}",
phase2_duration.as_secs_f64(),
call_resolver_hits,
path_resolver_hits,
unresolved_count
);
for call in resolved_calls {
combined_graph.add_call(call);
}
let phase3_start = std::time::Instant::now();
combined_graph.resolve_cross_file_calls();
let phase3_duration = phase3_start.elapsed();
log::info!("Phase 3 completed in {:.2}s", phase3_duration.as_secs_f64());
let final_node_count = combined_graph.node_count();
crate::io::progress::AnalysisProgress::with_global(|p| {
p.update_progress(crate::io::progress::PhaseProgress::Progress {
current: final_node_count,
total: final_node_count,
});
});
let total_duration = start_time.elapsed();
log::info!(
"extract_call_graph_multi_file total time: {:.2}s (Phase1: {:.2}s, Phase2: {:.2}s, Phase3: {:.2}s)",
total_duration.as_secs_f64(),
phase1_duration.as_secs_f64(),
phase2_duration.as_secs_f64(),
phase3_duration.as_secs_f64()
);
combined_graph
}
pub fn extract_call_graph_with_signatures(
file: &syn::File,
path: &Path,
_type_registry: Arc<GlobalTypeRegistry>,
) -> (CallGraph, FunctionSignatureRegistry) {
let mut extractor = SignatureExtractor::new();
extractor.extract_from_file(file);
let function_registry = extractor.registry;
let call_graph = extract_call_graph(file, path);
(call_graph, function_registry)
}
#[cfg(test)]
pub use crate::analyzers::call_graph::*;