use crate::metadata::token::Token;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CallType {
Call,
CallVirt,
TailCall,
NewObj,
Calli,
Ldftn,
LdVirtFtn,
}
impl CallType {
#[must_use]
pub const fn is_virtual(&self) -> bool {
matches!(self, Self::CallVirt | Self::LdVirtFtn)
}
#[must_use]
pub const fn is_indirect(&self) -> bool {
matches!(self, Self::Calli)
}
#[must_use]
pub const fn is_constructor(&self) -> bool {
matches!(self, Self::NewObj)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CallTarget {
Resolved(Token),
Virtual {
declared: Token,
possible_targets: Vec<Token>,
},
External {
assembly: String,
method: String,
},
Delegate {
delegate_type: Option<Token>,
},
Indirect,
Unresolved {
token: Token,
reason: String,
},
}
impl CallTarget {
#[must_use]
pub const fn is_resolved(&self) -> bool {
matches!(
self,
Self::Resolved(_) | Self::Virtual { .. } | Self::External { .. }
)
}
#[must_use]
pub fn primary_token(&self) -> Option<Token> {
match self {
Self::Resolved(token) | Self::Unresolved { token, .. } => Some(*token),
Self::Virtual { declared, .. } => Some(*declared),
_ => None,
}
}
#[must_use]
pub fn all_targets(&self) -> Vec<Token> {
match self {
Self::Resolved(token) | Self::Unresolved { token, .. } => vec![*token],
Self::Virtual {
possible_targets, ..
} => possible_targets.clone(),
_ => Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallSite {
pub offset: u32,
pub call_type: CallType,
pub target: CallTarget,
}
impl CallSite {
#[must_use]
pub const fn new(offset: u32, call_type: CallType, target: CallTarget) -> Self {
Self {
offset,
call_type,
target,
}
}
#[must_use]
pub fn is_polymorphic(&self) -> bool {
matches!(
&self.target,
CallTarget::Virtual {
possible_targets, ..
} if possible_targets.len() > 1
)
}
#[must_use]
pub const fn is_resolved(&self) -> bool {
self.target.is_resolved()
}
}
#[cfg(test)]
mod tests {
use crate::analysis::callgraph::{CallSite, CallTarget, CallType};
use crate::metadata::token::Token;
#[test]
fn test_call_type_properties() {
assert!(!CallType::Call.is_virtual());
assert!(CallType::CallVirt.is_virtual());
assert!(CallType::LdVirtFtn.is_virtual());
assert!(!CallType::Calli.is_virtual());
assert!(!CallType::Call.is_indirect());
assert!(CallType::Calli.is_indirect());
assert!(!CallType::Call.is_constructor());
assert!(CallType::NewObj.is_constructor());
}
#[test]
fn test_call_target_resolved() {
let token = Token::new(0x0600_0001);
let resolved = CallTarget::Resolved(token);
assert!(resolved.is_resolved());
assert_eq!(resolved.primary_token(), Some(token));
assert_eq!(resolved.all_targets(), vec![token]);
}
#[test]
fn test_call_target_virtual() {
let declared = Token::new(0x0600_0001);
let target1 = Token::new(0x0600_0002);
let target2 = Token::new(0x0600_0003);
let virtual_target = CallTarget::Virtual {
declared,
possible_targets: vec![target1, target2],
};
assert!(virtual_target.is_resolved());
assert_eq!(virtual_target.primary_token(), Some(declared));
assert_eq!(virtual_target.all_targets(), vec![target1, target2]);
}
#[test]
fn test_call_target_external() {
let external = CallTarget::External {
assembly: "mscorlib".to_string(),
method: "System.Console::WriteLine".to_string(),
};
assert!(external.is_resolved());
assert_eq!(external.primary_token(), None);
assert!(external.all_targets().is_empty());
}
#[test]
fn test_call_target_unresolved() {
let token = Token::new(0x0A00_0001);
let unresolved = CallTarget::Unresolved {
token,
reason: "External assembly not loaded".to_string(),
};
assert!(!unresolved.is_resolved());
assert_eq!(unresolved.primary_token(), Some(token));
}
#[test]
fn test_call_site_polymorphic() {
let declared = Token::new(0x0600_0001);
let single = CallSite::new(
0,
CallType::CallVirt,
CallTarget::Virtual {
declared,
possible_targets: vec![declared],
},
);
assert!(!single.is_polymorphic());
let multiple = CallSite::new(
0,
CallType::CallVirt,
CallTarget::Virtual {
declared,
possible_targets: vec![declared, Token::new(0x0600_0002)],
},
);
assert!(multiple.is_polymorphic());
let direct = CallSite::new(0, CallType::Call, CallTarget::Resolved(declared));
assert!(!direct.is_polymorphic());
}
}