use crate::{analysis::callgraph::CallSite, metadata::token::Token};
#[derive(Debug, Clone)]
pub struct CallGraphNode {
pub token: Token,
pub name: String,
pub full_name: String,
pub signature: String,
pub is_virtual: bool,
pub is_abstract: bool,
pub is_static: bool,
pub is_external: bool,
pub is_external_ref: bool,
pub is_constructor: bool,
pub call_sites: Vec<CallSite>,
pub overriders: Vec<Token>,
pub overrides: Option<Token>,
}
impl CallGraphNode {
#[must_use]
pub fn new(token: Token, name: String, full_name: String, signature: String) -> Self {
Self {
token,
name,
full_name,
signature,
is_virtual: false,
is_abstract: false,
is_static: false,
is_external: false,
is_external_ref: false,
is_constructor: false,
call_sites: Vec::new(),
overriders: Vec::new(),
overrides: None,
}
}
#[must_use]
pub fn call_count(&self) -> usize {
self.call_sites.len()
}
#[must_use]
pub fn is_leaf(&self) -> bool {
self.call_sites.is_empty()
}
#[must_use]
pub const fn is_overridable(&self) -> bool {
self.is_virtual && !self.is_static
}
#[must_use]
pub const fn has_body(&self) -> bool {
!self.is_abstract && !self.is_external
}
#[must_use]
pub fn callees(&self) -> Vec<Token> {
let mut callees = Vec::new();
for site in &self.call_sites {
for target in site.target.all_targets() {
if !callees.contains(&target) {
callees.push(target);
}
}
}
callees
}
}
#[cfg(test)]
mod tests {
use crate::analysis::callgraph::{CallGraphNode, CallSite, CallTarget, CallType};
use crate::metadata::token::Token;
#[test]
fn test_node_creation() {
let token = Token::new(0x0600_0001);
let node = CallGraphNode::new(
token,
"TestMethod".to_string(),
"TestClass::TestMethod".to_string(),
"void ()".to_string(),
);
assert_eq!(node.token, token);
assert_eq!(node.name, "TestMethod");
assert_eq!(node.full_name, "TestClass::TestMethod");
assert!(!node.is_virtual);
assert!(!node.is_abstract);
assert!(node.is_leaf());
assert_eq!(node.call_count(), 0);
}
#[test]
fn test_node_with_calls() {
let token = Token::new(0x0600_0001);
let mut node = CallGraphNode::new(
token,
"Caller".to_string(),
"TestClass::Caller".to_string(),
"void ()".to_string(),
);
let callee1 = Token::new(0x0600_0002);
let callee2 = Token::new(0x0600_0003);
node.call_sites.push(CallSite::new(
0x10,
CallType::Call,
CallTarget::Resolved(callee1),
));
node.call_sites.push(CallSite::new(
0x20,
CallType::Call,
CallTarget::Resolved(callee2),
));
assert!(!node.is_leaf());
assert_eq!(node.call_count(), 2);
assert_eq!(node.callees(), vec![callee1, callee2]);
}
#[test]
fn test_node_overridable() {
let token = Token::new(0x0600_0001);
let mut node = CallGraphNode::new(
token,
"Test".to_string(),
"TestClass::Test".to_string(),
"void ()".to_string(),
);
assert!(!node.is_overridable());
node.is_virtual = true;
assert!(node.is_overridable());
node.is_static = true;
assert!(!node.is_overridable());
}
#[test]
fn test_node_has_body() {
let token = Token::new(0x0600_0001);
let mut node = CallGraphNode::new(
token,
"Test".to_string(),
"TestClass::Test".to_string(),
"void ()".to_string(),
);
assert!(node.has_body());
node.is_abstract = true;
assert!(!node.has_body());
node.is_abstract = false;
node.is_external = true;
assert!(!node.has_body());
}
#[test]
fn test_callees_deduplication() {
let token = Token::new(0x0600_0001);
let mut node = CallGraphNode::new(
token,
"Test".to_string(),
"TestClass::Test".to_string(),
"void ()".to_string(),
);
let callee = Token::new(0x0600_0002);
node.call_sites.push(CallSite::new(
0x10,
CallType::Call,
CallTarget::Resolved(callee),
));
node.call_sites.push(CallSite::new(
0x20,
CallType::Call,
CallTarget::Resolved(callee),
));
assert_eq!(node.callees(), vec![callee]);
}
}