use crate::{
analysis::{ConstValue, MethodPurity, ReturnInfo},
metadata::{signatures::TypeSignature, token::Token},
};
#[derive(Debug, Clone)]
pub struct MethodSummary {
pub token: Token,
pub return_info: ReturnInfo,
pub purity: MethodPurity,
pub parameters: Vec<ParameterSummary>,
pub is_string_decryptor: bool,
pub is_dispatcher: bool,
pub inline_candidate: bool,
pub instruction_count: usize,
pub call_site_count: usize,
pub is_entry_point: bool,
pub return_type: Option<TypeSignature>,
pub parameter_types: Vec<TypeSignature>,
pub has_xor_operations: bool,
pub has_array_access: bool,
pub has_encoding_calls: bool,
pub distinct_arg_values: usize,
}
impl MethodSummary {
#[must_use]
pub fn new(token: Token) -> Self {
Self {
token,
return_info: ReturnInfo::Unknown,
purity: MethodPurity::Unknown,
parameters: Vec::new(),
is_string_decryptor: false,
is_dispatcher: false,
inline_candidate: false,
instruction_count: 0,
call_site_count: 0,
is_entry_point: false,
return_type: None,
parameter_types: Vec::new(),
has_xor_operations: false,
has_array_access: false,
has_encoding_calls: false,
distinct_arg_values: 0,
}
}
#[must_use]
pub fn is_dead(&self) -> bool {
self.call_site_count == 0 && !self.is_entry_point
}
#[must_use]
pub fn returns_constant(&self) -> Option<&ConstValue> {
match &self.return_info {
ReturnInfo::Constant(v) => Some(v),
_ => None,
}
}
#[must_use]
pub fn is_pure(&self) -> bool {
matches!(self.purity, MethodPurity::Pure)
}
#[must_use]
pub fn is_read_only(&self) -> bool {
matches!(self.purity, MethodPurity::Pure | MethodPurity::ReadOnly)
}
#[must_use]
pub fn parameter_constant(&self, index: usize) -> Option<&ConstValue> {
self.parameters
.get(index)
.and_then(|p| p.constant_value.as_ref())
}
#[must_use]
pub fn returns_string(&self) -> bool {
matches!(self.return_type, Some(TypeSignature::String))
}
#[must_use]
pub fn has_integer_parameter(&self) -> bool {
self.parameter_types.iter().any(|t| {
matches!(
t,
TypeSignature::I1
| TypeSignature::U1
| TypeSignature::I2
| TypeSignature::U2
| TypeSignature::I4
| TypeSignature::U4
| TypeSignature::I8
| TypeSignature::U8
| TypeSignature::I
| TypeSignature::U
)
})
}
#[must_use]
pub fn has_byte_array_parameter(&self) -> bool {
self.parameter_types.iter().any(|t| {
if let TypeSignature::SzArray(arr) = t {
matches!(*arr.base, TypeSignature::U1 | TypeSignature::I1)
} else {
false
}
})
}
#[must_use]
pub fn string_decryptor_score(&self) -> u32 {
let mut score = 0u32;
if self.returns_string() {
score += 30;
}
if self.has_integer_parameter() {
score += 15;
}
if self.has_byte_array_parameter() {
score += 15;
}
if self.has_xor_operations {
score += 15;
}
if self.has_array_access {
score += 10;
}
if self.has_encoding_calls {
score += 20;
}
if self.distinct_arg_values >= 5 {
score += 15;
} else if self.distinct_arg_values >= 2 {
score += 5;
}
if self.instruction_count > 0 && self.instruction_count <= 100 {
score += 5;
}
score.min(100)
}
}
#[derive(Debug, Clone)]
pub struct ParameterSummary {
pub index: usize,
pub name: Option<String>,
pub is_used: bool,
pub pure_usage_only: bool,
pub constant_value: Option<ConstValue>,
pub use_count: usize,
}
impl ParameterSummary {
#[must_use]
pub fn new(index: usize) -> Self {
Self {
index,
name: None,
is_used: false,
pure_usage_only: true,
constant_value: None,
use_count: 0,
}
}
#[must_use]
pub fn with_name(index: usize, name: impl Into<String>) -> Self {
Self {
name: Some(name.into()),
..Self::new(index)
}
}
#[must_use]
pub fn is_dead(&self) -> bool {
!self.is_used
}
#[must_use]
pub fn has_constant(&self) -> bool {
self.constant_value.is_some()
}
}
#[derive(Debug, Clone)]
pub struct CallSiteInfo {
pub caller: Token,
pub offset: usize,
pub arguments: Vec<Option<ConstValue>>,
pub is_live: bool,
}
impl CallSiteInfo {
#[must_use]
pub fn new(caller: Token, offset: usize) -> Self {
Self {
caller,
offset,
arguments: Vec::new(),
is_live: true,
}
}
#[must_use]
pub fn argument_constant(&self, index: usize) -> Option<&ConstValue> {
self.arguments.get(index).and_then(|v| v.as_ref())
}
#[must_use]
pub fn all_arguments_constant(&self) -> bool {
self.arguments.iter().all(Option::is_some)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_method_summary_default() {
let summary = MethodSummary::new(Token::new(0x06000001));
assert!(summary.is_dead());
assert!(!summary.is_pure());
assert!(summary.returns_constant().is_none());
}
#[test]
fn test_return_info() {
assert!(ReturnInfo::Void.is_known());
assert!(ReturnInfo::Constant(ConstValue::I32(42)).is_known());
assert!(!ReturnInfo::Dynamic.is_known());
assert!(ReturnInfo::PassThrough(0).is_potentially_foldable());
assert!(ReturnInfo::PureComputation.is_potentially_foldable());
assert!(!ReturnInfo::Dynamic.is_potentially_foldable());
}
#[test]
fn test_method_purity() {
assert!(MethodPurity::Pure.can_eliminate_if_unused());
assert!(MethodPurity::ReadOnly.can_eliminate_if_unused());
assert!(!MethodPurity::Impure.can_eliminate_if_unused());
assert!(MethodPurity::Pure.can_inline());
assert!(MethodPurity::LocalMutation.can_inline());
assert!(!MethodPurity::Impure.can_inline());
}
#[test]
fn test_parameter_summary() {
let mut param = ParameterSummary::new(0);
assert!(param.is_dead());
assert!(!param.has_constant());
param.is_used = true;
param.constant_value = Some(ConstValue::I32(42));
assert!(!param.is_dead());
assert!(param.has_constant());
}
#[test]
fn test_call_site_info() {
let mut call_site = CallSiteInfo::new(Token::new(0x06000001), 0x20);
call_site.arguments = vec![Some(ConstValue::I32(1)), None, Some(ConstValue::I32(3))];
assert_eq!(call_site.argument_constant(0), Some(&ConstValue::I32(1)));
assert_eq!(call_site.argument_constant(1), None);
assert!(!call_site.all_arguments_constant());
}
}