use std::fmt;
use crate::metadata::{
cilobject::CilObject,
method::{ExceptionHandlerFlags, Method},
signatures::{
parse_local_var_signature, CustomModifiers, SignatureArray, SignatureLocalVariable,
SignatureMethod, SignatureMethodSpec, SignatureParameter, SignaturePointer,
SignatureSzArray, TypeSignature,
},
tables::{MemberRefSignature, StandAloneSigRaw, StandAloneSignature},
token::Token,
typesystem::{wellknown, ArrayDimensions, CilFlavor},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TypeRef(pub Token);
impl TypeRef {
#[must_use]
pub const fn new(token: Token) -> Self {
Self(token)
}
#[must_use]
pub const fn token(&self) -> Token {
self.0
}
}
impl fmt::Display for TypeRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TypeRef({})", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MethodRef(pub Token);
impl MethodRef {
#[must_use]
pub const fn new(token: Token) -> Self {
Self(token)
}
#[must_use]
pub const fn token(&self) -> Token {
self.0
}
}
impl fmt::Display for MethodRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MethodRef({})", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FieldRef(pub Token);
impl FieldRef {
#[must_use]
pub const fn new(token: Token) -> Self {
Self(token)
}
#[must_use]
pub const fn token(&self) -> Token {
self.0
}
}
impl fmt::Display for FieldRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FieldRef({})", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SigRef(pub Token);
impl SigRef {
#[must_use]
pub const fn new(token: Token) -> Self {
Self(token)
}
#[must_use]
pub const fn token(&self) -> Token {
self.0
}
}
impl fmt::Display for SigRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SigRef({})", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SsaType {
Void,
Bool,
I8,
U8,
I16,
U16,
I32,
U32,
I64,
U64,
NativeInt,
NativeUInt,
F32,
F64,
Char,
Object,
String,
Class(TypeRef),
ValueType(TypeRef),
GenericInst(Box<SsaType>, Vec<SsaType>),
Array(Box<SsaType>, u32),
Pointer(Box<SsaType>),
ByRef(Box<SsaType>),
TypedReference,
GenericParam(u32),
MethodGenericParam(u32),
FnPtr(Box<FnPtrSig>),
Null,
Unknown,
Varying,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FnPtrSig {
pub ret: SsaType,
pub params: Vec<SsaType>,
pub call_conv: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TypeClass {
Int32,
Int64,
Float32,
Float64,
Reference,
NativeInt,
Other,
}
impl SsaType {
#[must_use]
pub const fn is_primitive(&self) -> bool {
matches!(
self,
Self::Bool
| Self::I8
| Self::U8
| Self::I16
| Self::U16
| Self::I32
| Self::U32
| Self::I64
| Self::U64
| Self::NativeInt
| Self::NativeUInt
| Self::F32
| Self::F64
| Self::Char
)
}
#[must_use]
pub const fn is_integer(&self) -> bool {
matches!(
self,
Self::I8
| Self::U8
| Self::I16
| Self::U16
| Self::I32
| Self::U32
| Self::I64
| Self::U64
| Self::NativeInt
| Self::NativeUInt
)
}
#[must_use]
pub const fn is_float(&self) -> bool {
matches!(self, Self::F32 | Self::F64)
}
#[must_use]
pub fn is_reference(&self) -> bool {
match self {
Self::Object | Self::String | Self::Class(_) | Self::Array(_, _) | Self::Null => true,
Self::GenericInst(base, _) => base.is_reference(),
_ => false,
}
}
#[must_use]
pub const fn is_value_type(&self) -> bool {
matches!(self, Self::ValueType(_)) || self.is_primitive()
}
#[must_use]
pub const fn is_array(&self) -> bool {
matches!(self, Self::Array(_, _))
}
#[must_use]
pub const fn is_pointer(&self) -> bool {
matches!(self, Self::Pointer(_) | Self::ByRef(_))
}
#[must_use]
pub const fn is_void(&self) -> bool {
matches!(self, Self::Void)
}
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown | Self::Varying)
}
#[must_use]
pub const fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
#[must_use]
pub const fn is_generic_param(&self) -> bool {
matches!(self, Self::GenericParam(_) | Self::MethodGenericParam(_))
}
#[must_use]
pub fn array_element_type(&self) -> Option<&SsaType> {
match self {
Self::Array(elem, _) => Some(elem),
_ => None,
}
}
#[must_use]
pub const fn array_rank(&self) -> Option<u32> {
match self {
Self::Array(_, rank) => Some(*rank),
_ => None,
}
}
#[must_use]
pub fn pointee_type(&self) -> Option<&SsaType> {
match self {
Self::Pointer(inner) | Self::ByRef(inner) => Some(inner),
_ => None,
}
}
#[must_use]
pub const fn size_bytes(&self) -> Option<u32> {
match self {
Self::Bool | Self::I8 | Self::U8 => Some(1),
Self::I16 | Self::U16 | Self::Char => Some(2),
Self::I32 | Self::U32 | Self::F32 => Some(4),
Self::I64 | Self::U64 | Self::F64 => Some(8),
_ => None,
}
}
#[must_use]
pub fn stack_type(&self) -> SsaType {
match self {
Self::Bool | Self::I8 | Self::U8 | Self::I16 | Self::U16 | Self::Char | Self::I32 => {
Self::I32
}
Self::U32 => Self::I32, Self::I64 | Self::U64 => Self::I64,
Self::NativeInt | Self::NativeUInt => Self::NativeInt,
Self::F32 | Self::F64 => Self::F64, _ => self.clone(),
}
}
#[must_use]
pub fn storage_class(&self) -> TypeClass {
match self {
Self::I32
| Self::U32
| Self::Bool
| Self::Char
| Self::I16
| Self::U16
| Self::I8
| Self::U8 => TypeClass::Int32,
Self::I64 | Self::U64 => TypeClass::Int64,
Self::F32 => TypeClass::Float32,
Self::F64 => TypeClass::Float64,
Self::NativeInt | Self::NativeUInt => TypeClass::NativeInt,
Self::Object
| Self::String
| Self::Class(_)
| Self::ValueType(_)
| Self::Array(_, _)
| Self::ByRef(_)
| Self::Pointer(_)
| Self::TypedReference => TypeClass::Reference,
Self::GenericInst(base, _) => base.storage_class(),
Self::Void
| Self::Unknown
| Self::Varying
| Self::Null
| Self::GenericParam(_)
| Self::MethodGenericParam(_)
| Self::FnPtr(_) => TypeClass::Other,
}
}
#[must_use]
pub fn is_compatible_for_storage(&self, other: &SsaType) -> bool {
if self == other {
return true;
}
if matches!(self, Self::Unknown) || matches!(other, Self::Unknown) {
return true;
}
if self.storage_class() != other.storage_class() {
return false;
}
if self.storage_class() == TypeClass::Reference {
return false; }
true
}
#[must_use]
pub fn merge(&self, other: &SsaType) -> SsaType {
if self == other {
return self.clone();
}
if matches!(self, Self::Unknown) {
return other.clone();
}
if matches!(other, Self::Unknown) {
return self.clone();
}
if matches!(self, Self::Null) && other.is_reference() {
return other.clone();
}
if matches!(other, Self::Null) && self.is_reference() {
return self.clone();
}
Self::Varying
}
#[must_use]
pub fn to_type_signature(&self) -> TypeSignature {
match self {
Self::Void => TypeSignature::Void,
Self::Bool => TypeSignature::Boolean,
Self::I8 => TypeSignature::I1,
Self::U8 => TypeSignature::U1,
Self::I16 => TypeSignature::I2,
Self::U16 => TypeSignature::U2,
Self::I32 => TypeSignature::I4,
Self::U32 => TypeSignature::U4,
Self::I64 => TypeSignature::I8,
Self::U64 => TypeSignature::U8,
Self::NativeInt => TypeSignature::I,
Self::NativeUInt => TypeSignature::U,
Self::F32 => TypeSignature::R4,
Self::F64 => TypeSignature::R8,
Self::Char => TypeSignature::Char,
Self::String => TypeSignature::String,
Self::Class(type_ref) => TypeSignature::Class(type_ref.token()),
Self::ValueType(type_ref) => TypeSignature::ValueType(type_ref.token()),
Self::GenericInst(base, type_args) => TypeSignature::GenericInst(
Box::new(base.to_type_signature()),
type_args.iter().map(SsaType::to_type_signature).collect(),
),
Self::Array(elem, 1) => TypeSignature::SzArray(SignatureSzArray {
modifiers: CustomModifiers::default(),
base: Box::new(elem.to_type_signature()),
}),
Self::Array(elem, rank) => TypeSignature::Array(SignatureArray {
base: Box::new(elem.to_type_signature()),
rank: *rank,
dimensions: (0..*rank)
.map(|_| ArrayDimensions {
size: None,
lower_bound: None,
})
.collect(),
}),
Self::Pointer(inner) => TypeSignature::Ptr(SignaturePointer {
modifiers: CustomModifiers::default(),
base: Box::new(inner.to_type_signature()),
}),
Self::ByRef(inner) => TypeSignature::ByRef(Box::new(inner.to_type_signature())),
Self::TypedReference => TypeSignature::TypedByRef,
Self::GenericParam(idx) => TypeSignature::GenericParamType(*idx),
Self::MethodGenericParam(idx) => TypeSignature::GenericParamMethod(*idx),
Self::FnPtr(sig) => TypeSignature::FnPtr(Box::new(SignatureMethod {
has_this: false,
explicit_this: false,
default: true,
vararg: false,
cdecl: false,
stdcall: false,
thiscall: false,
fastcall: false,
param_count_generic: 0,
#[allow(clippy::cast_possible_truncation)]
param_count: sig.params.len() as u32,
return_type: SignatureParameter {
modifiers: CustomModifiers::default(),
by_ref: false,
base: sig.ret.to_type_signature(),
},
params: sig
.params
.iter()
.map(|p| SignatureParameter {
modifiers: CustomModifiers::default(),
by_ref: false,
base: p.to_type_signature(),
})
.collect(),
varargs: Vec::new(),
})),
Self::Object | Self::Null | Self::Unknown | Self::Varying => TypeSignature::Object,
}
}
#[must_use]
pub fn from_cil_flavor(flavor: &CilFlavor, token: Token) -> Self {
match flavor {
CilFlavor::Void => Self::Void,
CilFlavor::Boolean => Self::Bool,
CilFlavor::Char => Self::Char,
CilFlavor::I1 => Self::I8,
CilFlavor::U1 => Self::U8,
CilFlavor::I2 => Self::I16,
CilFlavor::U2 => Self::U16,
CilFlavor::I4 => Self::I32,
CilFlavor::U4 => Self::U32,
CilFlavor::I8 => Self::I64,
CilFlavor::U8 => Self::U64,
CilFlavor::R4 => Self::F32,
CilFlavor::R8 => Self::F64,
CilFlavor::I => Self::NativeInt,
CilFlavor::U => Self::NativeUInt,
CilFlavor::Object => Self::Object,
CilFlavor::String => Self::String,
CilFlavor::Array { rank, .. } => Self::Array(Box::new(Self::Unknown), *rank),
CilFlavor::Pointer => Self::Pointer(Box::new(Self::Unknown)),
CilFlavor::ByRef => Self::ByRef(Box::new(Self::Unknown)),
CilFlavor::GenericParameter { index, method } => {
if *method {
Self::MethodGenericParam(*index)
} else {
Self::GenericParam(*index)
}
}
CilFlavor::GenericInstance | CilFlavor::Class | CilFlavor::Interface => {
Self::Class(TypeRef::new(token))
}
CilFlavor::ValueType | CilFlavor::TypedRef { .. } => {
Self::ValueType(TypeRef::new(token))
}
CilFlavor::Pinned | CilFlavor::FnPtr { .. } | CilFlavor::Unknown => Self::Unknown,
}
}
#[must_use]
pub fn from_type_signature(signature: &TypeSignature, assembly: &CilObject) -> Self {
match signature {
TypeSignature::Void => Self::Void,
TypeSignature::Boolean => Self::Bool,
TypeSignature::Char => Self::Char,
TypeSignature::I1 => Self::I8,
TypeSignature::U1 => Self::U8,
TypeSignature::I2 => Self::I16,
TypeSignature::U2 => Self::U16,
TypeSignature::I4 => Self::I32,
TypeSignature::U4 => Self::U32,
TypeSignature::I8 => Self::I64,
TypeSignature::U8 => Self::U64,
TypeSignature::R4 => Self::F32,
TypeSignature::R8 => Self::F64,
TypeSignature::I => Self::NativeInt,
TypeSignature::U => Self::NativeUInt,
TypeSignature::String => Self::String,
TypeSignature::Object | TypeSignature::Type | TypeSignature::Boxed => Self::Object,
TypeSignature::TypedByRef => Self::TypedReference,
TypeSignature::Class(token) | TypeSignature::ValueType(token) => {
Self::from_type_token(*token, assembly)
}
TypeSignature::SzArray(sz_array) => {
let elem_type = Self::from_type_signature(&sz_array.base, assembly);
Self::Array(Box::new(elem_type), 1)
}
TypeSignature::Array(array) => {
let elem_type = Self::from_type_signature(&array.base, assembly);
Self::Array(Box::new(elem_type), array.rank)
}
TypeSignature::Ptr(ptr) => {
let inner = Self::from_type_signature(&ptr.base, assembly);
Self::Pointer(Box::new(inner))
}
TypeSignature::ByRef(inner) => {
let inner_type = Self::from_type_signature(inner, assembly);
Self::ByRef(Box::new(inner_type))
}
TypeSignature::GenericParamType(index) => Self::GenericParam(*index),
TypeSignature::GenericParamMethod(index) => Self::MethodGenericParam(*index),
TypeSignature::GenericInst(base_type, type_args) => {
let base = Self::from_type_signature(base_type, assembly);
let args = type_args
.iter()
.map(|a| Self::from_type_signature(a, assembly))
.collect();
Self::GenericInst(Box::new(base), args)
}
TypeSignature::Pinned(inner) => Self::from_type_signature(inner, assembly),
TypeSignature::FnPtr(_)
| TypeSignature::ModifiedRequired(_)
| TypeSignature::ModifiedOptional(_)
| TypeSignature::Sentinel
| TypeSignature::Unknown
| TypeSignature::Internal
| TypeSignature::Modifier
| TypeSignature::Reserved
| TypeSignature::Field => Self::Unknown,
}
}
#[must_use]
pub fn from_type_token(token: Token, assembly: &CilObject) -> Self {
let table_id = token.table();
match table_id {
0x02 | 0x01 => {
let Some(cil_type) = assembly.types().get(&token) else {
return Self::Unknown;
};
let name = &cil_type.name;
let namespace = &cil_type.namespace;
if namespace == "System" {
if let Some(primitive) = Self::match_system_type(name) {
return primitive;
}
}
if cil_type.flavor().is_value_type() {
Self::ValueType(TypeRef::new(token))
} else {
Self::Class(TypeRef::new(token))
}
}
0x1B | 0xF0 => {
let Some(cil_type) = assembly.types().get(&token) else {
return Self::Unknown;
};
Self::from_cil_flavor(cil_type.flavor(), token)
}
_ => Self::Unknown,
}
}
fn match_system_type(name: &str) -> Option<Self> {
if let Some(kind) = wellknown::system_name_to_primitive(name) {
return Some(Self::from_cil_flavor(&CilFlavor::from(kind), Token::new(0)));
}
match name {
"TypedReference" => Some(Self::TypedReference),
_ => None,
}
}
}
impl fmt::Display for SsaType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Void => write!(f, "void"),
Self::Bool => write!(f, "bool"),
Self::I8 => write!(f, "i8"),
Self::U8 => write!(f, "u8"),
Self::I16 => write!(f, "i16"),
Self::U16 => write!(f, "u16"),
Self::I32 => write!(f, "i32"),
Self::U32 => write!(f, "u32"),
Self::I64 => write!(f, "i64"),
Self::U64 => write!(f, "u64"),
Self::NativeInt => write!(f, "nint"),
Self::NativeUInt => write!(f, "nuint"),
Self::F32 => write!(f, "f32"),
Self::F64 => write!(f, "f64"),
Self::Char => write!(f, "char"),
Self::Object => write!(f, "object"),
Self::String => write!(f, "string"),
Self::Class(t) => write!(f, "class {t}"),
Self::ValueType(t) => write!(f, "valuetype {t}"),
Self::GenericInst(base, args) => {
write!(f, "{base}<")?;
for (i, arg) in args.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{arg}")?;
}
write!(f, ">")
}
Self::Array(elem, 1) => write!(f, "{elem}[]"),
Self::Array(elem, rank) => write!(f, "{elem}[{rank}d]"),
Self::Pointer(inner) => write!(f, "{inner}*"),
Self::ByRef(inner) => write!(f, "{inner}&"),
Self::TypedReference => write!(f, "typedref"),
Self::GenericParam(idx) => write!(f, "!{idx}"),
Self::MethodGenericParam(idx) => write!(f, "!!{idx}"),
Self::FnPtr(_) => write!(f, "fnptr"),
Self::Null => write!(f, "null"),
Self::Unknown => write!(f, "?"),
Self::Varying => write!(f, "varying"),
}
}
}
#[must_use]
pub fn resolve_corelib_valuetype(assembly: &CilObject, fullname: &str) -> SsaType {
let registry = assembly.types();
for entry in registry.iter() {
let ty = entry.value();
if ty.token.table() == 0x01 && ty.fullname() == fullname {
return SsaType::ValueType(TypeRef::new(ty.token));
}
}
if let Some(ty) = registry.get_by_fullname(fullname, true) {
return SsaType::ValueType(TypeRef::new(ty.token));
}
log::warn!(
"resolve_corelib_valuetype: no TypeRef or TypeDef for {fullname:?}; \
SSA type falls back to Unknown — the assembly appears to be missing \
corelib imports for this type"
);
SsaType::Unknown
}
pub trait TypeProvider {
fn arg_type(&self, idx: u16) -> SsaType;
fn local_type(&self, idx: u16) -> SsaType;
fn call_return_type(&self, token: Token) -> SsaType;
fn newobj_type(&self, ctor_token: Token) -> SsaType;
fn field_type(&self, field_token: Token) -> SsaType;
fn call_indirect_return_type(&self, sig_token: Token) -> SsaType;
fn assembly(&self) -> Option<&CilObject>;
fn local_type_signatures(&self) -> Option<Vec<SignatureLocalVariable>> {
None
}
fn handler_catch_type(&self, _handler_index: usize) -> SsaType {
SsaType::Object
}
}
pub struct TypeContext<'a> {
method: &'a Method,
assembly: &'a CilObject,
}
impl<'a> TypeContext<'a> {
#[must_use]
pub fn new(method: &'a Method, assembly: &'a CilObject) -> Self {
Self { method, assembly }
}
#[must_use]
pub fn assembly(&self) -> &'a CilObject {
self.assembly
}
#[must_use]
pub fn arg_type(&self, idx: u16) -> SsaType {
let idx = idx as usize;
if self.method.signature.has_this {
if idx == 0 {
return self
.method
.declaring_type_rc()
.map_or(SsaType::Object, |dt| SsaType::Class(TypeRef::new(dt.token)));
}
if let Some(param) = self.method.signature.params.get(idx - 1) {
let ty = SsaType::from_type_signature(¶m.base, self.assembly);
return self.validate_generic_params(ty);
}
} else if let Some(param) = self.method.signature.params.get(idx) {
let ty = SsaType::from_type_signature(¶m.base, self.assembly);
return self.validate_generic_params(ty);
}
SsaType::Unknown
}
#[must_use]
pub fn local_type(&self, idx: u16) -> SsaType {
let signatures = self
.local_type_signatures()
.or_else(|| self.method.get_local_type_signatures());
signatures
.and_then(|types| types.into_iter().nth(idx as usize))
.map_or(SsaType::Unknown, |sig| {
let ty = SsaType::from_type_signature(&sig.base, self.assembly);
self.validate_generic_params(ty)
})
}
#[must_use]
pub fn call_return_type(&self, token: Token) -> SsaType {
let table_id = token.table();
match table_id {
0x06 => self
.assembly
.methods()
.get(&token)
.map_or(SsaType::Unknown, |entry| {
let method = entry.value();
SsaType::from_type_signature(&method.signature.return_type.base, self.assembly)
}),
0x0A => self
.assembly
.refs_members()
.get(&token)
.and_then(|entry| {
let member_ref = entry.value();
match &member_ref.signature {
MemberRefSignature::Method(sig) => Some(SsaType::from_type_signature(
&sig.return_type.base,
self.assembly,
)),
MemberRefSignature::Field(_) => None,
}
})
.unwrap_or(SsaType::Unknown),
0x2B => {
let Some(entry) = self.assembly.method_specs().get(&token) else {
return SsaType::Unknown;
};
let spec = entry.value();
let Some(method_token) = spec.method.token() else {
return SsaType::Unknown;
};
let base_type = self.call_return_type(method_token);
Self::resolve_method_generic_params(base_type, &spec.instantiation, self.assembly)
}
_ => SsaType::Unknown,
}
}
#[must_use]
pub fn newobj_type(&self, ctor_token: Token) -> SsaType {
let table_id = ctor_token.table();
match table_id {
0x06 => self
.assembly
.methods()
.get(&ctor_token)
.and_then(|entry| entry.value().declaring_type_rc().map(|dt| dt.token))
.map_or(SsaType::Object, |type_token| {
SsaType::Class(TypeRef::new(type_token))
}),
0x0A => self
.assembly
.refs_members()
.get(&ctor_token)
.and_then(|entry| entry.value().declaredby.token())
.map_or(SsaType::Object, |class_token| {
SsaType::from_type_token(class_token, self.assembly)
}),
0x2B => self
.assembly
.method_specs()
.get(&ctor_token)
.and_then(|entry| entry.value().method.token())
.map_or(SsaType::Object, |method_token| {
self.newobj_type(method_token)
}),
_ => SsaType::Object,
}
}
#[must_use]
pub fn field_type(&self, field_token: Token) -> SsaType {
let table_id = field_token.table();
match table_id {
0x04 => self
.assembly
.types()
.get_field_signature(&field_token)
.map_or(SsaType::Unknown, |sig| {
SsaType::from_type_signature(&sig, self.assembly)
}),
0x0A => self
.assembly
.refs_members()
.get(&field_token)
.and_then(|entry| {
if let MemberRefSignature::Field(field_sig) = &entry.value().signature {
Some(SsaType::from_type_signature(&field_sig.base, self.assembly))
} else {
None
}
})
.unwrap_or(SsaType::Unknown),
_ => SsaType::Unknown,
}
}
#[must_use]
pub fn call_indirect_return_type(&self, sig_token: Token) -> SsaType {
if sig_token.table() != 0x11 {
return SsaType::Unknown;
}
let Some(tables) = self.assembly.tables() else {
return SsaType::Unknown;
};
let Some(table) = tables.table::<StandAloneSigRaw>() else {
return SsaType::Unknown;
};
let Some(raw) = table.get(sig_token.row()) else {
return SsaType::Unknown;
};
let Some(blob) = self.assembly.blob() else {
return SsaType::Unknown;
};
let Ok(owned) = raw.to_owned(blob) else {
return SsaType::Unknown;
};
match &owned.parsed_signature {
StandAloneSignature::Method(sig) => {
SsaType::from_type_signature(&sig.return_type.base, self.assembly)
}
_ => SsaType::Unknown,
}
}
fn resolve_method_generic_params(
ty: SsaType,
spec: &SignatureMethodSpec,
assembly: &CilObject,
) -> SsaType {
match ty {
SsaType::MethodGenericParam(idx) => spec
.generic_args
.get(idx as usize)
.map_or(SsaType::Unknown, |sig| {
SsaType::from_type_signature(sig, assembly)
}),
SsaType::Array(elem, rank) => {
let resolved = Self::resolve_method_generic_params(*elem, spec, assembly);
SsaType::Array(Box::new(resolved), rank)
}
SsaType::ByRef(inner) => {
let resolved = Self::resolve_method_generic_params(*inner, spec, assembly);
SsaType::ByRef(Box::new(resolved))
}
SsaType::Pointer(inner) => {
let resolved = Self::resolve_method_generic_params(*inner, spec, assembly);
SsaType::Pointer(Box::new(resolved))
}
SsaType::GenericInst(base, args) => {
let resolved_base = Self::resolve_method_generic_params(*base, spec, assembly);
let resolved_args = args
.into_iter()
.map(|a| Self::resolve_method_generic_params(a, spec, assembly))
.collect();
SsaType::GenericInst(Box::new(resolved_base), resolved_args)
}
other => other,
}
}
fn validate_generic_params(&self, ty: SsaType) -> SsaType {
match ty {
SsaType::MethodGenericParam(idx) => {
if (idx as usize) < self.method.generic_params.count() {
SsaType::MethodGenericParam(idx)
} else {
SsaType::Unknown
}
}
SsaType::GenericParam(idx) => {
let valid = self
.method
.declaring_type_rc()
.is_some_and(|dt| (idx as usize) < dt.generic_params.count());
if valid {
SsaType::GenericParam(idx)
} else {
SsaType::Unknown
}
}
SsaType::Array(elem, rank) => {
SsaType::Array(Box::new(self.validate_generic_params(*elem)), rank)
}
SsaType::ByRef(inner) => SsaType::ByRef(Box::new(self.validate_generic_params(*inner))),
SsaType::Pointer(inner) => {
SsaType::Pointer(Box::new(self.validate_generic_params(*inner)))
}
SsaType::GenericInst(base, args) => {
let validated_base = self.validate_generic_params(*base);
let validated_args = args
.into_iter()
.map(|a| self.validate_generic_params(a))
.collect();
SsaType::GenericInst(Box::new(validated_base), validated_args)
}
other => other,
}
}
#[must_use]
pub fn local_type_signatures(&self) -> Option<Vec<SignatureLocalVariable>> {
let body = self.method.body.get()?;
if body.local_var_sig_token == 0 {
return None;
}
let tables = self.assembly.tables()?;
let table = tables.table::<StandAloneSigRaw>()?;
let raw = table.get(body.local_var_sig_token & 0x00FF_FFFF)?;
let blob = self.assembly.blob()?;
let sig_data = blob.get(raw.signature as usize).ok()?;
let locals_sig = parse_local_var_signature(sig_data).ok()?;
Some(locals_sig.locals)
}
#[must_use]
pub fn handler_catch_type(&self, handler_index: usize) -> SsaType {
let Some(body) = self.method.body.get() else {
return SsaType::Object;
};
let Some(handler) = body.exception_handlers.get(handler_index) else {
return SsaType::Object;
};
if handler.flags == ExceptionHandlerFlags::EXCEPTION {
if let Some(type_ref) = &handler.handler {
return SsaType::from_type_token(type_ref.token, self.assembly);
}
}
SsaType::Object
}
}
impl TypeProvider for TypeContext<'_> {
fn arg_type(&self, idx: u16) -> SsaType {
self.arg_type(idx)
}
fn local_type(&self, idx: u16) -> SsaType {
self.local_type(idx)
}
fn call_return_type(&self, token: Token) -> SsaType {
self.call_return_type(token)
}
fn newobj_type(&self, ctor_token: Token) -> SsaType {
self.newobj_type(ctor_token)
}
fn field_type(&self, field_token: Token) -> SsaType {
self.field_type(field_token)
}
fn call_indirect_return_type(&self, sig_token: Token) -> SsaType {
self.call_indirect_return_type(sig_token)
}
fn assembly(&self) -> Option<&CilObject> {
Some(self.assembly)
}
fn local_type_signatures(&self) -> Option<Vec<SignatureLocalVariable>> {
TypeContext::local_type_signatures(self)
}
fn handler_catch_type(&self, handler_index: usize) -> SsaType {
self.handler_catch_type(handler_index)
}
}
#[cfg(test)]
mod tests {
use crate::{
analysis::ssa::types::{SsaType, TypeClass, TypeRef},
metadata::{signatures::TypeSignature, token::Token},
};
#[test]
fn test_primitive_types() {
assert!(SsaType::I32.is_primitive());
assert!(SsaType::F64.is_primitive());
assert!(SsaType::Bool.is_primitive());
assert!(!SsaType::Object.is_primitive());
assert!(!SsaType::String.is_primitive());
}
#[test]
fn test_integer_types() {
assert!(SsaType::I32.is_integer());
assert!(SsaType::U64.is_integer());
assert!(SsaType::NativeInt.is_integer());
assert!(!SsaType::F32.is_integer());
assert!(!SsaType::Bool.is_integer());
}
#[test]
fn test_float_types() {
assert!(SsaType::F32.is_float());
assert!(SsaType::F64.is_float());
assert!(!SsaType::I32.is_float());
}
#[test]
fn test_reference_types() {
assert!(SsaType::Object.is_reference());
assert!(SsaType::String.is_reference());
assert!(SsaType::Null.is_reference());
let array_type = SsaType::Array(Box::new(SsaType::I32), 1);
assert!(array_type.is_reference());
assert!(!SsaType::I32.is_reference());
}
#[test]
fn test_array_type() {
let array = SsaType::Array(Box::new(SsaType::I32), 1);
assert!(array.is_array());
assert_eq!(array.array_element_type(), Some(&SsaType::I32));
assert_eq!(array.array_rank(), Some(1));
let multi_dim = SsaType::Array(Box::new(SsaType::F64), 3);
assert_eq!(multi_dim.array_rank(), Some(3));
}
#[test]
fn test_pointer_types() {
let ptr = SsaType::Pointer(Box::new(SsaType::I32));
assert!(ptr.is_pointer());
assert_eq!(ptr.pointee_type(), Some(&SsaType::I32));
let byref = SsaType::ByRef(Box::new(SsaType::Object));
assert!(byref.is_pointer());
assert_eq!(byref.pointee_type(), Some(&SsaType::Object));
}
#[test]
fn test_size_bytes() {
assert_eq!(SsaType::I8.size_bytes(), Some(1));
assert_eq!(SsaType::I16.size_bytes(), Some(2));
assert_eq!(SsaType::I32.size_bytes(), Some(4));
assert_eq!(SsaType::I64.size_bytes(), Some(8));
assert_eq!(SsaType::F32.size_bytes(), Some(4));
assert_eq!(SsaType::F64.size_bytes(), Some(8));
assert_eq!(SsaType::NativeInt.size_bytes(), None); assert_eq!(SsaType::Object.size_bytes(), None);
}
#[test]
fn test_stack_type() {
assert_eq!(SsaType::I8.stack_type(), SsaType::I32);
assert_eq!(SsaType::I16.stack_type(), SsaType::I32);
assert_eq!(SsaType::I32.stack_type(), SsaType::I32);
assert_eq!(SsaType::I64.stack_type(), SsaType::I64);
assert_eq!(SsaType::F32.stack_type(), SsaType::F64);
}
#[test]
fn test_type_merge() {
assert_eq!(SsaType::I32.merge(&SsaType::I32), SsaType::I32);
assert_eq!(SsaType::Unknown.merge(&SsaType::I32), SsaType::I32);
assert_eq!(SsaType::I32.merge(&SsaType::Unknown), SsaType::I32);
assert_eq!(SsaType::Null.merge(&SsaType::Object), SsaType::Object);
assert_eq!(SsaType::Object.merge(&SsaType::Null), SsaType::Object);
assert_eq!(SsaType::I32.merge(&SsaType::I64), SsaType::Varying);
assert_eq!(SsaType::Object.merge(&SsaType::I32), SsaType::Varying);
}
#[test]
fn test_display() {
assert_eq!(format!("{}", SsaType::I32), "i32");
assert_eq!(format!("{}", SsaType::Object), "object");
assert_eq!(
format!("{}", SsaType::Array(Box::new(SsaType::I32), 1)),
"i32[]"
);
assert_eq!(
format!("{}", SsaType::Pointer(Box::new(SsaType::I32))),
"i32*"
);
assert_eq!(
format!("{}", SsaType::ByRef(Box::new(SsaType::I32))),
"i32&"
);
assert_eq!(format!("{}", SsaType::GenericParam(0)), "!0");
assert_eq!(format!("{}", SsaType::MethodGenericParam(1)), "!!1");
}
#[test]
fn test_storage_class() {
assert_eq!(SsaType::I32.storage_class(), TypeClass::Int32);
assert_eq!(SsaType::U32.storage_class(), TypeClass::Int32);
assert_eq!(SsaType::Bool.storage_class(), TypeClass::Int32);
assert_eq!(SsaType::Char.storage_class(), TypeClass::Int32);
assert_eq!(SsaType::I8.storage_class(), TypeClass::Int32);
assert_eq!(SsaType::I64.storage_class(), TypeClass::Int64);
assert_eq!(SsaType::U64.storage_class(), TypeClass::Int64);
assert_eq!(SsaType::F32.storage_class(), TypeClass::Float32);
assert_eq!(SsaType::F64.storage_class(), TypeClass::Float64);
assert_eq!(SsaType::NativeInt.storage_class(), TypeClass::NativeInt);
assert_eq!(SsaType::NativeUInt.storage_class(), TypeClass::NativeInt);
assert_eq!(SsaType::Object.storage_class(), TypeClass::Reference);
assert_eq!(SsaType::String.storage_class(), TypeClass::Reference);
assert_eq!(
SsaType::Array(Box::new(SsaType::I32), 1).storage_class(),
TypeClass::Reference
);
assert_eq!(SsaType::Void.storage_class(), TypeClass::Other);
assert_eq!(SsaType::Unknown.storage_class(), TypeClass::Other);
}
#[test]
fn test_is_compatible_for_storage() {
assert!(SsaType::I32.is_compatible_for_storage(&SsaType::I32));
assert!(SsaType::I32.is_compatible_for_storage(&SsaType::U32));
assert!(SsaType::I32.is_compatible_for_storage(&SsaType::Bool));
assert!(SsaType::I32.is_compatible_for_storage(&SsaType::Char));
assert!(SsaType::I64.is_compatible_for_storage(&SsaType::U64));
assert!(!SsaType::Object.is_compatible_for_storage(&SsaType::String));
assert!(SsaType::String.is_compatible_for_storage(&SsaType::String));
assert!(SsaType::Unknown.is_compatible_for_storage(&SsaType::I32));
assert!(SsaType::I32.is_compatible_for_storage(&SsaType::Unknown));
assert!(!SsaType::I32.is_compatible_for_storage(&SsaType::I64));
assert!(!SsaType::I32.is_compatible_for_storage(&SsaType::Object));
assert!(!SsaType::F32.is_compatible_for_storage(&SsaType::F64));
}
fn make_generic_inst_class(args: Vec<SsaType>) -> SsaType {
SsaType::GenericInst(
Box::new(SsaType::Class(TypeRef::new(Token::new(0x0200_0001)))),
args,
)
}
#[test]
fn test_generic_inst_is_reference() {
let list_int = make_generic_inst_class(vec![SsaType::I32]);
assert!(list_int.is_reference());
let valuetype_inst = SsaType::GenericInst(
Box::new(SsaType::ValueType(TypeRef::new(Token::new(0x0200_0002)))),
vec![SsaType::I32],
);
assert!(!valuetype_inst.is_reference());
}
#[test]
fn test_generic_inst_storage_class() {
let list_int = make_generic_inst_class(vec![SsaType::I32]);
assert_eq!(list_int.storage_class(), TypeClass::Reference);
let valuetype_inst = SsaType::GenericInst(
Box::new(SsaType::ValueType(TypeRef::new(Token::new(0x0200_0002)))),
vec![SsaType::I32],
);
assert_eq!(valuetype_inst.storage_class(), TypeClass::Reference);
}
#[test]
fn test_generic_inst_display() {
let list_int = make_generic_inst_class(vec![SsaType::I32]);
assert_eq!(format!("{list_int}"), "class TypeRef(0x02000001)<i32>");
let dict = SsaType::GenericInst(
Box::new(SsaType::Class(TypeRef::new(Token::new(0x0200_0003)))),
vec![SsaType::String, SsaType::I32],
);
assert_eq!(format!("{dict}"), "class TypeRef(0x02000003)<string, i32>");
}
#[test]
fn test_generic_inst_to_type_signature_roundtrip() {
let list_int = make_generic_inst_class(vec![SsaType::I32]);
let sig = list_int.to_type_signature();
match &sig {
TypeSignature::GenericInst(base, args) => {
assert!(matches!(base.as_ref(), TypeSignature::Class(_)));
assert_eq!(args.len(), 1);
assert!(matches!(args[0], TypeSignature::I4));
}
other => panic!("Expected GenericInst, got {other:?}"),
}
}
#[test]
fn test_generic_inst_merge() {
let list_int = make_generic_inst_class(vec![SsaType::I32]);
let list_int2 = make_generic_inst_class(vec![SsaType::I32]);
assert_eq!(list_int.merge(&list_int2), list_int);
let list_str = make_generic_inst_class(vec![SsaType::String]);
assert_eq!(list_int.merge(&list_str), SsaType::Varying);
assert_eq!(SsaType::Null.merge(&list_int), list_int);
assert_eq!(list_int.merge(&SsaType::Null), list_int);
assert_eq!(SsaType::Unknown.merge(&list_int), list_int);
assert_eq!(list_int.merge(&SsaType::Unknown), list_int);
}
#[test]
fn test_generic_inst_storage_compatibility() {
let list_int = make_generic_inst_class(vec![SsaType::I32]);
assert!(!list_int.is_compatible_for_storage(&SsaType::Object));
assert!(!list_int.is_compatible_for_storage(&SsaType::String));
let list_int2 = make_generic_inst_class(vec![SsaType::I32]);
assert!(list_int.is_compatible_for_storage(&list_int2));
assert!(!list_int.is_compatible_for_storage(&SsaType::I32));
}
}