#![allow(missing_docs)]
use std::{
collections::{HashMap, HashSet},
hash::{Hash, Hasher},
sync::Arc,
};
use crate::{
analysis::{ConstValue, ControlFlowGraph, SsaConverter, SsaFunction, SsaOp, TypeContext},
assembly::Operand,
metadata::{
method::{Method, MethodModifiers},
signatures::TypeSignature,
token::Token,
typesystem::CilTypeReference,
},
CilObject,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VerificationLevel {
Normal,
Relaxed,
}
#[derive(Debug, Default)]
pub struct SemanticVerificationResult {
pub methods_checked: usize,
pub methods_preserved: usize,
pub average_similarity: f64,
}
#[derive(Debug, Clone, Copy)]
pub struct OrderedFloat(pub f64);
impl PartialEq for OrderedFloat {
fn eq(&self, other: &Self) -> bool {
self.0.to_bits() == other.0.to_bits()
}
}
impl Eq for OrderedFloat {}
impl Hash for OrderedFloat {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
#[derive(Debug, Clone, Default)]
pub struct MethodSemantics {
pub strings: HashSet<String>,
pub integer_constants: HashSet<i64>,
pub float_constants: HashSet<OrderedFloat>,
pub has_null: bool,
pub has_true: bool,
pub has_false: bool,
pub calls: HashSet<String>,
pub virtual_calls: HashSet<String>,
pub constructor_calls: HashSet<String>,
pub external_calls: HashSet<String>,
pub field_reads: HashSet<String>,
pub field_writes: HashSet<String>,
pub static_field_reads: HashSet<String>,
pub static_field_writes: HashSet<String>,
pub external_field_reads: HashSet<String>,
pub allocated_types: HashSet<String>,
pub cast_types: HashSet<String>,
pub block_count: usize,
pub has_loops: bool,
pub has_switches: bool,
pub has_exceptions: bool,
pub arith_ops: HashMap<&'static str, usize>,
}
impl MethodSemantics {
pub fn extract(ssa: &SsaFunction, assembly: &CilObject) -> Self {
let mut semantics = MethodSemantics {
block_count: ssa.blocks().len(),
..Default::default()
};
let mut has_back_edge = false;
for (block_idx, block) in ssa.blocks().iter().enumerate() {
for instr in block.instructions() {
match instr.op() {
SsaOp::Const { value, .. } => {
Self::extract_constant(&mut semantics, value, assembly);
}
SsaOp::Call { method, .. } => {
let token = method.token();
if let Some(name) = resolve_method_name(assembly, token) {
semantics.calls.insert(name.clone());
if is_external_method_token(token) {
semantics.external_calls.insert(name);
}
}
}
SsaOp::CallVirt { method, .. } => {
let token = method.token();
if let Some(name) = resolve_method_name(assembly, token) {
semantics.virtual_calls.insert(name.clone());
semantics.calls.insert(name.clone());
if is_external_method_token(token) {
semantics.external_calls.insert(name);
}
}
}
SsaOp::NewObj { ctor, .. } => {
let token = ctor.token();
if let Some(name) = resolve_method_name(assembly, token) {
semantics.constructor_calls.insert(name.clone());
semantics.calls.insert(name.clone());
if is_external_method_token(token) {
semantics.external_calls.insert(name);
}
}
if let Some(type_name) = resolve_type_from_ctor(assembly, ctor.token()) {
semantics.allocated_types.insert(type_name);
}
}
SsaOp::LoadField { field, .. } => {
let token = field.token();
if let Some(name) = resolve_field_name(assembly, token) {
semantics.field_reads.insert(name.clone());
if is_external_field_token(token) {
semantics.external_field_reads.insert(name);
}
}
}
SsaOp::StoreField { field, .. } => {
if let Some(name) = resolve_field_name(assembly, field.token()) {
semantics.field_writes.insert(name);
}
}
SsaOp::LoadStaticField { field, .. } => {
let token = field.token();
if let Some(name) = resolve_field_name(assembly, token) {
semantics.static_field_reads.insert(name.clone());
if is_external_field_token(token) {
semantics.external_field_reads.insert(name);
}
}
}
SsaOp::StoreStaticField { field, .. } => {
if let Some(name) = resolve_field_name(assembly, field.token()) {
semantics.static_field_writes.insert(name);
}
}
SsaOp::CastClass { target_type, .. } | SsaOp::IsInst { target_type, .. } => {
if let Some(name) = resolve_type_name(assembly, target_type.token()) {
semantics.cast_types.insert(name);
}
}
SsaOp::Box { value_type, .. }
| SsaOp::Unbox { value_type, .. }
| SsaOp::UnboxAny { value_type, .. } => {
if let Some(name) = resolve_type_name(assembly, value_type.token()) {
semantics.cast_types.insert(name);
}
}
SsaOp::Switch { .. } => {
semantics.has_switches = true;
}
SsaOp::Jump { target } => {
if *target <= block_idx {
has_back_edge = true;
}
}
SsaOp::Branch {
true_target,
false_target,
..
} => {
if *true_target <= block_idx || *false_target <= block_idx {
has_back_edge = true;
}
}
SsaOp::BranchCmp {
true_target,
false_target,
..
} => {
if *true_target <= block_idx || *false_target <= block_idx {
has_back_edge = true;
}
}
SsaOp::Throw { .. } | SsaOp::Rethrow => {
semantics.has_exceptions = true;
}
SsaOp::Add { .. } | SsaOp::AddOvf { .. } => {
*semantics.arith_ops.entry("add").or_insert(0) += 1;
}
SsaOp::Sub { .. } | SsaOp::SubOvf { .. } => {
*semantics.arith_ops.entry("sub").or_insert(0) += 1;
}
SsaOp::Mul { .. } | SsaOp::MulOvf { .. } => {
*semantics.arith_ops.entry("mul").or_insert(0) += 1;
}
SsaOp::Div { .. } => {
*semantics.arith_ops.entry("div").or_insert(0) += 1;
}
SsaOp::Rem { .. } => {
*semantics.arith_ops.entry("rem").or_insert(0) += 1;
}
SsaOp::And { .. } => {
*semantics.arith_ops.entry("and").or_insert(0) += 1;
}
SsaOp::Or { .. } => {
*semantics.arith_ops.entry("or").or_insert(0) += 1;
}
SsaOp::Xor { .. } => {
*semantics.arith_ops.entry("xor").or_insert(0) += 1;
}
SsaOp::Shl { .. } => {
*semantics.arith_ops.entry("shl").or_insert(0) += 1;
}
SsaOp::Shr { .. } => {
*semantics.arith_ops.entry("shr").or_insert(0) += 1;
}
SsaOp::Neg { .. } => {
*semantics.arith_ops.entry("neg").or_insert(0) += 1;
}
SsaOp::Not { .. } => {
*semantics.arith_ops.entry("not").or_insert(0) += 1;
}
_ => {}
}
}
}
semantics.has_loops = has_back_edge;
semantics
}
pub fn extract_constant(
semantics: &mut MethodSemantics,
value: &ConstValue,
assembly: &CilObject,
) {
match value {
ConstValue::String(token) => {
if let Some(content) = resolve_user_string(assembly, *token) {
semantics.strings.insert(content);
}
}
ConstValue::DecryptedString(content) => {
semantics.strings.insert(content.clone());
}
ConstValue::I8(v) => {
semantics.integer_constants.insert(i64::from(*v));
}
ConstValue::I16(v) => {
semantics.integer_constants.insert(i64::from(*v));
}
ConstValue::I32(v) => {
semantics.integer_constants.insert(i64::from(*v));
}
ConstValue::I64(v) => {
semantics.integer_constants.insert(*v);
}
ConstValue::U8(v) => {
semantics.integer_constants.insert(i64::from(*v));
}
ConstValue::U16(v) => {
semantics.integer_constants.insert(i64::from(*v));
}
ConstValue::U32(v) => {
semantics.integer_constants.insert(i64::from(*v));
}
ConstValue::U64(v) => {
if *v <= i64::MAX as u64 {
semantics.integer_constants.insert(*v as i64);
}
}
ConstValue::NativeInt(v) => {
semantics.integer_constants.insert(*v);
}
ConstValue::NativeUInt(v) => {
if *v <= i64::MAX as u64 {
semantics.integer_constants.insert(*v as i64);
}
}
ConstValue::F32(v) => {
semantics
.float_constants
.insert(OrderedFloat(f64::from(*v)));
}
ConstValue::F64(v) => {
semantics.float_constants.insert(OrderedFloat(*v));
}
ConstValue::Null => {
semantics.has_null = true;
}
ConstValue::True => {
semantics.has_true = true;
}
ConstValue::False => {
semantics.has_false = true;
}
_ => {}
}
}
pub fn similarity(&self, other: &MethodSemantics) -> f64 {
let string_sim = jaccard_similarity(&self.strings, &other.strings);
let call_sim = jaccard_similarity(&self.external_calls, &other.external_calls);
let field_read_sim = jaccard_similarity(&self.field_reads, &other.field_reads);
let const_sim = jaccard_similarity(&self.integer_constants, &other.integer_constants);
string_sim * 0.35 + call_sim * 0.35 + field_read_sim * 0.15 + const_sim * 0.15
}
pub fn preserves_semantics_of(&self, original: &MethodSemantics) -> bool {
let strings_preserved = original.strings.is_subset(&self.strings);
let significant_consts: HashSet<_> = original
.integer_constants
.iter()
.filter(|&&c| c != 0 && c != 1 && c != -1)
.copied()
.collect();
let deob_consts: HashSet<_> = self.integer_constants.iter().copied().collect();
let consts_preserved =
significant_consts.is_empty() || significant_consts.is_subset(&deob_consts);
strings_preserved && consts_preserved
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TypeKind {
Void,
Bool,
Char,
I8,
U8,
I16,
U16,
I32,
U32,
I64,
U64,
F32,
F64,
String,
Object,
Array,
IntPtr,
UIntPtr,
ValueType,
Class,
Generic,
ByRef,
Pointer,
Other,
}
impl TypeKind {
pub fn from_type_signature(sig: &TypeSignature) -> Self {
match sig {
TypeSignature::Void => TypeKind::Void,
TypeSignature::Boolean => TypeKind::Bool,
TypeSignature::Char => TypeKind::Char,
TypeSignature::I1 => TypeKind::I8,
TypeSignature::U1 => TypeKind::U8,
TypeSignature::I2 => TypeKind::I16,
TypeSignature::U2 => TypeKind::U16,
TypeSignature::I4 => TypeKind::I32,
TypeSignature::U4 => TypeKind::U32,
TypeSignature::I8 => TypeKind::I64,
TypeSignature::U8 => TypeKind::U64,
TypeSignature::R4 => TypeKind::F32,
TypeSignature::R8 => TypeKind::F64,
TypeSignature::String => TypeKind::String,
TypeSignature::Object => TypeKind::Object,
TypeSignature::I => TypeKind::IntPtr,
TypeSignature::U => TypeKind::UIntPtr,
TypeSignature::SzArray(_) | TypeSignature::Array(_) => TypeKind::Array,
TypeSignature::ValueType(_) => TypeKind::ValueType,
TypeSignature::Class(_) => TypeKind::Class,
TypeSignature::GenericInst { .. }
| TypeSignature::GenericParamType(_)
| TypeSignature::GenericParamMethod(_) => TypeKind::Generic,
TypeSignature::ByRef(_) => TypeKind::ByRef,
TypeSignature::Ptr(_) => TypeKind::Pointer,
_ => TypeKind::Other,
}
}
}
#[derive(Debug, Clone)]
pub struct MethodFingerprint {
pub param_count: usize,
pub return_type_kind: TypeKind,
pub param_type_kinds: Vec<TypeKind>,
pub is_instance: bool,
pub block_count: usize,
pub instruction_count: usize,
pub has_loops: bool,
pub has_switches: bool,
}
impl MethodFingerprint {
pub fn build(method: &Method, ssa: &SsaFunction, semantics: &MethodSemantics) -> Self {
let is_instance = !method.flags_modifiers.contains(MethodModifiers::STATIC);
let param_count = method.signature.params.len();
let return_type_kind = TypeKind::from_type_signature(&method.signature.return_type.base);
let param_type_kinds: Vec<_> = method
.signature
.params
.iter()
.map(|p| TypeKind::from_type_signature(&p.base))
.collect();
let instruction_count: usize = ssa.blocks().iter().map(|b| b.instructions().len()).sum();
MethodFingerprint {
param_count,
return_type_kind,
param_type_kinds,
is_instance,
block_count: semantics.block_count,
instruction_count,
has_loops: semantics.has_loops,
has_switches: semantics.has_switches,
}
}
pub fn similarity(&self, other: &MethodFingerprint) -> f64 {
let mut score = 0.0;
let mut weight = 0.0;
if self.return_type_kind == other.return_type_kind {
score += 0.2;
}
weight += 0.2;
if self.param_count == other.param_count {
score += 0.15;
let matching_params = self
.param_type_kinds
.iter()
.zip(&other.param_type_kinds)
.filter(|(a, b)| a == b)
.count();
if self.param_count > 0 {
score += 0.15 * (matching_params as f64 / self.param_count as f64);
} else {
score += 0.15;
}
}
weight += 0.3;
if self.is_instance == other.is_instance {
score += 0.1;
}
weight += 0.1;
let block_ratio = if self.block_count.max(other.block_count) > 0 {
self.block_count.min(other.block_count) as f64
/ self.block_count.max(other.block_count) as f64
} else {
1.0
};
score += 0.1 * block_ratio;
weight += 0.1;
let instr_ratio = if self.instruction_count.max(other.instruction_count) > 0 {
self.instruction_count.min(other.instruction_count) as f64
/ self.instruction_count.max(other.instruction_count) as f64
} else {
1.0
};
score += 0.1 * instr_ratio;
weight += 0.1;
if self.has_loops == other.has_loops {
score += 0.05;
}
weight += 0.05;
if self.has_switches == other.has_switches {
score += 0.05;
}
weight += 0.05;
if weight > 0.0 {
score / weight
} else {
0.0
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SignatureKey {
pub param_count: usize,
pub return_type: TypeKind,
pub param_types: Vec<TypeKind>,
pub is_instance: bool,
}
pub fn check_has_switch_dispatcher(assembly: &CilObject) -> (bool, usize) {
let mut total_switches = 0;
for method_entry in assembly.methods().iter() {
let method = method_entry.value();
if method.name.starts_with('.') || method.name.contains('<') {
continue;
}
if let Some(cfg) = method.cfg() {
for node_id in cfg.node_ids() {
if let Some(block) = cfg.block(node_id) {
for instr in &block.instructions {
if instr.mnemonic == "switch" {
total_switches += 1;
}
}
}
}
}
}
(total_switches > 0, total_switches)
}
pub fn verify_semantic_preservation(
original_asm: &CilObject,
deobfuscated_asm: Arc<CilObject>,
method_names: &[&str],
level: VerificationLevel,
) -> SemanticVerificationResult {
let mut methods_checked = 0;
let mut methods_preserved = 0;
let mut total_similarity = 0.0;
let mut details = Vec::new();
let mut name_matched = 0;
for method_name in method_names {
if build_ssa_for_method(&deobfuscated_asm, method_name).is_some() {
name_matched += 1;
}
}
let use_name_matching = name_matched >= method_names.len() / 2;
if use_name_matching {
for method_name in method_names {
let Some((original_ssa, _)) = build_ssa_for_method(original_asm, method_name) else {
continue;
};
let original_semantics = MethodSemantics::extract(&original_ssa, original_asm);
let Some((deobfuscated_ssa, _)) = build_ssa_for_method(&deobfuscated_asm, method_name)
else {
methods_checked += 1;
details.push((method_name.to_string(), 0.0, false, "not found".to_string()));
continue;
};
let deobfuscated_semantics =
MethodSemantics::extract(&deobfuscated_ssa, &deobfuscated_asm);
let similarity = deobfuscated_semantics.similarity(&original_semantics);
let preserves = deobfuscated_semantics.preserves_semantics_of(&original_semantics);
methods_checked += 1;
total_similarity += similarity;
if preserves {
methods_preserved += 1;
details.push((method_name.to_string(), similarity, true, String::new()));
} else {
let missing_strings: Vec<_> = original_semantics
.strings
.difference(&deobfuscated_semantics.strings)
.take(3)
.cloned()
.collect();
let significant_consts: HashSet<_> = original_semantics
.integer_constants
.iter()
.filter(|&&c| c != 0 && c != 1 && c != -1)
.copied()
.collect();
let missing_consts: Vec<_> = significant_consts
.difference(&deobfuscated_semantics.integer_constants)
.take(3)
.copied()
.collect();
let reason = format!(
"missing strings: {:?}, missing constants: {:?}",
missing_strings, missing_consts
);
details.push((method_name.to_string(), similarity, false, reason));
}
}
} else {
let mut deob_candidates: Vec<(
Arc<Method>,
SsaFunction,
MethodSemantics,
MethodFingerprint,
)> = Vec::new();
for method_entry in deobfuscated_asm.methods().iter() {
let method = method_entry.value();
if method.name.starts_with('.') || method.name.contains('<') {
continue;
}
if let Some(ssa) = build_ssa_for_method_entry(&deobfuscated_asm, method) {
let semantics = MethodSemantics::extract(&ssa, &deobfuscated_asm);
let fingerprint = MethodFingerprint::build(method, &ssa, &semantics);
deob_candidates.push((method.clone(), ssa, semantics, fingerprint));
}
}
for method_name in method_names {
let Some((ref_ssa, ref_method)) =
build_ssa_for_method_with_method(original_asm, method_name)
else {
continue;
};
let ref_semantics = MethodSemantics::extract(&ref_ssa, original_asm);
let ref_fingerprint = MethodFingerprint::build(&ref_method, &ref_ssa, &ref_semantics);
let is_instance = !ref_method.flags_modifiers.contains(MethodModifiers::STATIC);
let ref_sig = SignatureKey {
param_count: ref_method.signature.params.len(),
return_type: TypeKind::from_type_signature(&ref_method.signature.return_type.base),
param_types: ref_method
.signature
.params
.iter()
.map(|p| TypeKind::from_type_signature(&p.base))
.collect(),
is_instance,
};
let mut best_match: Option<(f64, &MethodSemantics)> = None;
for (cand_method, _cand_ssa, cand_semantics, cand_fingerprint) in &deob_candidates {
let cand_is_instance = !cand_method
.flags_modifiers
.contains(MethodModifiers::STATIC);
let cand_sig = SignatureKey {
param_count: cand_method.signature.params.len(),
return_type: TypeKind::from_type_signature(
&cand_method.signature.return_type.base,
),
param_types: cand_method
.signature
.params
.iter()
.map(|p| TypeKind::from_type_signature(&p.base))
.collect(),
is_instance: cand_is_instance,
};
if cand_sig != ref_sig {
continue; }
let fp_sim = cand_fingerprint.similarity(&ref_fingerprint);
let sem_sim = cand_semantics.similarity(&ref_semantics);
let combined = fp_sim * 0.3 + sem_sim * 0.7;
if best_match.as_ref().is_none_or(|m| combined > m.0) {
best_match = Some((combined, cand_semantics));
}
}
if let Some((score, deob_semantics)) = best_match {
if score > 0.3 {
let preserves = deob_semantics.preserves_semantics_of(&ref_semantics);
methods_checked += 1;
total_similarity += score;
if preserves {
methods_preserved += 1;
details.push((method_name.to_string(), score, true, String::new()));
} else {
let missing_strings: Vec<_> = ref_semantics
.strings
.difference(&deob_semantics.strings)
.take(3)
.cloned()
.collect();
let significant_consts: HashSet<_> = ref_semantics
.integer_constants
.iter()
.filter(|&&c| c != 0 && c != 1 && c != -1)
.copied()
.collect();
let missing_consts: Vec<_> = significant_consts
.difference(&deob_semantics.integer_constants)
.take(3)
.copied()
.collect();
let reason = format!(
"missing strings: {:?}, missing constants: {:?}",
missing_strings, missing_consts
);
details.push((method_name.to_string(), score, false, reason));
}
}
}
}
}
for (name, sim, preserved, reason) in &details {
if !preserved {
eprintln!(
" [SEM {}] {} - similarity={:.0}% {}",
if level == VerificationLevel::Relaxed {
"WARN"
} else {
"FAIL"
},
name,
sim * 100.0,
reason
);
}
}
let average_similarity = if methods_checked > 0 {
total_similarity / methods_checked as f64
} else {
0.0
};
SemanticVerificationResult {
methods_checked,
methods_preserved,
average_similarity,
}
}
pub fn jaccard_similarity<T: Eq + Hash>(a: &HashSet<T>, b: &HashSet<T>) -> f64 {
if a.is_empty() && b.is_empty() {
return 1.0;
}
let intersection = a.intersection(b).count();
let union = a.union(b).count();
if union == 0 {
1.0
} else {
intersection as f64 / union as f64
}
}
pub fn resolve_user_string(assembly: &CilObject, token: u32) -> Option<String> {
let userstrings = assembly.userstrings()?;
let content = userstrings.get(token as usize).ok()?;
Some(content.to_string_lossy().to_string())
}
pub fn resolve_field_name(assembly: &CilObject, token: Token) -> Option<String> {
let table_id = token.table();
match table_id {
0x04 => {
for entry in assembly.types().iter() {
let cil_type = entry.value();
for (_idx, field) in cil_type.fields.iter() {
if field.token == token {
let namespace = if cil_type.namespace.is_empty() {
String::new()
} else {
format!("{}.", cil_type.namespace)
};
return Some(format!("{}{}::{}", namespace, cil_type.name, field.name));
}
}
}
Some(format!("Field<{}>", token.row()))
}
0x0A => {
if let Some(member_ref) = assembly.member_ref(&token) {
let type_name = get_declaring_type_name(&member_ref.declaredby);
Some(format!("{}::{}", type_name, member_ref.name))
} else {
None
}
}
_ => None,
}
}
pub fn resolve_type_name(assembly: &CilObject, token: Token) -> Option<String> {
let table_id = token.table();
match table_id {
0x01 | 0x02 | 0x1B => {
if let Some(cil_type) = assembly.types().get(&token) {
if cil_type.namespace.is_empty() {
Some(cil_type.name.clone())
} else {
Some(format!("{}.{}", cil_type.namespace, cil_type.name))
}
} else {
Some(format!("Type<{:08X}>", token.value()))
}
}
_ => None,
}
}
pub fn resolve_type_from_ctor(assembly: &CilObject, ctor_token: Token) -> Option<String> {
let table_id = ctor_token.table();
match table_id {
0x06 => {
for entry in assembly.types().iter() {
let cil_type = entry.value();
for (_idx, method_ref) in cil_type.methods.iter() {
if let Some(m) = method_ref.upgrade() {
if m.token == ctor_token {
if cil_type.namespace.is_empty() {
return Some(cil_type.name.clone());
} else {
return Some(format!("{}.{}", cil_type.namespace, cil_type.name));
}
}
}
}
}
None
}
0x0A => {
assembly
.member_ref(&ctor_token)
.map(|member_ref| get_declaring_type_name(&member_ref.declaredby))
}
_ => None,
}
}
pub fn get_declaring_type_name(type_ref: &CilTypeReference) -> String {
match type_ref {
CilTypeReference::TypeRef(tr) => {
let ns = tr.namespace().unwrap_or_default();
let name = tr.name().unwrap_or_else(|| "Unknown".to_string());
if ns.is_empty() {
name
} else {
format!("{}.{}", ns, name)
}
}
CilTypeReference::TypeDef(td) => {
let ns = td.namespace().unwrap_or_default();
let name = td.name().unwrap_or_else(|| "Unknown".to_string());
if ns.is_empty() {
name
} else {
format!("{}.{}", ns, name)
}
}
CilTypeReference::TypeSpec(ts) => ts.name().unwrap_or_else(|| "TypeSpec".to_string()),
CilTypeReference::Module(m) => format!("[Module:{}]", m.name),
CilTypeReference::ModuleRef(mr) => format!("[ModuleRef:{}]", mr.name),
CilTypeReference::MethodDef(md) => {
if let Some(m) = md.upgrade() {
format!("[MethodDef:{}]", m.name)
} else {
"[MethodDef:Unknown]".to_string()
}
}
CilTypeReference::None => "Unknown".to_string(),
_ => "Unknown".to_string(),
}
}
pub fn resolve_method_name(assembly: &CilObject, token: Token) -> Option<String> {
let table_id = token.table();
match table_id {
0x06 => {
let method = assembly.method(&token)?;
for entry in assembly.types().iter() {
let cil_type = entry.value();
for (_idx, method_ref) in cil_type.methods.iter() {
if let Some(m) = method_ref.upgrade() {
if m.token == token {
let namespace = if cil_type.namespace.is_empty() {
String::new()
} else {
format!("{}.", cil_type.namespace)
};
return Some(format!(
"{}{}::{}",
namespace, cil_type.name, method.name
));
}
}
}
}
Some(method.name.clone())
}
0x0A => {
if let Some(member_ref) = assembly.member_ref(&token) {
let type_name = get_declaring_type_name(&member_ref.declaredby);
return Some(format!("{}::{}", type_name, member_ref.name));
}
Some(format!("MemberRef<{}>", token.row()))
}
0x2B => Some(format!("MethodSpec<{}>", token.row())),
_ => None,
}
}
pub fn build_ssa_for_method(
assembly: &CilObject,
method_name: &str,
) -> Option<(SsaFunction, Token)> {
let (ssa, method) = build_ssa_for_method_with_method(assembly, method_name)?;
Some((ssa, method.token))
}
pub fn build_ssa_for_method_with_method(
assembly: &CilObject,
method_name: &str,
) -> Option<(SsaFunction, Arc<Method>)> {
let method = assembly
.methods()
.iter()
.find(|e| e.value().name == method_name)
.map(|e| e.value().clone())?;
let cfg = method.cfg()?;
let is_static = method.flags_modifiers.contains(MethodModifiers::STATIC);
let num_args = method.signature.params.len() + if is_static { 0 } else { 1 };
let declared_locals = method.local_vars.count();
let max_local_used = find_max_local_index(&cfg);
let num_locals = declared_locals.max(max_local_used + 1);
let type_context = TypeContext::new(&method, assembly);
let ssa = SsaConverter::build(&cfg, num_args, num_locals, &type_context).ok()?;
Some((ssa, method))
}
pub fn build_ssa_for_method_entry(
assembly: &CilObject,
method: &Arc<Method>,
) -> Option<SsaFunction> {
let cfg = method.cfg()?;
let is_static = method.flags_modifiers.contains(MethodModifiers::STATIC);
let num_args = method.signature.params.len() + if is_static { 0 } else { 1 };
let declared_locals = method.local_vars.count();
let max_local_used = find_max_local_index(&cfg);
let num_locals = declared_locals.max(max_local_used + 1);
let type_context = TypeContext::new(method, assembly);
SsaConverter::build(&cfg, num_args, num_locals, &type_context).ok()
}
pub fn find_max_local_index(cfg: &ControlFlowGraph) -> usize {
let mut max_index: usize = 0;
for node_id in cfg.node_ids() {
if let Some(block) = cfg.block(node_id) {
for instr in &block.instructions {
if let Operand::Local(idx) = &instr.operand {
max_index = max_index.max(*idx as usize);
}
}
}
}
max_index
}
pub fn is_external_method_token(token: Token) -> bool {
let table_id = token.table();
table_id == 0x0A || table_id == 0x2B
}
pub fn is_external_field_token(token: Token) -> bool {
let table_id = token.table();
table_id == 0x0A
}