use crate::cli::WasmOutputFormat;
use crate::wasm::{
analyzer::{AnalysisResult, WasmAnalyzer},
baseline::{Metrics, QualityAssessment, QualityBaseline},
profiler::AsyncProfiler,
security::{PatternDetector, VulnerabilityMatch},
verifier::{IncrementalVerifier, VerificationResult},
ProfilingReport,
};
use anyhow::Result;
use std::path::PathBuf;
use tracing::{debug, info};
#[allow(clippy::too_many_arguments)]
pub async fn handle_analyze_wasm(
wasm_file: PathBuf,
format: WasmOutputFormat,
verify: bool,
security: bool,
profile: bool,
baseline: Option<PathBuf>,
output: Option<PathBuf>,
verbose: bool,
) -> Result<()> {
info!("🔍 Analyzing WASM module: {}", wasm_file.display());
let binary = load_wasm_file(&wasm_file)?;
let analysis_result = run_basic_analysis(&binary)?;
let verification_result = run_verification_if_requested(verify, &binary).await?;
let security_results = run_security_scan_if_requested(security, &binary)?;
let profiling_results = run_profiling_if_requested(profile, &binary).await?;
let baseline_comparison =
run_baseline_comparison_if_requested(baseline, &binary, &analysis_result).await?;
let output_str = format_results(
format,
&analysis_result,
verification_result.as_ref(),
security_results.as_ref(),
profiling_results.as_ref(),
baseline_comparison.as_ref(),
verbose,
)?;
write_output(output_str, output)?;
check_for_failures(
verification_result.as_ref(),
security_results.as_ref(),
baseline_comparison.as_ref(),
)?;
info!("✅ WASM analysis complete");
Ok(())
}
fn load_wasm_file(wasm_file: &PathBuf) -> Result<Vec<u8>> {
let binary = std::fs::read(wasm_file)?;
debug!("Loaded WASM binary: {} bytes", binary.len());
Ok(binary)
}
fn run_basic_analysis(binary: &[u8]) -> Result<AnalysisResult> {
let analyzer = WasmAnalyzer::new()?;
analyzer.analyze(binary)
}
async fn run_verification_if_requested(
verify: bool,
binary: &[u8],
) -> Result<Option<VerificationResult>> {
if !verify {
return Ok(None);
}
info!("🔒 Running formal verification...");
let verifier = IncrementalVerifier::new()?;
Ok(Some(verifier.verify_module(binary)?))
}
fn run_security_scan_if_requested(
security: bool,
binary: &[u8],
) -> Result<Option<Vec<VulnerabilityMatch>>> {
if !security {
return Ok(None);
}
info!("🛡️ Running security vulnerability scanning...");
let mut detector = PatternDetector::new();
for payload in wasmparser::Parser::new(0).parse_all(binary) {
let payload = payload?;
detector.scan(&payload)?;
}
Ok(Some(detector.finalize()))
}
async fn run_profiling_if_requested(
profile: bool,
binary: &[u8],
) -> Result<Option<ProfilingReport>> {
if !profile {
return Ok(None);
}
info!("📊 Running performance profiling...");
let profiler = AsyncProfiler::new();
Ok(Some(profiler.profile_module(binary).await?))
}
async fn run_baseline_comparison_if_requested(
baseline: Option<PathBuf>,
_binary: &[u8],
analysis_result: &AnalysisResult,
) -> Result<Option<QualityAssessment>> {
let baseline_path = match baseline {
Some(path) => path,
None => return Ok(None),
};
info!("📈 Comparing with baseline: {}", baseline_path.display());
let baseline_metrics = load_and_analyze_baseline(&baseline_path)?;
let current_metrics = create_metrics_from_analysis(analysis_result);
let quality_baseline = QualityBaseline::new(baseline_metrics.clone(), baseline_metrics);
Ok(Some(quality_baseline.evaluate(¤t_metrics)))
}
fn load_and_analyze_baseline(baseline_path: &PathBuf) -> Result<Metrics> {
let baseline_binary = std::fs::read(baseline_path)?;
let baseline_analyzer = WasmAnalyzer::new()?;
let baseline_result = baseline_analyzer.analyze(&baseline_binary)?;
Ok(create_metrics_from_analysis(&baseline_result))
}
fn write_output(output_str: String, output: Option<PathBuf>) -> Result<()> {
if let Some(output_path) = output {
std::fs::write(&output_path, &output_str)?;
info!("✅ Results written to: {}", output_path.display());
} else {
println!("{output_str}");
}
Ok(())
}
fn check_for_failures(
verification: Option<&VerificationResult>,
security: Option<&Vec<VulnerabilityMatch>>,
baseline: Option<&QualityAssessment>,
) -> Result<()> {
check_verification_failure(verification)?;
check_security_failures(security)?;
check_baseline_failure(baseline)?;
Ok(())
}
fn check_verification_failure(verification: Option<&VerificationResult>) -> Result<()> {
if let Some(verification) = verification {
if !verification.is_safe() {
anyhow::bail!("❌ Verification failed: {verification:?}");
}
}
Ok(())
}
fn check_security_failures(security: Option<&Vec<VulnerabilityMatch>>) -> Result<()> {
if let Some(security) = security {
let critical_count = security
.iter()
.filter(|v| v.severity == crate::wasm::security::Severity::Critical)
.count();
if critical_count > 0 {
anyhow::bail!(
"❌ Found {critical_count} critical security vulnerabilities"
);
}
}
Ok(())
}
fn check_baseline_failure(baseline: Option<&QualityAssessment>) -> Result<()> {
if let Some(baseline_comp) = baseline {
if !baseline_comp.is_passing() {
anyhow::bail!("❌ Quality regression detected");
}
}
Ok(())
}
fn format_results(
format: WasmOutputFormat,
analysis: &AnalysisResult,
verification: Option<&VerificationResult>,
security: Option<&Vec<VulnerabilityMatch>>,
profiling: Option<&ProfilingReport>,
baseline: Option<&QualityAssessment>,
verbose: bool,
) -> Result<String> {
match format {
WasmOutputFormat::Summary => {
format_summary(analysis, verification, security, profiling, baseline)
}
WasmOutputFormat::Json => {
format_json(analysis, verification, security, profiling, baseline)
}
WasmOutputFormat::Detailed => format_detailed(
analysis,
verification,
security,
profiling,
baseline,
verbose,
),
WasmOutputFormat::Sarif => format_sarif(security.unwrap_or(&Vec::new())),
}
}
fn format_summary(
analysis: &AnalysisResult,
verification: Option<&VerificationResult>,
security: Option<&Vec<VulnerabilityMatch>>,
profiling: Option<&ProfilingReport>,
baseline: Option<&QualityAssessment>,
) -> Result<String> {
let mut output = String::new();
append_summary_header(&mut output);
append_basic_metrics(&mut output, analysis);
append_verification_status(&mut output, verification);
append_security_summary(&mut output, security);
append_profiling_summary(&mut output, profiling);
append_baseline_summary(&mut output, baseline);
Ok(output)
}
fn append_summary_header(output: &mut String) {
output.push_str("WASM Analysis Summary\n");
output.push_str("====================\n\n");
}
fn append_basic_metrics(output: &mut String, analysis: &AnalysisResult) {
output.push_str(&format!("Functions: {}\n", analysis.function_count));
output.push_str(&format!("Instructions: {}\n", analysis.instruction_count));
output.push_str(&format!("Binary Size: {} bytes\n", analysis.binary_size));
output.push_str(&format!("Memory Pages: {}\n", analysis.memory_pages));
output.push_str(&format!("Max Complexity: {}\n", analysis.max_complexity));
}
fn append_verification_status(output: &mut String, verification: Option<&VerificationResult>) {
if let Some(ver) = verification {
output.push_str(&format!(
"\nVerification: {}\n",
if ver.is_safe() {
"✅ SAFE"
} else {
"❌ UNSAFE"
}
));
}
}
fn append_security_summary(output: &mut String, security: Option<&Vec<VulnerabilityMatch>>) {
use crate::wasm::security::Severity;
if let Some(sec) = security {
let critical = count_by_severity(sec, Severity::Critical);
let high = count_by_severity(sec, Severity::High);
let medium = count_by_severity(sec, Severity::Medium);
let low = count_by_severity(sec, Severity::Low);
output.push_str("\nSecurity Vulnerabilities:\n");
output.push_str(&format!(" Critical: {critical}\n"));
output.push_str(&format!(" High: {high}\n"));
output.push_str(&format!(" Medium: {medium}\n"));
output.push_str(&format!(" Low: {low}\n"));
}
}
fn count_by_severity(
vulnerabilities: &[VulnerabilityMatch],
severity: crate::wasm::security::Severity,
) -> usize {
vulnerabilities
.iter()
.filter(|v| v.severity == severity)
.count()
}
fn append_profiling_summary(output: &mut String, profiling: Option<&ProfilingReport>) {
if let Some(prof) = profiling {
output.push_str("\nPerformance Profile:\n");
let control_pct = calculate_percentage(
prof.instruction_mix.control_flow,
prof.instruction_mix.total_instructions,
);
let memory_pct = calculate_percentage(
prof.instruction_mix.memory_ops,
prof.instruction_mix.total_instructions,
);
let arith_pct = calculate_percentage(
prof.instruction_mix.arithmetic,
prof.instruction_mix.total_instructions,
);
let call_pct = calculate_percentage(
prof.instruction_mix.calls,
prof.instruction_mix.total_instructions,
);
output.push_str(&format!(" Control Flow: {control_pct}%\n"));
output.push_str(&format!(" Memory Ops: {memory_pct}%\n"));
output.push_str(&format!(" Arithmetic: {arith_pct}%\n"));
output.push_str(&format!(" Function Calls: {call_pct}%\n"));
}
}
fn calculate_percentage(part: usize, total: usize) -> usize {
if total == 0 {
return 0;
}
(part * 100) / total
}
fn append_baseline_summary(output: &mut String, baseline: Option<&QualityAssessment>) {
if let Some(base) = baseline {
output.push_str("\nQuality Assessment:\n");
output.push_str(&format!(" Health Score: {:.1}%\n", base.overall_health));
output.push_str(&format!(
" Status: {}\n",
if base.is_passing() {
"✅ PASSING"
} else {
"❌ FAILING"
}
));
output.push_str(&format!(" {}\n", base.recommendation));
}
}
fn format_json(
analysis: &AnalysisResult,
verification: Option<&VerificationResult>,
security: Option<&Vec<VulnerabilityMatch>>,
profiling: Option<&ProfilingReport>,
baseline: Option<&QualityAssessment>,
) -> Result<String> {
let json_output = serde_json::json!({
"analysis": analysis,
"verification": verification,
"security": security,
"profiling": profiling,
"baseline": baseline,
});
Ok(serde_json::to_string_pretty(&json_output)?)
}
fn format_detailed(
analysis: &AnalysisResult,
verification: Option<&VerificationResult>,
security: Option<&Vec<VulnerabilityMatch>>,
profiling: Option<&ProfilingReport>,
baseline: Option<&QualityAssessment>,
verbose: bool,
) -> Result<String> {
let mut output = format_summary(analysis, verification, security, profiling, baseline)?;
if verbose {
append_detailed_information(&mut output, profiling, security);
}
Ok(output)
}
fn append_detailed_information(
output: &mut String,
profiling: Option<&ProfilingReport>,
security: Option<&Vec<VulnerabilityMatch>>,
) {
output.push_str("\n\nDetailed Analysis\n");
output.push_str("=================\n\n");
append_detailed_profiling(output, profiling);
append_detailed_vulnerabilities(output, security);
}
fn append_detailed_profiling(output: &mut String, profiling: Option<&ProfilingReport>) {
if let Some(prof) = profiling {
output.push_str("Instruction Breakdown:\n");
output.push_str(&format!(
" Total: {}\n",
prof.instruction_mix.total_instructions
));
output.push_str(&format!(
" Control Flow: {}\n",
prof.instruction_mix.control_flow
));
output.push_str(&format!(
" Memory Operations: {}\n",
prof.instruction_mix.memory_ops
));
output.push_str(&format!(
" Arithmetic: {}\n",
prof.instruction_mix.arithmetic
));
output.push_str(&format!(" Calls: {}\n", prof.instruction_mix.calls));
append_hot_functions(output, prof);
}
}
fn append_hot_functions(output: &mut String, prof: &ProfilingReport) {
if !prof.hot_functions.is_empty() {
output.push_str("\nHot Functions:\n");
for func in &prof.hot_functions {
output.push_str(&format!(
" {} - {:.1}% ({} samples)\n",
func.name, func.percentage, func.samples
));
}
}
}
fn append_detailed_vulnerabilities(
output: &mut String,
security: Option<&Vec<VulnerabilityMatch>>,
) {
if let Some(sec) = security {
if !sec.is_empty() {
output.push_str("\nVulnerability Details:\n");
for vuln in sec {
output.push_str(&format!(
" [{:?}] {} at offset {}\n",
vuln.severity, vuln.pattern, vuln.operator_index
));
}
}
}
}
fn format_sarif(vulnerabilities: &[VulnerabilityMatch]) -> Result<String> {
let sarif_output = create_sarif_output(vulnerabilities);
Ok(serde_json::to_string_pretty(&sarif_output)?)
}
fn create_sarif_output(vulnerabilities: &[VulnerabilityMatch]) -> serde_json::Value {
let rules = create_sarif_rules(vulnerabilities);
let results = create_sarif_results(vulnerabilities);
serde_json::json!({
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [{
"tool": {
"driver": {
"name": "pmat-wasm-analyzer",
"version": env!("CARGO_PKG_VERSION"),
"informationUri": "https://github.com/paiml/pmat",
"rules": rules
}
},
"results": results
}]
})
}
fn create_sarif_rules(vulnerabilities: &[VulnerabilityMatch]) -> Vec<serde_json::Value> {
let unique_patterns: std::collections::HashSet<_> =
vulnerabilities.iter().map(|v| &v.pattern).collect();
unique_patterns
.into_iter()
.map(|pattern| create_sarif_rule(pattern))
.collect()
}
fn create_sarif_rule(pattern: &str) -> serde_json::Value {
serde_json::json!({
"id": pattern,
"name": pattern,
"shortDescription": {
"text": format!("WASM vulnerability: {}", pattern)
},
"fullDescription": {
"text": format!("Security vulnerability pattern detected in WebAssembly module: {}", pattern)
},
"defaultConfiguration": {
"level": "warning"
}
})
}
fn create_sarif_results(vulnerabilities: &[VulnerabilityMatch]) -> Vec<serde_json::Value> {
vulnerabilities.iter().map(create_sarif_result).collect()
}
fn create_sarif_result(vuln: &VulnerabilityMatch) -> serde_json::Value {
use crate::wasm::security::Severity;
let level = match vuln.severity {
Severity::Critical | Severity::High => "error",
Severity::Medium => "warning",
Severity::Low => "note",
};
serde_json::json!({
"ruleId": vuln.pattern,
"level": level,
"message": {
"text": format!("Found {} vulnerability at instruction {}", vuln.pattern, vuln.operator_index)
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": "module.wasm"
},
"region": {
"startLine": vuln.operator_index,
"startColumn": 1
}
}
}]
})
}
fn create_metrics_from_analysis(analysis: &AnalysisResult) -> Metrics {
Metrics {
timestamp: chrono::Utc::now(),
complexity_p90: analysis.max_complexity.saturating_sub(2),
complexity_p95: analysis.max_complexity,
complexity_p99: analysis.max_complexity.saturating_add(2),
binary_size: analysis.binary_size,
init_time_ms: 10, memory_usage_mb: (analysis.memory_pages * 64) / 1024, function_count: analysis.function_count,
instruction_count: analysis.instruction_count,
}
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}