#![cfg_attr(coverage_nightly, coverage(off))]
use crate::services::deep_wasm::{
BytecodeAnalyzer, CorrelationEngine, DeepWasmAnalysisRequest, DeepWasmReport, DeepWasmResult,
Disassembler, DwarfParser, PipelineOverview, SourceLanguage, SourceMapHandler, SourceMetrics,
WasmInspector, WasmModuleAnalysis, WasmQualityGates,
};
use crate::services::rust_wasm_analyzer;
use std::fs;
use std::path::Path;
use wasmparser;
pub struct DeepWasmService {
wasm_inspector: WasmInspector,
dwarf_parser: DwarfParser,
source_map_handler: SourceMapHandler,
correlation_engine: CorrelationEngine,
quality_gates: WasmQualityGates,
bytecode_analyzer: BytecodeAnalyzer,
disassembler: Disassembler,
enable_deep_analysis: bool,
}
impl DeepWasmService {
pub fn new() -> Self {
Self {
wasm_inspector: WasmInspector::new(),
dwarf_parser: DwarfParser::new(),
source_map_handler: SourceMapHandler::new(),
correlation_engine: CorrelationEngine::new(),
quality_gates: WasmQualityGates::new(),
bytecode_analyzer: BytecodeAnalyzer::new(),
disassembler: Disassembler::new(),
enable_deep_analysis: true,
}
}
pub fn with_quality_gates(mut self, gates: WasmQualityGates) -> Self {
self.quality_gates = gates;
self
}
pub fn with_deep_analysis(mut self, enabled: bool) -> Self {
self.enable_deep_analysis = enabled;
self.bytecode_analyzer = BytecodeAnalyzer::with_deep_analysis(enabled);
self.disassembler = Disassembler::with_pattern_detection(enabled);
self
}
pub async fn analyze(
&self,
request: DeepWasmAnalysisRequest,
) -> DeepWasmResult<DeepWasmReport> {
let wasm_analysis = if let Some(ref wasm_path) = request.wasm_path {
self.wasm_inspector.inspect_file(wasm_path)?
} else {
WasmModuleAnalysis {
module_size_bytes: 0,
function_count: 0,
exported_functions: 0,
max_complexity: 0,
has_dwarf: false,
has_source_map: false,
}
};
let (bytecode_analysis, disassembled_functions, suspicious_patterns) =
if self.enable_deep_analysis && request.wasm_path.is_some() {
let wasm_bytes = fs::read(request.wasm_path.as_ref().expect("checked is_some"))
.map_err(crate::services::deep_wasm::DeepWasmError::Io)?;
let bytecode_result = self.bytecode_analyzer.analyze(&wasm_bytes)?;
let mut disassembled = Vec::new();
let mut all_patterns = Vec::new();
let parser = wasmparser::Parser::new(0);
let mut code_section_index = 0;
for payload in parser.parse_all(&wasm_bytes) {
if let Ok(wasmparser::Payload::CodeSectionEntry(function_body)) = payload {
let func_idx = code_section_index;
let func_analysis = bytecode_result
.functions
.iter()
.find(|f| f.function_index as usize == func_idx);
code_section_index += 1;
if let Some(func_analysis) = func_analysis {
if func_analysis.is_exported
|| func_analysis.complexity.cyclomatic_complexity > 10
{
let result = self.disassembler.disassemble_function(
func_analysis.function_index,
func_analysis.name.clone(),
&function_body,
)?;
let function_patterns =
self.disassembler.detect_patterns(&result.instructions);
all_patterns.extend(function_patterns);
disassembled.push(result);
}
}
}
}
(
Some(bytecode_result),
Some(disassembled),
Some(all_patterns),
)
} else {
(None, None, None)
};
let source_metrics =
self.analyze_source_code(&request.source_path, request.language.clone())?;
let dwarf_entries = if let Some(ref dwarf_path) = request.dwarf_path {
let dwarf_data =
fs::read(dwarf_path).map_err(crate::services::deep_wasm::DeepWasmError::Io)?;
self.dwarf_parser
.parse_dwarf_sections(&dwarf_data, None, None)?
} else {
vec![]
};
let source_map_entries = if let Some(ref source_map_path) = request.source_map_path {
self.source_map_handler.parse_source_map(source_map_path)?
} else {
vec![]
};
let correlations = if !dwarf_entries.is_empty() || !source_map_entries.is_empty() {
self.correlation_engine
.correlate(&dwarf_entries, &source_map_entries)?
} else {
vec![]
};
let quality_results = self.quality_gates.evaluate(&wasm_analysis)?;
Ok(DeepWasmReport {
project_name: request
.source_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown")
.to_string(),
timestamp: chrono::Utc::now().to_rfc3339(),
pmat_version: env!("CARGO_PKG_VERSION").to_string(),
pipeline_overview: PipelineOverview {
source_language: request.language,
source_version: String::new(),
target: "wasm32-unknown-unknown".to_string(),
optimization_level: String::new(),
debug_symbols: Some(if dwarf_entries.is_empty() {
"none".to_string()
} else {
format!("{} DWARF entries", dwarf_entries.len())
}),
},
source_metrics,
wasm_module_analysis: wasm_analysis,
correlations,
type_flows: vec![],
hotspots: vec![],
quality_gate_results: quality_results,
bytecode_analysis,
disassembled_functions,
suspicious_patterns,
})
}
fn analyze_source_code(
&self,
source_path: &Path,
language: SourceLanguage,
) -> DeepWasmResult<SourceMetrics> {
use crate::services::deep_wasm::DeepWasmError;
let source_code = fs::read_to_string(source_path).map_err(DeepWasmError::Io)?;
let lines_of_code = source_code.lines().count();
match language {
SourceLanguage::Rust => {
let syntax_tree = syn::parse_file(&source_code).map_err(|e| {
DeepWasmError::Analysis(format!("Failed to parse Rust source: {}", e))
})?;
let wasm_analysis = rust_wasm_analyzer::analyze_wasm_constructs(&syntax_tree);
let mut total_functions = 0;
let mut max_complexity = 0;
for item in &syntax_tree.items {
match item {
syn::Item::Fn(_) => total_functions += 1,
syn::Item::Impl(impl_block) => {
for impl_item in &impl_block.items {
if let syn::ImplItem::Fn(_) = impl_item {
total_functions += 1;
}
}
}
_ => {}
}
}
for boundary_fn in &wasm_analysis.boundary_functions {
let complexity_estimate = if boundary_fn.has_unsafe { 5 } else { 3 };
max_complexity = max_complexity.max(complexity_estimate);
}
Ok(SourceMetrics {
lines_of_code,
function_count: total_functions,
max_complexity,
wasm_boundary_functions: wasm_analysis.boundary_functions.len(),
})
}
SourceLanguage::Ruchy => {
let mut total_functions = 0;
let mut max_complexity = 0;
for line in source_code.lines() {
let trimmed = line.trim();
if (trimmed.starts_with("fun ") || trimmed.starts_with("async fun "))
&& trimmed.contains('(')
{
total_functions += 1;
if trimmed.contains("unsafe") || trimmed.contains("async") {
max_complexity = max_complexity.max(5);
} else {
max_complexity = max_complexity.max(3);
}
}
}
Ok(SourceMetrics {
lines_of_code,
function_count: total_functions,
max_complexity,
wasm_boundary_functions: 0, })
}
}
}
}
impl Default for DeepWasmService {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use crate::services::deep_wasm::{AnalysisFocus, SourceLanguage};
use std::path::PathBuf;
#[test]
fn test_service_creation() {
let _service = DeepWasmService::new();
}
#[test]
fn test_service_with_custom_gates() {
let gates = WasmQualityGates::new();
let _service = DeepWasmService::new().with_quality_gates(gates);
}
#[tokio::test]
#[ignore] async fn test_analyze_minimal_request() {
let service = DeepWasmService::new();
let request = DeepWasmAnalysisRequest {
source_path: PathBuf::from("tests/fixtures/test.rs"),
wasm_path: None,
dwarf_path: None,
source_map_path: None,
language: SourceLanguage::Rust,
analysis_focus: AnalysisFocus::Full,
};
let result = service.analyze(request).await;
assert!(result.is_ok());
let report = result.unwrap();
assert!(!report.project_name.is_empty());
}
#[tokio::test]
#[ignore] async fn test_analyze_ruchy_file() {
let service = DeepWasmService::new();
let request = DeepWasmAnalysisRequest {
source_path: PathBuf::from("tests/fixtures/deep_wasm_ruchy_test.ruchy"),
wasm_path: None,
dwarf_path: None,
source_map_path: None,
language: SourceLanguage::Ruchy,
analysis_focus: AnalysisFocus::Source,
};
let result = service.analyze(request).await;
assert!(result.is_ok());
let report = result.unwrap();
assert_eq!(
report.pipeline_overview.source_language,
SourceLanguage::Ruchy
);
assert!(report.source_metrics.lines_of_code > 0);
assert_eq!(report.source_metrics.function_count, 3); assert!(report.source_metrics.max_complexity > 0);
assert_eq!(report.source_metrics.wasm_boundary_functions, 0);
}
#[tokio::test]
#[serial_test::serial]
#[ignore] async fn test_disassemble_wasm_module() {
let service = DeepWasmService::new().with_deep_analysis(true);
let request = DeepWasmAnalysisRequest {
source_path: PathBuf::from("tests/fixtures/test.rs"),
wasm_path: Some(PathBuf::from("tests/fixtures/test.wasm")),
dwarf_path: None,
source_map_path: None,
language: SourceLanguage::Rust,
analysis_focus: AnalysisFocus::Full,
};
let result = service.analyze(request).await;
assert!(result.is_ok());
let report = result.unwrap();
assert!(report.bytecode_analysis.is_some());
if let Some(bytecode) = &report.bytecode_analysis {
let exported_count = bytecode.functions.iter().filter(|f| f.is_exported).count();
if exported_count > 0 {
assert!(report.disassembled_functions.is_some());
if let Some(disassembled) = &report.disassembled_functions {
assert!(!disassembled.is_empty());
assert!(!disassembled[0].instructions.is_empty());
assert!(!disassembled[0].basic_blocks.is_empty());
}
}
if let Some(patterns) = &report.suspicious_patterns {
for pattern in patterns {
assert!(!pattern.name.is_empty());
assert!(!pattern.description.is_empty());
if pattern.suspicious {
assert!(pattern.suspicion_reason.is_some());
}
}
}
}
}
}