mod base;
mod builder;
mod encoder;
mod hash;
mod primitives;
mod registry;
mod resolver;
use std::{
collections::HashSet,
sync::{Arc, OnceLock},
};
pub use base::{
ArrayDimensions, CilFlavor, CilModifier, CilTypeRef, CilTypeRefList, CilTypeRefListIter,
CilTypeReference, PointerSize, ELEMENT_TYPE,
};
pub use builder::TypeBuilder;
pub use encoder::TypeSignatureEncoder;
pub use hash::TypeSignatureHash;
pub use primitives::{CilPrimitive, CilPrimitiveData, CilPrimitiveKind};
pub use registry::{CompleteTypeSpec, TypeRegistry, TypeSource};
pub use resolver::TypeResolver;
use crate::{
metadata::{
customattributes::CustomAttributeValueList,
method::{MethodRc, MethodRefList},
query::MethodQuery,
security::Security,
tables::{
EventList, FieldList, GenericParamList, MethodSpec, MethodSpecList, PropertyList,
TableId, TypeAttributes,
},
token::Token,
},
Error, Result,
};
pub type CilTypeList = Arc<boxcar::Vec<CilTypeRc>>;
pub type CilTypeRc = Arc<CilType>;
pub struct CilType {
pub token: Token,
flavor: OnceLock<CilFlavor>,
pub namespace: String,
pub name: String,
external: OnceLock<CilTypeReference>,
base: OnceLock<CilTypeRef>,
pub flags: u32,
pub fields: FieldList,
pub methods: MethodRefList,
pub properties: PropertyList,
pub events: EventList,
pub interfaces: CilTypeRefList,
pub overwrites: Arc<boxcar::Vec<CilTypeReference>>,
pub nested_types: CilTypeRefList,
pub generic_params: GenericParamList,
pub generic_args: MethodSpecList,
pub custom_attributes: CustomAttributeValueList,
pub packing_size: OnceLock<u16>,
pub class_size: OnceLock<u32>,
pub spec: OnceLock<CilFlavor>,
pub modifiers: Arc<boxcar::Vec<CilModifier>>,
pub security: OnceLock<Security>,
pub enclosing_type: OnceLock<CilTypeRef>,
fullname: OnceLock<String>,
}
impl CilType {
#[allow(clippy::too_many_arguments)]
pub fn new(
token: Token,
namespace: String,
name: String,
external: Option<CilTypeReference>,
base: Option<CilTypeRef>,
flags: u32,
fields: FieldList,
methods: MethodRefList,
flavor: Option<CilFlavor>,
) -> Self {
let base_lock = OnceLock::new();
if let Some(base_value) = base {
base_lock.set(base_value).ok();
}
let external_lock = OnceLock::new();
if let Some(external_value) = external {
external_lock.set(external_value).ok();
}
let flavor_lock = OnceLock::new();
if let Some(explicit_flavor) = flavor {
flavor_lock.set(explicit_flavor).ok();
}
CilType {
token,
namespace,
name,
external: external_lock,
base: base_lock,
flags,
flavor: flavor_lock,
fields,
methods,
properties: Arc::new(boxcar::Vec::new()),
events: Arc::new(boxcar::Vec::new()),
interfaces: Arc::new(boxcar::Vec::new()),
overwrites: Arc::new(boxcar::Vec::new()),
nested_types: Arc::new(boxcar::Vec::new()),
generic_params: Arc::new(boxcar::Vec::new()),
generic_args: Arc::new(boxcar::Vec::new()),
custom_attributes: Arc::new(boxcar::Vec::new()),
packing_size: OnceLock::new(),
class_size: OnceLock::new(),
spec: OnceLock::new(),
modifiers: Arc::new(boxcar::Vec::new()),
security: OnceLock::new(),
enclosing_type: OnceLock::new(),
fullname: OnceLock::new(),
}
}
pub fn set_base(&self, base_type: &CilTypeRef) -> Result<()> {
match self.base.set(base_type.clone()) {
Ok(()) => Ok(()),
Err(_) => {
if let Some(existing) = self.base.get() {
match (existing.upgrade(), base_type.upgrade()) {
(Some(existing_ref), Some(new_ref)) => {
if existing_ref.token == new_ref.token
|| existing_ref.is_structurally_equivalent(&new_ref)
{
Ok(())
} else {
Err(Error::TypeError(
format!("Base type was already set with different value: existing {} vs new {}",
existing_ref.fullname(), new_ref.fullname())
))
}
}
(None, None) => {
Ok(())
}
(Some(_existing_ref), None) => {
Ok(())
}
(None, Some(_new_ref)) => {
Ok(())
}
}
} else {
Err(Error::TypeError(
"Impossible OnceLock state detected".to_string(),
))
}
}
}
}
pub fn base(&self) -> Option<CilTypeRc> {
if let Some(base) = self.base.get() {
base.upgrade()
} else {
None
}
}
pub fn set_enclosing_type(&self, enclosing_type: &CilTypeRef) -> Result<()> {
match self.enclosing_type.set(enclosing_type.clone()) {
Ok(()) => Ok(()),
Err(_) => {
if let Some(existing) = self.enclosing_type.get() {
match (existing.upgrade(), enclosing_type.upgrade()) {
(Some(existing_ref), Some(new_ref)) => {
if existing_ref.token == new_ref.token
|| existing_ref.is_structurally_equivalent(&new_ref)
{
Ok(())
} else {
Err(Error::TypeError(format!(
"Enclosing type was already set with different value: existing {} vs new {}",
existing_ref.fullname(),
new_ref.fullname()
)))
}
}
(None | Some(_), None) | (None, Some(_)) => Ok(()),
}
} else {
Err(Error::TypeError(
"Impossible OnceLock state detected in set_enclosing_type".to_string(),
))
}
}
}
}
pub fn enclosing_type(&self) -> Option<CilTypeRc> {
if let Some(enclosing) = self.enclosing_type.get() {
enclosing.upgrade()
} else {
None
}
}
pub fn set_external(&self, external_ref: &CilTypeReference) -> Result<()> {
match self.external.set(external_ref.clone()) {
Ok(()) => Ok(()),
Err(_) => {
if let Some(existing) = self.external.get() {
if Self::external_refs_compatible(existing, external_ref) {
Ok(())
} else {
Err(malformed_error!(
"External reference was already set with different value"
))
}
} else {
Err(malformed_error!("External reference was already set"))
}
}
}
}
fn external_refs_compatible(existing: &CilTypeReference, new: &CilTypeReference) -> bool {
match (existing, new) {
(CilTypeReference::AssemblyRef(ar1), CilTypeReference::AssemblyRef(ar2)) => {
ar1.token == ar2.token
}
(CilTypeReference::ModuleRef(mr1), CilTypeReference::ModuleRef(mr2)) => {
mr1.token == mr2.token
}
(CilTypeReference::File(f1), CilTypeReference::File(f2)) => f1.token == f2.token,
_ => true,
}
}
pub fn get_external(&self) -> Option<&CilTypeReference> {
self.external.get()
}
pub fn flavor(&self) -> &CilFlavor {
self.flavor.get_or_init(|| self.compute_flavor())
}
fn compute_flavor(&self) -> CilFlavor {
if self.flags & TypeAttributes::INTERFACE != 0 {
return CilFlavor::Interface;
}
if self.namespace == "System" {
match self.name.as_str() {
"Boolean" => return CilFlavor::Boolean,
"Char" => return CilFlavor::Char,
"SByte" => return CilFlavor::I1,
"Byte" => return CilFlavor::U1,
"Int16" => return CilFlavor::I2,
"UInt16" => return CilFlavor::U2,
"Int32" => return CilFlavor::I4,
"UInt32" => return CilFlavor::U4,
"Int64" => return CilFlavor::I8,
"UInt64" => return CilFlavor::U8,
"Single" => return CilFlavor::R4,
"Double" => return CilFlavor::R8,
"IntPtr" => return CilFlavor::I,
"UIntPtr" => return CilFlavor::U,
"Decimal" | "ValueType" | "Enum" => return CilFlavor::ValueType,
"Object" => return CilFlavor::Object,
"String" => return CilFlavor::String,
"Void" => return CilFlavor::Void,
"Delegate" | "MulticastDelegate" => return CilFlavor::Class,
_ => {}
}
}
if let Some(inherited_flavor) = self.classify_by_inheritance() {
return inherited_flavor;
}
if let Some(attribute_flavor) = self.classify_by_attributes() {
return attribute_flavor;
}
CilFlavor::Class
}
fn classify_by_inheritance(&self) -> Option<CilFlavor> {
if let Some(base_type) = self.base() {
let base_fullname = base_type.fullname();
if base_fullname == "System.ValueType" || base_fullname == "System.Enum" {
return Some(CilFlavor::ValueType);
}
if base_fullname == "System.Delegate" || base_fullname == "System.MulticastDelegate" {
return Some(CilFlavor::Class); }
if base_type.fullname() != self.fullname() {
if let Some(base_flavor) = base_type.flavor.get() {
match base_flavor {
CilFlavor::ValueType => return Some(CilFlavor::ValueType),
CilFlavor::Interface => {
return Some(CilFlavor::Class);
}
_ => {}
}
} else {
if let Some(transitive_flavor) =
Self::analyze_transitive_inheritance(&base_type)
{
return Some(transitive_flavor);
}
}
}
}
None
}
fn analyze_transitive_inheritance(base_type: &CilType) -> Option<CilFlavor> {
const MAX_INHERITANCE_DEPTH: usize = 10;
let mut current = base_type.base();
let mut depth = 0;
while let Some(ancestor) = current {
depth += 1;
if depth > MAX_INHERITANCE_DEPTH {
break;
}
let ancestor_name = ancestor.fullname();
if ancestor_name == "System.ValueType" || ancestor_name == "System.Enum" {
return Some(CilFlavor::ValueType);
}
if ancestor_name == "System.Delegate" || ancestor_name == "System.MulticastDelegate" {
return Some(CilFlavor::Class);
}
if ancestor_name == "System.Object" {
return Some(CilFlavor::Class);
}
current = ancestor.base();
}
None
}
fn classify_by_attributes(&self) -> Option<CilFlavor> {
let is_sealed = self.flags & TypeAttributes::SEALED != 0;
let is_abstract = self.flags & TypeAttributes::ABSTRACT != 0;
if is_sealed && !is_abstract && self.methods.is_empty() && !self.fields.is_empty() {
return Some(CilFlavor::ValueType);
}
let layout = self.flags & TypeAttributes::LAYOUT_MASK;
if (layout == TypeAttributes::SEQUENTIAL_LAYOUT
|| layout == TypeAttributes::EXPLICIT_LAYOUT)
&& is_sealed
&& !is_abstract
{
return Some(CilFlavor::ValueType);
}
if is_abstract && !is_sealed {
return Some(CilFlavor::Class);
}
if self.has_enum_characteristics() {
return Some(CilFlavor::ValueType);
}
if self.has_delegate_characteristics() {
return Some(CilFlavor::Class);
}
None
}
fn has_enum_characteristics(&self) -> bool {
if self.flags & TypeAttributes::SEALED == 0 {
return false;
}
let instance_fields = self
.fields
.iter()
.filter(|(_, field)| field.flags & 0x10 == 0) .count();
let has_value_field = self
.fields
.iter()
.any(|(_, field)| field.name == "value__" && field.flags & 0x10 == 0);
instance_fields == 1 && has_value_field
}
fn has_delegate_characteristics(&self) -> bool {
if self.flags & TypeAttributes::SEALED == 0 {
return false;
}
let has_invoke = self.find_method("Invoke").is_some();
let has_async_methods =
self.find_method("BeginInvoke").is_some() || self.find_method("EndInvoke").is_some();
has_invoke && has_async_methods
}
pub fn is_public(&self) -> bool {
let vis = self.flags & TypeAttributes::VISIBILITY_MASK;
vis == TypeAttributes::PUBLIC || vis == TypeAttributes::NESTED_PUBLIC
}
pub fn is_internal(&self) -> bool {
let vis = self.flags & TypeAttributes::VISIBILITY_MASK;
vis == TypeAttributes::NOT_PUBLIC || vis == TypeAttributes::NESTED_ASSEMBLY
}
#[must_use]
pub fn is_interface(&self) -> bool {
matches!(self.flavor(), CilFlavor::Interface)
}
#[must_use]
pub fn is_class(&self) -> bool {
matches!(self.flavor(), CilFlavor::Class)
}
#[must_use]
pub fn is_value_type(&self) -> bool {
self.flavor().is_value_type()
}
#[must_use]
pub fn is_sealed(&self) -> bool {
self.flags & TypeAttributes::SEALED != 0
}
#[must_use]
pub fn is_abstract(&self) -> bool {
self.flags & TypeAttributes::ABSTRACT != 0
}
#[must_use]
pub fn is_enum(&self) -> bool {
self.base().is_some_and(|b| b.fullname() == "System.Enum")
}
#[must_use]
pub fn is_delegate(&self) -> bool {
self.base().is_some_and(|b| {
let name = b.fullname();
name == "System.MulticastDelegate" || name == "System.Delegate"
})
}
pub fn query_methods(&self) -> MethodQuery<'_> {
MethodQuery::from_type(&self.methods)
}
pub fn is_nested_internal(&self) -> bool {
let vis = self.flags & TypeAttributes::VISIBILITY_MASK;
vis == TypeAttributes::NESTED_PRIVATE
|| vis == TypeAttributes::NESTED_ASSEMBLY
|| vis == TypeAttributes::NESTED_FAM_AND_ASSEM
}
pub fn has_public_methods(&self) -> bool {
self.methods
.iter()
.any(|(_, method_ref)| method_ref.upgrade().is_some_and(|m| m.is_public()))
}
pub fn has_public_fields(&self) -> bool {
self.fields.iter().any(|(_, field)| {
(field.flags & 0x07) == 0x06 })
}
#[must_use]
pub fn find_method(&self, name: &str) -> Option<MethodRc> {
self.methods
.iter()
.find_map(|(_, method_ref)| method_ref.upgrade().filter(|m| m.name == name))
}
#[must_use]
pub fn find_methods(&self, name: &str) -> Vec<MethodRc> {
self.methods
.iter()
.filter_map(|(_, method_ref)| method_ref.upgrade().filter(|m| m.name == name))
.collect()
}
pub fn fullname(&self) -> String {
if let Some(cached) = self.fullname.get() {
return cached.clone();
}
let fullname = self.compute_fullname();
let _ = self.fullname.set(fullname.clone());
fullname
}
fn compute_fullname(&self) -> String {
let path_components = self.collect_type_path();
if path_components.len() > 1 {
self.format_nested_fullname(&path_components)
} else {
self.format_toplevel_fullname()
}
}
fn collect_type_path(&self) -> Vec<String> {
let mut path_components = Vec::new();
path_components.push(self.safe_type_name());
let mut visited_tokens = HashSet::new();
visited_tokens.insert(self.token);
let mut current_type = self.enclosing_type();
while let Some(enclosing) = current_type {
if visited_tokens.contains(&enclosing.token) {
break; }
visited_tokens.insert(enclosing.token);
path_components.push(Self::safe_name_for(&enclosing.name, enclosing.token));
current_type = enclosing.enclosing_type();
}
path_components.reverse();
path_components
}
fn safe_type_name(&self) -> String {
Self::safe_name_for(&self.name, self.token)
}
fn safe_name_for(name: &str, token: Token) -> String {
if name.is_empty() {
format!("<unnamed_{:08X}>", token.value())
} else {
name.to_string()
}
}
fn format_nested_fullname(&self, path_components: &[String]) -> String {
let nested_path = path_components.join("/");
if self.namespace.is_empty() {
nested_path
} else {
format!("{}.{}", self.namespace, nested_path)
}
}
fn format_toplevel_fullname(&self) -> String {
let type_name = self.safe_type_name();
if self.namespace.is_empty() {
type_name
} else {
format!("{}.{}", self.namespace, type_name)
}
}
pub fn is_typeref(&self) -> bool {
self.token.is_table(TableId::TypeRef)
}
pub fn is_compatible_with(&self, target: &CilType) -> bool {
if self.token == target.token {
return true;
}
if self.namespace == target.namespace && self.name == target.name {
return true;
}
self.is_assignable_to(target)
}
fn is_assignable_to(&self, target: &CilType) -> bool {
if self.flavor().is_primitive() && target.flavor().is_primitive() {
return self.flavor().is_compatible_with(target.flavor());
}
if matches!(target.flavor(), CilFlavor::GenericParameter { .. })
|| matches!(self.flavor(), CilFlavor::GenericParameter { .. })
{
return true;
}
if target.namespace == "System"
&& target.name == "Object"
&& self.flavor().is_reference_type()
{
return true;
}
if self.is_subtype_of(target) {
return true;
}
if target.is_interface() && self.implements_interface(target) {
return true;
}
false
}
fn is_subtype_of(&self, target: &CilType) -> bool {
let mut current = self.base();
while let Some(base_type) = current {
if base_type.token == target.token
|| (base_type.namespace == target.namespace && base_type.name == target.name)
{
return true;
}
current = base_type.base();
}
false
}
fn implements_interface(&self, interface: &CilType) -> bool {
for (_, interface_impl) in self.interfaces.iter() {
if let Some(impl_type) = interface_impl.upgrade() {
if impl_type.token == interface.token
|| (impl_type.namespace == interface.namespace
&& impl_type.name == interface.name)
{
return true;
}
}
}
if let Some(base_type) = self.base() {
return base_type.implements_interface(interface);
}
false
}
pub fn accepts_constant(&self, constant: &CilPrimitive) -> bool {
let constant_flavor = constant.to_flavor();
self.flavor().accepts_constant(&constant_flavor)
}
pub fn is_structurally_equivalent(&self, other: &CilType) -> bool {
if self.namespace != other.namespace
|| self.name != other.name
|| *self.flavor() != *other.flavor()
{
return false;
}
if self.token != other.token {
return false;
}
if !self.external_sources_equivalent(other) {
return false;
}
if !self.generic_args_equivalent(other) {
return false;
}
if !self.generic_params_equivalent(other) {
return false;
}
self.base_types_equivalent(other)
}
pub fn external_sources_equivalent(&self, other: &CilType) -> bool {
match (self.external.get(), other.external.get()) {
(Some(ext1), Some(ext2)) => Self::type_sources_equivalent(ext1, ext2),
(None, None) => true,
_ => false,
}
}
fn type_sources_equivalent(source1: &CilTypeReference, source2: &CilTypeReference) -> bool {
match (source1, source2) {
(CilTypeReference::AssemblyRef(ar1), CilTypeReference::AssemblyRef(ar2)) => {
if ar1.token == ar2.token {
return true;
}
ar1.name == ar2.name
&& ar1.identifier == ar2.identifier
&& ar1.major_version == ar2.major_version
&& ar1.minor_version == ar2.minor_version
&& ar1.build_number == ar2.build_number
&& ar1.revision_number == ar2.revision_number
&& ar1.culture == ar2.culture
}
(CilTypeReference::ModuleRef(mr1), CilTypeReference::ModuleRef(mr2)) => {
mr1.token == mr2.token
}
(CilTypeReference::File(f1), CilTypeReference::File(f2)) => f1.token == f2.token,
(CilTypeReference::None, CilTypeReference::None) => true,
_ => false,
}
}
fn generic_args_equivalent(&self, other: &CilType) -> bool {
let count = self.generic_args.count();
if count != other.generic_args.count() {
return false;
}
(0..count).all(
|i| match (self.generic_args.get(i), other.generic_args.get(i)) {
(Some(a1), Some(a2)) => Self::method_specs_equivalent(a1, a2),
(None, None) => true,
_ => false,
},
)
}
fn method_specs_equivalent(spec1: &MethodSpec, spec2: &MethodSpec) -> bool {
let inner_count = spec1.generic_args.count();
if inner_count != spec2.generic_args.count() {
return false;
}
(0..inner_count).all(
|j| match (spec1.generic_args.get(j), spec2.generic_args.get(j)) {
(Some(i1), Some(i2)) => i1.token() == i2.token(),
(None, None) => true,
_ => false,
},
)
}
fn generic_params_equivalent(&self, other: &CilType) -> bool {
let count = self.generic_params.count();
if count != other.generic_params.count() {
return false;
}
(0..count).all(
|i| match (self.generic_params.get(i), other.generic_params.get(i)) {
(Some(p1), Some(p2)) => p1.name == p2.name && p1.number == p2.number,
(None, None) => true,
_ => false,
},
)
}
fn base_types_equivalent(&self, other: &CilType) -> bool {
match (self.base.get(), other.base.get()) {
(Some(base1), Some(base2)) => {
match (base1.upgrade(), base2.upgrade()) {
(Some(b1), Some(b2)) => {
if b1.token == b2.token {
true
} else {
b1.is_structurally_equivalent(&b2)
}
}
(None, None) => true, _ => false, }
}
(None, None) => true, _ => false, }
}
pub fn is_array_of(&self, base_fullname: &str) -> bool {
if base_fullname.is_empty() || base_fullname.trim().is_empty() {
return false;
}
if let Some(element_name) = self.fullname().strip_suffix("[]") {
base_fullname == element_name || base_fullname.ends_with(&format!("/{element_name}"))
} else {
false
}
}
pub fn is_pointer_to(&self, base_fullname: &str) -> bool {
if base_fullname.is_empty() || base_fullname.trim().is_empty() {
return false;
}
if let Some(element_name) = self.fullname().strip_suffix('*') {
base_fullname == element_name || base_fullname.ends_with(&format!("/{element_name}"))
} else {
false
}
}
pub fn is_generic_of(&self, base_fullname: &str) -> bool {
if base_fullname.is_empty() || base_fullname.trim().is_empty() {
return false;
}
let derived_fullname = self.fullname();
(derived_fullname.contains('`') || base_fullname.contains('`'))
&& (derived_fullname.starts_with(base_fullname.split('`').next().unwrap_or(""))
|| base_fullname.starts_with(derived_fullname.split('`').next().unwrap_or("")))
}
}