#![allow(clippy::uninlined_format_args)]
#[cfg(feature = "control-flow")]
use threatflux_binary_analysis::{AnalysisConfig, BinaryAnalyzer};
#[cfg(feature = "control-flow")]
use threatflux_binary_analysis::types::*;
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
use threatflux_binary_analysis::DisassemblyEngine;
mod common;
#[cfg(feature = "control-flow")]
use common::fixtures::*;
#[test]
#[cfg(feature = "control-flow")]
fn test_enhanced_control_flow_analysis() {
let data = create_realistic_elf_64();
let config = AnalysisConfig {
enable_disassembly: true,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: DisassemblyEngine::Auto,
enable_control_flow: true,
enable_call_graph: false,
enable_cognitive_complexity: true,
enable_advanced_loops: true,
enable_entropy: false,
enable_symbols: true,
max_analysis_size: 10 * 1024 * 1024,
architecture_hint: Some(Architecture::X86_64),
call_graph_config: None,
};
let analyzer = BinaryAnalyzer::with_config(config);
let result = analyzer.analyze(&data).unwrap();
assert_eq!(result.format, BinaryFormat::Elf);
assert_eq!(result.architecture, Architecture::X86_64);
if let Some(enhanced_cf) = result.enhanced_control_flow {
let _stats = &enhanced_cf.cognitive_complexity_summary;
let _loop_stats = &enhanced_cf.loop_analysis_summary;
}
}
#[test]
#[cfg(feature = "control-flow")]
fn test_call_graph_analysis() {
let data = create_realistic_elf_64();
let call_graph_config = CallGraphConfig {
analyze_indirect_calls: true,
detect_tail_calls: true,
resolve_virtual_calls: false,
follow_import_thunks: true,
max_call_depth: Some(10),
include_library_calls: false,
};
let config = AnalysisConfig {
enable_disassembly: true,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: DisassemblyEngine::Auto,
enable_control_flow: false,
enable_call_graph: true,
enable_cognitive_complexity: false,
enable_advanced_loops: false,
enable_entropy: false,
enable_symbols: true,
max_analysis_size: 10 * 1024 * 1024,
architecture_hint: Some(Architecture::X86_64),
call_graph_config: Some(call_graph_config),
};
let analyzer = BinaryAnalyzer::with_config(config);
let result = analyzer.analyze(&data).unwrap();
if let Some(call_graph) = result.call_graph {
assert!(!call_graph.nodes.is_empty());
let stats = &call_graph.statistics;
assert_eq!(stats.total_functions, call_graph.nodes.len());
assert_eq!(stats.total_calls, call_graph.edges.len());
let dot_output = call_graph.to_dot();
assert!(dot_output.contains("digraph CallGraph"));
assert!(dot_output.contains("rankdir=TB"));
#[cfg(feature = "serde-support")]
{
let json_output = call_graph.to_json();
assert!(!json_output.is_empty());
assert!(json_output.contains("nodes"));
assert!(json_output.contains("edges"));
}
let cycles = call_graph.detect_cycles();
assert!(cycles.is_empty() || !cycles.is_empty());
}
}
#[test]
#[cfg(feature = "control-flow")]
fn test_comprehensive_enhanced_analysis() {
let data = create_realistic_elf_64();
let config = AnalysisConfig {
enable_disassembly: true,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: DisassemblyEngine::Auto,
enable_control_flow: true,
enable_call_graph: true,
enable_cognitive_complexity: true,
enable_advanced_loops: true,
enable_entropy: false,
enable_symbols: true,
max_analysis_size: 10 * 1024 * 1024,
architecture_hint: Some(Architecture::X86_64),
call_graph_config: Some(CallGraphConfig::default()),
};
let analyzer = BinaryAnalyzer::with_config(config);
let result = analyzer.analyze(&data).unwrap();
assert!(result.enhanced_control_flow.is_some());
assert!(result.call_graph.is_some());
let enhanced_cf = result.enhanced_control_flow.unwrap();
let call_graph = result.call_graph.unwrap();
let cf_functions = enhanced_cf.cognitive_complexity_summary.functions_analyzed;
let cg_functions = call_graph.statistics.total_functions;
if cf_functions > 0 && cg_functions > 0 {
let ratio = (cf_functions as f64) / (cg_functions as f64);
assert!(
(0.5..=2.0).contains(&ratio),
"Function counts too different: CF={}, CG={}",
cf_functions,
cg_functions
);
}
}
#[test]
#[cfg(feature = "control-flow")]
fn test_complexity_metrics_calculation() {
let data = create_realistic_elf_64();
let config = AnalysisConfig {
enable_disassembly: true,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: DisassemblyEngine::Auto,
enable_control_flow: true,
enable_call_graph: false,
enable_cognitive_complexity: true,
enable_advanced_loops: true,
enable_entropy: false,
enable_symbols: true,
max_analysis_size: 10 * 1024 * 1024,
architecture_hint: Some(Architecture::X86_64),
call_graph_config: None,
};
let analyzer = BinaryAnalyzer::with_config(config);
let result = analyzer.analyze(&data).unwrap();
if let Some(enhanced_cf) = result.enhanced_control_flow {
for cfg in &enhanced_cf.control_flow_graphs {
let complexity = &cfg.complexity;
assert!(complexity.cyclomatic_complexity >= 1);
assert!(complexity.basic_block_count > 0);
if !cfg.basic_blocks.is_empty()
&& cfg
.basic_blocks
.iter()
.any(|bb| !bb.instructions.is_empty())
{
if let Some(ref halstead) = complexity.halstead_metrics {
assert!(halstead.vocabulary > 0);
assert!(halstead.length > 0);
assert!(halstead.volume > 0.0);
}
if complexity.halstead_metrics.is_some() {
if let Some(mi) = complexity.maintainability_index {
assert!((0.0..=100.0).contains(&mi));
}
}
}
}
}
}
#[test]
#[cfg(feature = "control-flow")]
fn test_loop_analysis() {
let data = create_test_elf_with_loops();
let config = AnalysisConfig {
enable_disassembly: true,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: DisassemblyEngine::Auto,
enable_control_flow: true,
enable_call_graph: false,
enable_cognitive_complexity: false,
enable_advanced_loops: true,
enable_entropy: false,
enable_symbols: true,
max_analysis_size: 10 * 1024 * 1024,
architecture_hint: Some(Architecture::X86_64),
call_graph_config: None,
};
let analyzer = BinaryAnalyzer::with_config(config);
let result = analyzer.analyze(&data).unwrap();
if let Some(enhanced_cf) = result.enhanced_control_flow {
let loop_stats = &enhanced_cf.loop_analysis_summary;
assert!(loop_stats.natural_loops <= loop_stats.total_loops);
assert!(loop_stats.irreducible_loops <= loop_stats.total_loops);
assert!(loop_stats.nested_loops <= loop_stats.total_loops);
let total_by_type: usize = loop_stats.loops_by_type.values().sum();
assert!(total_by_type <= loop_stats.total_loops);
}
}
#[allow(dead_code)]
fn create_test_elf_with_functions() -> Vec<u8> {
let mut elf_data = vec![
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, ];
elf_data.extend_from_slice(&[
0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]);
while elf_data.len() < 0x1000 {
elf_data.push(0x00);
}
elf_data.extend_from_slice(&[
0x48, 0x89, 0xe5, 0xe8, 0x05, 0x00, 0x00, 0x00, 0xc3, 0x48, 0x89, 0xe5, 0xb8, 0x00, 0x00, 0x00, 0x00, 0xc3, 0x48, 0x89, 0xe5, 0xb8, 0x0a, 0x00, 0x00, 0x00, 0x48, 0x83, 0xe8, 0x01, 0x75, 0xfb, 0xc3, ]);
elf_data
}
#[allow(dead_code)]
fn create_test_elf_with_loops() -> Vec<u8> {
let mut elf_data = create_test_elf_with_functions();
elf_data.extend_from_slice(&[
0x48, 0x89, 0xe5, 0xb8, 0x05, 0x00, 0x00, 0x00, 0xbb, 0x03, 0x00, 0x00, 0x00, 0x48, 0x83, 0xeb, 0x01, 0x75, 0xfb, 0x48, 0x83, 0xe8, 0x01, 0x75, 0xf1, 0xc3, ]);
elf_data
}
#[test]
#[cfg(feature = "control-flow")]
fn test_enhanced_analysis_performance() {
use std::time::Instant;
let data = create_large_test_binary();
let config = AnalysisConfig {
enable_disassembly: true,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: DisassemblyEngine::Auto,
enable_control_flow: true,
enable_call_graph: true,
enable_cognitive_complexity: true,
enable_advanced_loops: true,
enable_entropy: false,
enable_symbols: true,
max_analysis_size: 10 * 1024 * 1024,
architecture_hint: Some(Architecture::X86_64),
call_graph_config: None,
};
let analyzer = BinaryAnalyzer::with_config(config);
let start = Instant::now();
let result = analyzer.analyze(&data);
let duration = start.elapsed();
assert!(
duration.as_secs() < 5,
"Analysis took too long: {:?}",
duration
);
assert!(result.is_ok(), "Analysis failed: {:?}", result.err());
if let Ok(analysis) = result {
assert!(analysis.enhanced_control_flow.is_some() || analysis.call_graph.is_some());
}
}
#[allow(dead_code)]
fn create_large_test_binary() -> Vec<u8> {
let mut data = create_test_elf_with_functions();
for i in 0..50 {
data.extend_from_slice(&[
0x48,
0x89,
0xe5, 0xb8,
(i & 0xff) as u8,
0x00,
0x00,
0x00, 0x48,
0x83,
0xf8,
0x0a, 0x7c,
0x05, 0x48,
0x83,
0xe8,
0x0a, 0xeb,
0x03, 0x48,
0x83,
0xc0,
0x01, 0xc3, ]);
}
data
}