use std::cell::OnceCell;
use std::fmt;
use std::hash::{Hash, Hasher};
use idakit_sys as sys;
use serde::{Deserialize, Serialize};
use super::diff::TypeKey;
use super::{
SinkAdapter, TypeBuilder, TypeId, TypeMember, TypeShape, TypeSink, TypeTable, TypeValue, tid,
};
use crate::Database;
use crate::address::Address;
use crate::decompiler::ctree::ExtractError;
use crate::error::{Error, Result};
impl Database {
#[doc(alias("get_named_type"))]
pub fn type_named(&self, name: &str) -> Result<Type> {
crate::claim::ensure_kernel_thread();
match walk_type(|sink| sys::walk_type_named(name, sink)) {
Ok(Some(image)) => Ok(image),
Ok(None) => Err(Error::TypeNotFound {
name: name.to_owned(),
}),
Err(source) => Err(Error::Extract { address: 0, source }),
}
}
#[doc(alias("get_tinfo"))]
pub fn type_at(&self, address: Address) -> Result<Option<Type>> {
crate::claim::ensure_kernel_thread();
walk_type(|sink| sys::walk_func_type(address.get(), sink)).map_err(|source| {
Error::Extract {
address: address.get(),
source,
}
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[doc(alias("tinfo_t"))]
pub struct Type {
types: TypeTable,
root: TypeId,
#[serde(skip)]
key: OnceCell<TypeKey>,
}
impl Type {
#[must_use]
pub fn key(&self) -> TypeKey {
*self.key.get_or_init(|| self.canonical().key())
}
#[inline]
#[must_use]
pub const fn root(&self) -> TypeId {
self.root
}
#[inline]
#[must_use]
pub const fn types(&self) -> &TypeTable {
&self.types
}
#[inline]
#[must_use]
pub fn get(&self, id: TypeId) -> &TypeValue {
self.types.get(id)
}
#[inline]
#[must_use]
pub fn shape(&self) -> &TypeShape {
&self.types.get(self.root).shape
}
#[inline]
#[must_use]
pub fn size(&self) -> Option<u64> {
self.types.get(self.root).size
}
#[inline]
#[must_use]
pub fn members(&self) -> Option<&[TypeMember]> {
match self.shape() {
TypeShape::Struct { members, .. } | TypeShape::Union { members, .. } => Some(members),
_ => None,
}
}
}
impl PartialEq for Type {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.key() == other.key()
}
}
impl Eq for Type {}
impl Hash for Type {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.key().hash(state);
}
}
impl fmt::Display for Type {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.canonical())
}
}
struct ResolvedTypeBuilder {
types: TypeBuilder,
}
impl TypeSink for ResolvedTypeBuilder {
fn type_builder(&mut self) -> &mut TypeBuilder {
&mut self.types
}
}
pub(crate) fn walk_type(
run: impl FnOnce(&mut dyn sys::TypeWalkSink) -> Option<u32>,
) -> core::result::Result<Option<Type>, ExtractError> {
let mut b = ResolvedTypeBuilder {
types: TypeBuilder::new(),
};
let root = run(&mut SinkAdapter(&mut b));
let Some(root) = root else {
return Ok(None);
};
if let Some(bytes) = b.types.too_wide() {
return Err(ExtractError::ScalarTooWide { bytes });
}
let unfilled = b.types.unfilled();
if unfilled != 0 {
return Err(ExtractError::UnfilledType { count: unfilled });
}
Ok(Some(Type {
root: tid(root),
types: b.types.into_table(),
key: OnceCell::new(),
}))
}
#[cfg(test)]
mod tests {
use assert2::assert;
use super::*;
use crate::types::diff::{AggregateKind, TypeIdentity};
const fn assert_send<T: Send>() {}
const _: () = assert_send::<Type>();
fn u32_type(types: &mut TypeTable) -> TypeId {
types.intern(TypeValue {
shape: TypeShape::Int {
bytes: 4,
signed: false,
},
size: Some(4),
})
}
#[test]
fn image_exposes_root_shape_and_members() {
let mut types = TypeTable::new();
let field = u32_type(&mut types);
let root = types.intern(TypeValue {
shape: TypeShape::Struct {
name: Some("pt".into()),
members: vec![TypeMember {
name: "x".into(),
bit_offset: 0,
ty: field,
bitfield_width: None,
repr: None,
}],
},
size: Some(4),
});
let img = Type {
types,
root,
key: OnceCell::new(),
};
assert!(img.root() == root);
assert!(img.size() == Some(4));
assert!(let TypeShape::Struct { .. } = img.shape());
let members = img.members().expect("a struct has members");
assert!(members.len() == 1);
assert!(
img.get(members[0].ty).shape
== TypeShape::Int {
bytes: 4,
signed: false,
}
);
}
#[test]
fn equality_hashing_and_display_track_structure() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
fn scalar_image(signed: bool) -> Type {
let mut types = TypeTable::new();
let root = types.intern(TypeValue {
shape: TypeShape::Int { bytes: 4, signed },
size: Some(4),
});
Type {
types,
root,
key: OnceCell::new(),
}
}
fn hash_of(t: &Type) -> u64 {
let mut h = DefaultHasher::new();
t.hash(&mut h);
h.finish()
}
let a = scalar_image(false);
let b = scalar_image(false);
let c = scalar_image(true);
assert!(a == b, "structurally identical types should be equal");
assert!(a != c, "unsigned and signed should differ");
assert!(hash_of(&a) == hash_of(&b), "equal types must hash equally");
assert!(
hash_of(&a) != hash_of(&c),
"different types should hash apart"
);
assert!(!format!("{a}").is_empty(), "Display should render the type");
}
#[test]
fn identity_is_the_root_tag_and_anonymous_roots_have_none() {
let mut types = TypeTable::new();
let field = u32_type(&mut types);
let root = types.intern(TypeValue {
shape: TypeShape::Struct {
name: Some("pt".into()),
members: vec![TypeMember {
name: "x".into(),
bit_offset: 0,
ty: field,
bitfield_width: None,
repr: None,
}],
},
size: Some(4),
});
let tagged = Type {
types,
root,
key: OnceCell::new(),
};
assert!(
tagged.identity()
== Some(TypeIdentity::Tagged {
tag: "pt".into(),
kind: AggregateKind::Struct,
})
);
let mut types = TypeTable::new();
let root = u32_type(&mut types);
let anonymous = Type {
types,
root,
key: OnceCell::new(),
};
assert!(anonymous.identity() == None);
}
#[test]
fn scalar_root_has_no_members() {
let mut types = TypeTable::new();
let root = u32_type(&mut types);
let img = Type {
types,
root,
key: OnceCell::new(),
};
assert!(img.members().is_none());
}
#[test]
fn type_clone_has_equal_key() {
let mut types = TypeTable::new();
let root = u32_type(&mut types);
let img = Type {
types,
root,
key: OnceCell::new(),
};
let cloned = img.clone();
assert!(cloned.key() == img.key());
}
#[test]
fn type_serde_round_trip_recomputes_key() {
let mut types = TypeTable::new();
let root = u32_type(&mut types);
let img = Type {
types,
root,
key: OnceCell::new(),
};
let original_key = img.key();
let json = serde_json::to_string(&img).unwrap();
let round_tripped: Type = serde_json::from_str(&json).unwrap();
assert!(round_tripped.root() == img.root());
assert!(round_tripped.key() == original_key);
}
}