impl BytecodeAnalyzer {
fn external_kind_str(kind: wasmparser::ExternalKind) -> &'static str {
match kind {
wasmparser::ExternalKind::Func => "function",
wasmparser::ExternalKind::Memory => "memory",
wasmparser::ExternalKind::Table => "table",
wasmparser::ExternalKind::Global => "global",
wasmparser::ExternalKind::Tag => "tag",
}
}
fn type_ref_kind_str(ty: &TypeRef) -> &'static str {
match ty {
TypeRef::Func(_) => "function",
TypeRef::Memory(_) => "memory",
TypeRef::Table(_) => "table",
TypeRef::Global(_) => "global",
TypeRef::Tag(_) => "tag",
}
}
fn parse_import(import: wasmparser::Import, type_section: &[FuncType]) -> ImportAnalysis {
let kind = Self::type_ref_kind_str(&import.ty);
let signature = if let TypeRef::Func(type_idx) = import.ty {
type_section
.get(type_idx as usize)
.map(|func_type| FunctionSignature {
params: func_type.params().iter().map(valtype_to_string).collect(),
results: func_type.results().iter().map(valtype_to_string).collect(),
type_index: type_idx,
})
} else {
None
};
ImportAnalysis {
module: import.module.to_string(),
field: import.name.to_string(),
kind: kind.to_string(),
signature,
}
}
fn parse_name_section(data: &[u8], name_map: &mut HashMap<u32, String>) {
let name_reader =
wasmparser::NameSectionReader::new(wasmparser::BinaryReader::new(data, 0));
for section in name_reader {
if let Ok(wasmparser::Name::Function(func_names)) = section {
for naming in func_names.into_iter().flatten() {
name_map.insert(naming.index, naming.name.to_string());
}
}
}
}
fn analyze_single_function(
&self,
func_index: u32,
type_idx: u32,
body: &FunctionBody,
type_section: &[FuncType],
name_map: &HashMap<u32, String>,
export_section: &[(String, u32, String)],
) -> DeepWasmResult<Option<FunctionAnalysis>> {
let func_type = match type_section.get(type_idx as usize) {
Some(ft) => ft,
None => return Ok(None),
};
let signature = FunctionSignature {
params: func_type.params().iter().map(valtype_to_string).collect(),
results: func_type.results().iter().map(valtype_to_string).collect(),
type_index: type_idx,
};
let name = name_map.get(&func_index).cloned();
let (is_exported, export_name) = export_section
.iter()
.find(|(_, idx, kind)| *idx == func_index && kind == "function")
.map(|(name, _, _)| (true, Some(name.clone())))
.unwrap_or((false, None));
let (complexity, instruction_stats, stack_depth, control_flow_patterns) =
if self.deep_analysis {
self.analyze_function_body(body)?
} else {
self.analyze_function_body_shallow(body)?
};
Ok(Some(FunctionAnalysis {
function_index: func_index,
name,
signature,
complexity,
instruction_stats,
stack_depth,
control_flow_patterns,
is_exported,
export_name,
}))
}
fn parse_sections<'a>(parser: Parser, bytes: &'a [u8]) -> WasmSections<'a> {
let mut sections = WasmSections {
type_section: Vec::new(),
function_section: Vec::new(),
code_section: Vec::new(),
export_section: Vec::new(),
import_section: Vec::new(),
name_map: HashMap::new(),
validation_errors: Vec::new(),
};
for payload in parser.parse_all(bytes) {
match payload {
Ok(Payload::TypeSection(reader)) => {
for recgroup in reader.into_iter().flatten() {
for func_type in recgroup.into_types() {
sections.type_section.push(func_type.unwrap_func().clone());
}
}
}
Ok(Payload::FunctionSection(reader)) => {
sections
.function_section
.extend(reader.into_iter().flatten());
}
Ok(Payload::CodeSectionEntry(body)) => sections.code_section.push(body),
Ok(Payload::ExportSection(reader)) => {
for export in reader.into_iter().flatten() {
let kind = Self::external_kind_str(export.kind);
sections.export_section.push((
export.name.to_string(),
export.index,
kind.to_string(),
));
}
}
Ok(Payload::ImportSection(reader)) => {
for import in reader.into_iter().flatten() {
sections
.import_section
.push(Self::parse_import(import, §ions.type_section));
}
}
Ok(Payload::CustomSection(reader)) if reader.name() == "name" => {
Self::parse_name_section(reader.data(), &mut sections.name_map);
}
Err(e) => {
sections.validation_errors.push(ValidationError {
message: format!("Parse error: {}", e),
offset: None,
});
}
_ => {}
}
}
sections
}
fn build_result(
functions: Vec<FunctionAnalysis>,
sections: WasmSections<'_>,
) -> ModuleBytecodeAnalysis {
let total_instructions: u32 = functions.iter().map(|f| f.instruction_stats.total).sum();
let max_complexity = functions
.iter()
.map(|f| f.complexity.cyclomatic_complexity)
.max()
.unwrap_or(0);
let avg_complexity = if !functions.is_empty() {
functions
.iter()
.map(|f| f.complexity.cyclomatic_complexity as f64)
.sum::<f64>()
/ functions.len() as f64
} else {
0.0
};
let exports: Vec<ExportAnalysis> = sections
.export_section
.into_iter()
.map(|(name, index, kind)| ExportAnalysis { name, kind, index })
.collect();
ModuleBytecodeAnalysis {
module_stats: ModuleStats {
total_functions: sections.function_section.len() as u32,
total_instructions,
avg_complexity,
max_complexity,
import_count: sections.import_section.len() as u32,
export_count: exports.len() as u32,
},
functions,
imports: sections.import_section,
exports,
validation_errors: sections.validation_errors,
}
}
pub fn analyze(&self, bytes: &[u8]) -> DeepWasmResult<ModuleBytecodeAnalysis> {
let parser = Parser::new(0);
let mut sections = Self::parse_sections(parser, bytes);
let import_count = sections
.import_section
.iter()
.filter(|imp| imp.kind == "function")
.count() as u32;
let mut functions = Vec::new();
for (func_idx, (type_idx, body)) in sections
.function_section
.iter()
.zip(sections.code_section.iter())
.enumerate()
{
let func_index = import_count + func_idx as u32;
match self.analyze_single_function(
func_index,
*type_idx,
body,
§ions.type_section,
§ions.name_map,
§ions.export_section,
)? {
Some(fa) => functions.push(fa),
None => {
sections.validation_errors.push(ValidationError {
message: format!(
"Function {} references invalid type index {}",
func_index, type_idx
),
offset: None,
});
}
}
}
Ok(Self::build_result(functions, sections))
}
}