use crate::discovery::Address;
#[derive(
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
pub enum TransferKind {
IndirectBranch,
IndirectCall,
}
impl TransferKind {
pub fn has_resolver(self) -> bool {
match self {
TransferKind::IndirectBranch => true,
TransferKind::IndirectCall => false,
}
}
}
#[derive(
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct ObligationKey {
pub site: Address,
pub kind: TransferKind,
}
impl ObligationKey {
pub fn branch(site: Address) -> Self {
Self {
site,
kind: TransferKind::IndirectBranch,
}
}
pub fn call(site: Address) -> Self {
Self {
site,
kind: TransferKind::IndirectCall,
}
}
}
impl std::fmt::Display for ObligationKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let kind = match self.kind {
TransferKind::IndirectBranch => "branch",
TransferKind::IndirectCall => "call",
};
write!(f, "{kind}@{:#x}", self.site)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kind_participates_in_identity() {
assert_ne!(ObligationKey::branch(0x1000), ObligationKey::call(0x1000));
}
#[test]
fn same_site_and_kind_is_the_same_obligation() {
assert_eq!(ObligationKey::branch(0x1000), ObligationKey::branch(0x1000));
}
#[test]
fn only_indirect_branches_have_a_resolver_today() {
assert!(TransferKind::IndirectBranch.has_resolver());
assert!(!TransferKind::IndirectCall.has_resolver());
}
#[test]
fn ordering_groups_by_site_then_kind() {
let mut keys = vec![
ObligationKey::call(0x2000),
ObligationKey::branch(0x2000),
ObligationKey::branch(0x1000),
];
keys.sort();
assert_eq!(
keys,
vec![
ObligationKey::branch(0x1000),
ObligationKey::branch(0x2000),
ObligationKey::call(0x2000),
]
);
}
#[test]
fn display_is_stable_and_readable() {
assert_eq!(
ObligationKey::branch(0x401a60).to_string(),
"branch@0x401a60"
);
assert_eq!(ObligationKey::call(0x401a60).to_string(), "call@0x401a60");
}
}