use std::collections::HashSet;
use dashmap::DashSet;
use crate::{
analysis::{ConstValue, FieldRef, MethodRef, SsaFunction, SsaOp, SsaVarId, VariableOrigin},
compiler::{CompilerContext, EventKind, ModificationScope, SsaPass},
deobfuscation::utils::is_method_named,
metadata::token::Token,
CilObject, Result,
};
#[derive(Debug)]
enum ReflectionSite {
ResolveMethodCalli {
block: usize,
idx: usize,
target_token: u32,
dest: Option<SsaVarId>,
args: Vec<SsaVarId>,
intermediates: Vec<(usize, usize)>,
},
ResolveMethodInvoke {
block: usize,
idx: usize,
target_token: u32,
dest: Option<SsaVarId>,
obj_var: SsaVarId,
array_var: SsaVarId,
intermediates: Vec<(usize, usize)>,
},
GetMethodInvoke {
block: usize,
idx: usize,
type_token: Token,
method_name: String,
dest: Option<SsaVarId>,
obj_var: SsaVarId,
array_var: SsaVarId,
intermediates: Vec<(usize, usize)>,
},
ActivatorCreate {
block: usize,
idx: usize,
type_token: Token,
dest: Option<SsaVarId>,
intermediates: Vec<(usize, usize)>,
},
FieldAccess {
block: usize,
idx: usize,
field_token: u32,
is_set: bool,
dest: Option<SsaVarId>,
obj_var: SsaVarId,
value_var: Option<SsaVarId>,
intermediates: Vec<(usize, usize)>,
},
}
pub struct ReflectionDevirtualizationPass {
target_methods: HashSet<Token>,
processed: DashSet<Token>,
}
impl ReflectionDevirtualizationPass {
pub fn with_methods(methods: HashSet<Token>) -> Self {
Self {
target_methods: methods,
processed: DashSet::new(),
}
}
}
impl SsaPass for ReflectionDevirtualizationPass {
fn name(&self) -> &'static str {
"reflection-devirtualization"
}
fn description(&self) -> &'static str {
"Resolves reflection-based call indirection to direct calls"
}
fn modification_scope(&self) -> ModificationScope {
ModificationScope::InstructionsOnly
}
fn should_run(&self, method_token: Token, _ctx: &CompilerContext) -> bool {
self.target_methods.contains(&method_token) && !self.processed.contains(&method_token)
}
fn run_on_method(
&self,
ssa: &mut SsaFunction,
method_token: Token,
ctx: &CompilerContext,
assembly: &CilObject,
) -> Result<bool> {
let sites = find_reflection_sites(ssa, assembly);
if sites.is_empty() {
self.processed.insert(method_token);
return Ok(false);
}
let mut count = 0usize;
for site in &sites {
let success = match site {
ReflectionSite::ResolveMethodCalli { .. } => {
rewrite_resolve_method_calli(ssa, site, ctx)
}
ReflectionSite::ResolveMethodInvoke { .. } => {
rewrite_resolve_method_invoke(ssa, site, ctx)
}
ReflectionSite::GetMethodInvoke { .. } => {
rewrite_get_method_invoke(ssa, site, ctx, assembly)
}
ReflectionSite::ActivatorCreate { .. } => {
rewrite_activator_create(ssa, site, ctx, assembly)
}
ReflectionSite::FieldAccess { .. } => rewrite_field_access(ssa, site, ctx),
};
if success {
count += 1;
}
}
if count > 0 {
ctx.events
.record(EventKind::InstructionRemoved)
.method(method_token)
.message(format!("Devirtualized {count} reflection call sites"));
}
self.processed.insert(method_token);
Ok(count > 0)
}
}
pub fn count_resolve_method_calli_sites(ssa: &SsaFunction, assembly: &CilObject) -> usize {
let tracer = ChainTracer { ssa, assembly };
let mut count = 0;
for (block_idx, block) in ssa.blocks().iter().enumerate() {
for (i, instr) in block.instructions().iter().enumerate() {
let SsaOp::CallIndirect {
dest, fptr, args, ..
} = instr.op()
else {
continue;
};
if tracer
.trace_resolve_method_calli(block_idx, i, *dest, *fptr, args)
.is_some()
{
count += 1;
}
}
}
count
}
struct ChainTracer<'a> {
ssa: &'a SsaFunction,
assembly: &'a CilObject,
}
impl<'a> ChainTracer<'a> {
fn is_method_from_type(&self, token: Token, type_name: &str) -> bool {
let table = token.table();
if table == 0x06 {
if let Some(method) = self.assembly.method(&token) {
if let Some(ty) = method.declaring_type_rc() {
return ty.name.contains(type_name);
}
}
} else if table == 0x0A {
if let Some(entry) = self.assembly.refs_members().get(&token) {
let member = entry.value();
if let Some(fullname) = member.declaredby.fullname() {
return fullname.contains(type_name);
}
}
}
false
}
fn def_site(&self, var: SsaVarId) -> Option<(usize, usize)> {
let variable = self.ssa.variable(var)?;
let ds = variable.def_site();
Some((ds.block, ds.instruction?))
}
fn is_null(&self, var: SsaVarId) -> bool {
matches!(
self.ssa.get_definition(var),
Some(SsaOp::Const {
value: ConstValue::Null,
..
})
)
}
fn extract_token_from_const(&self, var: SsaVarId, valid_tables: &[u8]) -> Option<u32> {
let SsaOp::Const { value, .. } = self.ssa.get_definition(var)? else {
return None;
};
let raw = value.as_i32()? as u32;
let table = (raw >> 24) as u8;
if valid_tables.contains(&table) {
Some(raw)
} else {
None
}
}
fn extract_method_token(&self, var: SsaVarId) -> Option<u32> {
self.extract_token_from_const(var, &[0x06, 0x0A, 0x2B])
}
fn extract_field_token(&self, var: SsaVarId) -> Option<u32> {
self.extract_token_from_const(var, &[0x04, 0x0A])
}
fn extract_string_const(&self, var: SsaVarId) -> Option<String> {
let SsaOp::Const { value, .. } = self.ssa.get_definition(var)? else {
return None;
};
match value {
ConstValue::DecryptedString(s) => Some(s.clone()),
_ => None,
}
}
fn trace_type_from_handle(&self, var: SsaVarId) -> Option<Token> {
let SsaOp::Call { args, .. } = self.ssa.get_definition(var)? else {
return None;
};
if args.is_empty() {
return None;
}
match self.ssa.get_definition(args[0])? {
SsaOp::LoadToken { token, .. } => Some(token.0),
_ => None,
}
}
fn trace_resolve_method_call(&self, var: SsaVarId) -> Option<(SsaVarId, SsaVarId)> {
let SsaOp::Call {
method,
args: rm_args,
..
} = self.ssa.get_definition(var)?
else {
return None;
};
if rm_args.len() >= 2 && is_method_named(self.assembly, method.token(), "ResolveMethod") {
Some((rm_args[0], rm_args[1]))
} else {
None
}
}
fn trace_resolve_field(&self, field_info_var: SsaVarId) -> Option<(u32, Vec<(usize, usize)>)> {
let mut intermediates = Vec::new();
let SsaOp::Call {
method,
args: rf_args,
..
} = self.ssa.get_definition(field_info_var)?
else {
return None;
};
if rf_args.len() < 2 || !is_method_named(self.assembly, method.token(), "ResolveField") {
return None;
}
intermediates.extend(self.def_site(field_info_var));
let field_token = self.extract_field_token(rf_args[1])?;
intermediates.extend(self.def_site(rf_args[1]));
Some((field_token, intermediates))
}
fn trace_module_chain(&self, module_var: SsaVarId, intermediates: &mut Vec<(usize, usize)>) {
let Some(SsaOp::CallVirt { args, .. }) = self.ssa.get_definition(module_var) else {
return;
};
if args.is_empty() {
return;
}
intermediates.extend(self.def_site(module_var));
let type_handle_var = args[0];
let Some(SsaOp::Call { args, .. }) = self.ssa.get_definition(type_handle_var) else {
return;
};
if args.is_empty() {
return;
}
intermediates.extend(self.def_site(type_handle_var));
let loadtoken_arg = args[0];
if matches!(
self.ssa.get_definition(loadtoken_arg),
Some(SsaOp::LoadToken { .. })
) {
intermediates.extend(self.def_site(loadtoken_arg));
}
}
fn resolve_fptr_source(&self, fptr: SsaVarId) -> Option<(SsaVarId, Token, SsaVarId)> {
if let Some(def) = self.ssa.get_definition(fptr) {
if let SsaOp::Call { method, args, .. } = def {
if !args.is_empty()
&& is_method_named(self.assembly, method.token(), "GetFunctionPointer")
{
return Some((fptr, method.token(), args[0]));
}
}
return None;
}
let (_block_idx, phi) = self.ssa.find_phi_defining(fptr)?;
for operand in phi.operands() {
let op_var = operand.value();
if let Some(SsaOp::Call { method, args, .. }) = self.ssa.get_definition(op_var) {
if !args.is_empty()
&& is_method_named(self.assembly, method.token(), "GetFunctionPointer")
{
return Some((op_var, method.token(), args[0]));
}
}
}
None
}
fn find_get_method_handle(
&self,
intermediates: &mut Vec<(usize, usize)>,
local_index: u16,
) -> Option<SsaVarId> {
for (block_idx, block) in self.ssa.blocks().iter().enumerate() {
for (instr_idx, instr) in block.instructions().iter().enumerate() {
let SsaOp::Copy { dest, src } = instr.op() else {
continue;
};
let is_target_local = self
.ssa
.variable(*dest)
.is_some_and(|v| v.origin() == VariableOrigin::Local(local_index));
if !is_target_local {
continue;
}
if let Some(SsaOp::CallVirt { method, args, .. }) = self.ssa.get_definition(*src) {
if !args.is_empty()
&& is_method_named(self.assembly, method.token(), "get_MethodHandle")
{
intermediates.extend(self.def_site(*src));
intermediates.push((block_idx, instr_idx));
return Some(args[0]);
}
}
}
}
None
}
fn trace_resolve_method_calli(
&self,
ci_block: usize,
ci_idx: usize,
dest: Option<SsaVarId>,
fptr: SsaVarId,
args: &[SsaVarId],
) -> Option<ReflectionSite> {
let mut intermediates: Vec<(usize, usize)> = Vec::new();
let (getfp_var, _getfp_method, getfp_arg) = self.resolve_fptr_source(fptr)?;
intermediates.extend(self.def_site(getfp_var));
let resolved_var = match self.ssa.get_definition(getfp_arg)? {
SsaOp::CallVirt { method, args, .. }
if !args.is_empty()
&& is_method_named(self.assembly, method.token(), "get_MethodHandle") =>
{
intermediates.extend(self.def_site(getfp_arg));
args[0]
}
SsaOp::LoadLocalAddr { local_index, .. } => {
let local_idx = *local_index;
intermediates.extend(self.def_site(getfp_arg));
self.find_get_method_handle(&mut intermediates, local_idx)?
}
_ => return None,
};
let (module_var, token_const_var) = self.trace_resolve_method_call(resolved_var)?;
intermediates.extend(self.def_site(resolved_var));
let target_token = self.extract_method_token(token_const_var)?;
intermediates.extend(self.def_site(token_const_var));
self.trace_module_chain(module_var, &mut intermediates);
Some(ReflectionSite::ResolveMethodCalli {
block: ci_block,
idx: ci_idx,
target_token,
dest,
args: args.to_vec(),
intermediates,
})
}
fn trace_resolve_method_invoke(
&self,
block: usize,
idx: usize,
args: &[SsaVarId],
dest: Option<SsaVarId>,
) -> Option<ReflectionSite> {
let (method_info_var, obj_var, array_var) = (args[0], args[1], args[2]);
let mut intermediates: Vec<(usize, usize)> = Vec::new();
let (module_var, token_const_var) = self.trace_resolve_method_call(method_info_var)?;
intermediates.extend(self.def_site(method_info_var));
let target_token = self.extract_method_token(token_const_var)?;
intermediates.extend(self.def_site(token_const_var));
self.trace_module_chain(module_var, &mut intermediates);
Some(ReflectionSite::ResolveMethodInvoke {
block,
idx,
target_token,
dest,
obj_var,
array_var,
intermediates,
})
}
fn trace_get_method_invoke(
&self,
block: usize,
idx: usize,
args: &[SsaVarId],
dest: Option<SsaVarId>,
) -> Option<ReflectionSite> {
let (method_info_var, obj_var, array_var) = (args[0], args[1], args[2]);
let mut intermediates: Vec<(usize, usize)> = Vec::new();
let def = self.ssa.get_definition(method_info_var)?;
let (type_var, name_var) = match def {
SsaOp::Call {
method, args: a, ..
}
| SsaOp::CallVirt {
method, args: a, ..
} if a.len() >= 2 && is_method_named(self.assembly, method.token(), "GetMethod") => {
intermediates.extend(self.def_site(method_info_var));
(a[0], a[1])
}
_ => return None,
};
let method_name = self.extract_string_const(name_var)?;
intermediates.extend(self.def_site(name_var));
let type_token = self.trace_type_from_handle(type_var)?;
intermediates.extend(self.def_site(type_var));
Some(ReflectionSite::GetMethodInvoke {
block,
idx,
type_token,
method_name,
dest,
obj_var,
array_var,
intermediates,
})
}
fn try_invoke_site(
&self,
block: usize,
idx: usize,
args: &[SsaVarId],
dest: Option<SsaVarId>,
) -> Option<ReflectionSite> {
self.trace_resolve_method_invoke(block, idx, args, dest)
.or_else(|| self.trace_get_method_invoke(block, idx, args, dest))
}
fn trace_activator_create(
&self,
block: usize,
idx: usize,
args: &[SsaVarId],
dest: Option<SsaVarId>,
) -> Option<ReflectionSite> {
let type_var = args[0];
let mut intermediates = Vec::new();
let type_token = self.trace_type_from_handle(type_var)?;
intermediates.extend(self.def_site(type_var));
Some(ReflectionSite::ActivatorCreate {
block,
idx,
type_token,
dest,
intermediates,
})
}
fn trace_field_get_value(
&self,
block: usize,
idx: usize,
args: &[SsaVarId],
dest: Option<SsaVarId>,
) -> Option<ReflectionSite> {
let (field_info_var, obj_var) = (args[0], args[1]);
let (field_token, intermediates) = self.trace_resolve_field(field_info_var)?;
Some(ReflectionSite::FieldAccess {
block,
idx,
field_token,
is_set: false,
dest,
obj_var,
value_var: None,
intermediates,
})
}
fn trace_field_set_value(
&self,
block: usize,
idx: usize,
args: &[SsaVarId],
) -> Option<ReflectionSite> {
let (field_info_var, obj_var, value_var) = (args[0], args[1], args[2]);
let (field_token, intermediates) = self.trace_resolve_field(field_info_var)?;
let actual_value = unwrap_box(self.ssa, value_var).unwrap_or(value_var);
Some(ReflectionSite::FieldAccess {
block,
idx,
field_token,
is_set: true,
dest: None,
obj_var,
value_var: Some(actual_value),
intermediates,
})
}
}
fn find_reflection_sites(ssa: &SsaFunction, assembly: &CilObject) -> Vec<ReflectionSite> {
let tracer = ChainTracer { ssa, assembly };
let mut sites = Vec::new();
for (block_idx, block) in ssa.blocks().iter().enumerate() {
for (i, instr) in block.instructions().iter().enumerate() {
match instr.op() {
SsaOp::CallIndirect {
dest, fptr, args, ..
} => {
if let Some(site) =
tracer.trace_resolve_method_calli(block_idx, i, *dest, *fptr, args)
{
sites.push(site);
}
}
SsaOp::Call {
method, args, dest, ..
}
| SsaOp::CallVirt {
method, args, dest, ..
} => {
if let Some(site) =
try_reflection_api_site(&tracer, block_idx, i, method.token(), args, *dest)
{
sites.push(site);
}
}
_ => {}
}
}
}
sites
}
fn try_reflection_api_site(
tracer: &ChainTracer,
block: usize,
idx: usize,
method_token: Token,
args: &[SsaVarId],
dest: Option<SsaVarId>,
) -> Option<ReflectionSite> {
let name = tracer.assembly.resolve_method_name(method_token)?;
if name == "Invoke" && args.len() == 3 {
return tracer.try_invoke_site(block, idx, args, dest);
}
if name.contains("CreateInstance")
&& (args.len() == 1 || args.len() == 2)
&& tracer.is_method_from_type(method_token, "Activator")
{
return tracer.trace_activator_create(block, idx, args, dest);
}
if name == "GetValue"
&& args.len() == 2
&& tracer.is_method_from_type(method_token, "FieldInfo")
{
return tracer.trace_field_get_value(block, idx, args, dest);
}
if name == "SetValue"
&& args.len() == 3
&& tracer.is_method_from_type(method_token, "FieldInfo")
{
return tracer.trace_field_set_value(block, idx, args);
}
None
}
fn unpack_object_array(ssa: &SsaFunction, array_var: SsaVarId) -> Option<Vec<SsaVarId>> {
let def = ssa.get_definition(array_var)?;
let SsaOp::NewArr { length, .. } = def else {
if matches!(
def,
SsaOp::Const {
value: ConstValue::Null,
..
}
) {
return Some(Vec::new());
}
return None;
};
let SsaOp::Const { value, .. } = ssa.get_definition(*length)? else {
return None;
};
let arr_len = value.as_i32()? as usize;
if arr_len == 0 {
return Some(Vec::new());
}
let mut elements: Vec<Option<SsaVarId>> = vec![None; arr_len];
for block in ssa.blocks() {
for instr in block.instructions() {
if let SsaOp::StoreElement {
array,
index,
value,
..
} = instr.op()
{
if *array != array_var {
continue;
}
if let Some(SsaOp::Const { value: idx_val, .. }) = ssa.get_definition(*index) {
if let Some(i) = idx_val.as_i32() {
let i = i as usize;
if i < arr_len {
let actual_val = unwrap_box(ssa, *value).unwrap_or(*value);
elements[i] = Some(actual_val);
}
}
}
}
}
}
elements.into_iter().collect()
}
fn unwrap_box(ssa: &SsaFunction, var: SsaVarId) -> Option<SsaVarId> {
if let Some(SsaOp::Box { value, .. }) = ssa.get_definition(var) {
Some(*value)
} else {
None
}
}
fn build_call_args(
tracer: &ChainTracer,
obj_var: SsaVarId,
unpacked_args: Vec<SsaVarId>,
) -> Vec<SsaVarId> {
if tracer.is_null(obj_var) {
unpacked_args
} else {
let mut a = vec![obj_var];
a.extend(unpacked_args);
a
}
}
fn rewrite_resolve_method_calli(
ssa: &mut SsaFunction,
site: &ReflectionSite,
ctx: &CompilerContext,
) -> bool {
let ReflectionSite::ResolveMethodCalli {
block,
idx,
target_token,
dest,
args,
intermediates,
} = site
else {
return false;
};
let target_method = MethodRef::new(Token::new(*target_token));
if let Some(blk) = ssa.block_mut(*block) {
if let Some(instr) = blk.instruction_mut(*idx) {
let stored_type = instr.result_type().cloned();
instr.set_op(SsaOp::Call {
dest: *dest,
method: target_method,
args: args.clone(),
});
instr.set_result_type(stored_type);
}
}
nop_intermediates(ssa, intermediates, ctx);
true
}
fn rewrite_resolve_method_invoke(
ssa: &mut SsaFunction,
site: &ReflectionSite,
ctx: &CompilerContext,
) -> bool {
let ReflectionSite::ResolveMethodInvoke {
block,
idx,
target_token,
dest,
obj_var,
array_var,
intermediates,
} = site
else {
return false;
};
let Some(unpacked_args) = unpack_object_array(ssa, *array_var) else {
return false;
};
let is_null_obj = matches!(
ssa.get_definition(*obj_var),
Some(SsaOp::Const {
value: ConstValue::Null,
..
})
);
let call_args = if is_null_obj {
unpacked_args
} else {
let mut a = vec![*obj_var];
a.extend(unpacked_args);
a
};
let target_method = MethodRef::new(Token::new(*target_token));
if let Some(blk) = ssa.block_mut(*block) {
if let Some(instr) = blk.instruction_mut(*idx) {
let stored_type = instr.result_type().cloned();
instr.set_op(SsaOp::Call {
dest: *dest,
method: target_method,
args: call_args,
});
instr.set_result_type(stored_type);
}
}
nop_intermediates(ssa, intermediates, ctx);
true
}
fn rewrite_get_method_invoke(
ssa: &mut SsaFunction,
site: &ReflectionSite,
ctx: &CompilerContext,
assembly: &CilObject,
) -> bool {
let ReflectionSite::GetMethodInvoke {
block,
idx,
type_token,
method_name,
dest,
obj_var,
array_var,
intermediates,
} = site
else {
return false;
};
let Some(resolved_method) = resolve_method_by_name(assembly, *type_token, method_name) else {
return false;
};
let Some(unpacked_args) = unpack_object_array(ssa, *array_var) else {
return false;
};
let is_null_obj = matches!(
ssa.get_definition(*obj_var),
Some(SsaOp::Const {
value: ConstValue::Null,
..
})
);
let call_args = if is_null_obj {
unpacked_args
} else {
let mut a = vec![*obj_var];
a.extend(unpacked_args);
a
};
let target_method = MethodRef::new(resolved_method);
if let Some(blk) = ssa.block_mut(*block) {
if let Some(instr) = blk.instruction_mut(*idx) {
let stored_type = instr.result_type().cloned();
instr.set_op(SsaOp::Call {
dest: *dest,
method: target_method,
args: call_args,
});
instr.set_result_type(stored_type);
}
}
nop_intermediates(ssa, intermediates, ctx);
true
}
fn rewrite_activator_create(
ssa: &mut SsaFunction,
site: &ReflectionSite,
ctx: &CompilerContext,
assembly: &CilObject,
) -> bool {
let ReflectionSite::ActivatorCreate {
block,
idx,
type_token,
dest,
intermediates,
} = site
else {
return false;
};
let Some(ctor_token) = find_parameterless_ctor(assembly, *type_token) else {
return false;
};
let Some(dest_var) = dest else {
return false;
};
if let Some(blk) = ssa.block_mut(*block) {
if let Some(instr) = blk.instruction_mut(*idx) {
instr.set_op(SsaOp::NewObj {
dest: *dest_var,
ctor: MethodRef::new(ctor_token),
args: Vec::new(),
});
}
}
nop_intermediates(ssa, intermediates, ctx);
true
}
fn rewrite_field_access(
ssa: &mut SsaFunction,
site: &ReflectionSite,
ctx: &CompilerContext,
) -> bool {
let ReflectionSite::FieldAccess {
block,
idx,
field_token,
is_set,
dest,
obj_var,
value_var,
intermediates,
} = site
else {
return false;
};
let field = FieldRef(Token::new(*field_token));
let is_null_obj = matches!(
ssa.get_definition(*obj_var),
Some(SsaOp::Const {
value: ConstValue::Null,
..
})
);
if *is_set {
let Some(val) = value_var else {
return false;
};
let new_op = if is_null_obj {
SsaOp::StoreStaticField { field, value: *val }
} else {
SsaOp::StoreField {
object: *obj_var,
field,
value: *val,
}
};
if let Some(blk) = ssa.block_mut(*block) {
if let Some(instr) = blk.instruction_mut(*idx) {
instr.set_op(new_op);
}
}
} else {
let Some(dest_var) = dest else {
return false;
};
let new_op = if is_null_obj {
SsaOp::LoadStaticField {
dest: *dest_var,
field,
}
} else {
SsaOp::LoadField {
dest: *dest_var,
object: *obj_var,
field,
}
};
if let Some(blk) = ssa.block_mut(*block) {
if let Some(instr) = blk.instruction_mut(*idx) {
instr.set_op(new_op);
}
}
}
nop_intermediates(ssa, intermediates, ctx);
true
}
fn nop_intermediates(
ssa: &mut SsaFunction,
intermediates: &[(usize, usize)],
ctx: &CompilerContext,
) {
for &(blk, instr_idx) in intermediates {
if let Some(block) = ssa.block_mut(blk) {
if let Some(instr) = block.instruction(instr_idx) {
match instr.op() {
SsaOp::Call { method, .. } | SsaOp::CallVirt { method, .. } => {
ctx.neutralized_tokens.insert(method.token());
}
_ => {}
}
}
if let Some(instr) = block.instruction_mut(instr_idx) {
instr.set_op(SsaOp::Nop);
}
}
}
}
fn resolve_method_by_name(assembly: &CilObject, type_token: Token, name: &str) -> Option<Token> {
let ty = assembly.types().get(&type_token)?;
let result = ty.query_methods().name(name).find_first().map(|m| m.token);
result
}
fn find_parameterless_ctor(assembly: &CilObject, type_token: Token) -> Option<Token> {
let ty = assembly.types().get(&type_token)?;
let ctor_token = ty.ctor()?;
let method = assembly.method(&ctor_token)?;
if method.params.is_empty() {
Some(ctor_token)
} else {
None
}
}
#[cfg(test)]
mod tests {
use crate::{
analysis::{ConstValue, SsaFunctionBuilder, SsaOp, SsaType},
deobfuscation::passes::reflection::{unpack_object_array, unwrap_box},
};
#[test]
fn test_unpack_null_array() {
let ssa = SsaFunctionBuilder::new(0, 0)
.build_with(|f| {
f.block(0, |b| {
let _null = b.const_null();
b.ret();
});
})
.unwrap();
let block = ssa.block(0).unwrap();
let mut null_var = None;
for instr in block.instructions() {
if let SsaOp::Const {
dest,
value: ConstValue::Null,
} = instr.op()
{
null_var = Some(*dest);
}
}
let result = unpack_object_array(&ssa, null_var.unwrap());
assert!(result.is_some(), "null array should unpack as empty");
assert!(result.unwrap().is_empty());
}
#[test]
fn test_unwrap_box_absent() {
let ssa = SsaFunctionBuilder::new(0, 0)
.build_with(|f| {
f.block(0, |b| {
let _val = b.const_i32(42);
b.ret();
});
})
.unwrap();
let block = ssa.block(0).unwrap();
let mut val_var = None;
for instr in block.instructions() {
if let SsaOp::Const { dest, .. } = instr.op() {
val_var = Some(*dest);
}
}
let result = unwrap_box(&ssa, val_var.unwrap());
assert!(result.is_none(), "non-Box should return None");
}
#[test]
fn test_no_reflection_sites_in_clean_function() {
let ssa = SsaFunctionBuilder::new(1, 0)
.build_with(|f| {
let arg0 = f.arg(0, SsaType::I32);
f.block(0, |b| {
let one = b.const_i32(1);
let _sum = b.add(arg0, one);
b.ret();
});
})
.unwrap();
assert_eq!(ssa.blocks().len(), 1);
}
}