use std::sync::{
atomic::{AtomicU32, Ordering},
Arc,
};
use crossbeam_skiplist::SkipMap;
use dashmap::DashMap;
use crate::{
metadata::{
identity::AssemblyIdentity,
signatures::{SignatureMethodSpec, TypeSignature},
tables::{AssemblyRefRc, FileRc, MethodSpec, ModuleRc, ModuleRefRc, TableId},
token::Token,
typesystem::{
CilFlavor, CilPrimitive, CilPrimitiveKind, CilType, CilTypeRc, CilTypeRef,
CilTypeReference, PointerSize, TypeSignatureHash,
},
},
Error::TypeNotFound,
Result,
};
#[derive(Clone)]
pub struct CompleteTypeSpec {
pub token_init: Option<Token>,
pub flavor: CilFlavor,
pub namespace: String,
pub name: String,
pub source: TypeSource,
pub generic_args: Option<Vec<CilTypeRc>>,
pub base_type: Option<CilTypeRc>,
pub flags: Option<u32>,
}
impl CompleteTypeSpec {
pub fn matches(&self, existing_type: &CilType) -> bool {
if existing_type.namespace != self.namespace
|| existing_type.name != self.name
|| *existing_type.flavor() != self.flavor
{
return false;
}
if !self.source_matches(existing_type) {
return false;
}
if !self.base_type_matches(existing_type) {
return false;
}
self.generic_args_match(existing_type)
}
fn source_matches(&self, existing_type: &CilType) -> bool {
let ext_ref = existing_type.get_external();
match (&self.source, ext_ref) {
(TypeSource::Assembly(_), None) => true,
(TypeSource::Assembly(_), Some(_)) => false, (src, Some(ext_ref)) => {
match (ext_ref, src) {
(CilTypeReference::AssemblyRef(ar), TypeSource::AssemblyRef(tok)) => {
ar.token == *tok
}
(CilTypeReference::ModuleRef(mr), TypeSource::ModuleRef(tok)) => {
mr.token == *tok
}
(CilTypeReference::File(f), TypeSource::File(tok)) => f.token == *tok,
_ => true, }
}
_ => false, }
}
fn base_type_matches(&self, existing_type: &CilType) -> bool {
match (&self.base_type, existing_type.base.get()) {
(Some(spec_base), Some(type_base)) => {
match type_base.upgrade() {
Some(base_type) => {
spec_base.token == base_type.token
|| spec_base.is_structurally_equivalent(&base_type)
}
None => false, }
}
(None, None) => true, _ => false, }
}
fn generic_args_match(&self, existing_type: &CilType) -> bool {
match &self.generic_args {
Some(spec_args) => {
if spec_args.len() != existing_type.generic_args.count() {
return false;
}
for (i, spec_arg) in spec_args.iter().enumerate() {
if let Some(type_arg) = existing_type.generic_args.get(i) {
if spec_arg.token != type_arg.token {
return false;
}
} else {
return false;
}
}
true
}
None => existing_type.generic_args.count() == 0, }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TypeSource {
Assembly(AssemblyIdentity),
Module(Token),
ModuleRef(Token),
AssemblyRef(Token),
File(Token),
Primitive,
Unknown,
}
impl TypeSource {
#[must_use]
pub fn is_external(&self) -> bool {
matches!(
self,
TypeSource::ModuleRef(_) | TypeSource::AssemblyRef(_) | TypeSource::File(_)
)
}
#[must_use]
pub fn is_primitive(&self) -> bool {
matches!(self, TypeSource::Primitive)
}
#[must_use]
pub fn is_unknown(&self) -> bool {
matches!(self, TypeSource::Unknown)
}
#[must_use]
pub fn token(&self) -> Option<Token> {
match self {
TypeSource::Module(t)
| TypeSource::ModuleRef(t)
| TypeSource::AssemblyRef(t)
| TypeSource::File(t) => Some(*t),
TypeSource::Assembly(_) | TypeSource::Primitive | TypeSource::Unknown => None,
}
}
}
struct SourceRegistry {
modules: DashMap<Token, ModuleRc>,
module_refs: DashMap<Token, ModuleRefRc>,
assembly_refs: DashMap<Token, AssemblyRefRc>,
files: DashMap<Token, FileRc>,
}
impl SourceRegistry {
fn new() -> Self {
SourceRegistry {
modules: DashMap::new(),
module_refs: DashMap::new(),
assembly_refs: DashMap::new(),
files: DashMap::new(),
}
}
fn register_source(&self, source: &CilTypeReference) -> TypeSource {
match source {
CilTypeReference::Module(module) => {
self.modules.insert(module.token, module.clone());
TypeSource::Module(module.token)
}
CilTypeReference::ModuleRef(module_ref) => {
self.module_refs
.insert(module_ref.token, module_ref.clone());
TypeSource::ModuleRef(module_ref.token)
}
CilTypeReference::AssemblyRef(assembly_ref) => {
self.assembly_refs
.insert(assembly_ref.token, assembly_ref.clone());
TypeSource::AssemblyRef(assembly_ref.token)
}
CilTypeReference::Assembly(assembly) => {
TypeSource::Assembly(AssemblyIdentity::from_assembly(assembly))
}
CilTypeReference::File(file) => {
self.files.insert(file.token, file.clone());
TypeSource::File(file.token)
}
_ => TypeSource::Unknown,
}
}
fn get_source(&self, source: &TypeSource) -> Option<CilTypeReference> {
match source {
TypeSource::Module(token) => self
.modules
.get(token)
.map(|module| CilTypeReference::Module(module.clone())),
TypeSource::ModuleRef(token) => self
.module_refs
.get(token)
.map(|moduleref| CilTypeReference::ModuleRef(moduleref.clone())),
TypeSource::AssemblyRef(token) => self
.assembly_refs
.get(token)
.map(|assemblyref| CilTypeReference::AssemblyRef(assemblyref.clone())),
TypeSource::File(token) => self
.files
.get(token)
.map(|file| CilTypeReference::File(file.clone())),
TypeSource::Primitive | TypeSource::Unknown | TypeSource::Assembly(_) => None,
}
}
}
pub struct TypeRegistry {
types: SkipMap<Token, CilTypeRc>,
next_token: AtomicU32,
sources: SourceRegistry,
current_assembly: AssemblyIdentity,
types_by_source: DashMap<TypeSource, Vec<Token>>,
types_by_fullname: DashMap<String, Vec<Token>>,
types_by_name: DashMap<String, Vec<Token>>,
types_by_namespace: DashMap<String, Vec<Token>>,
external_registries: DashMap<AssemblyIdentity, Arc<TypeRegistry>>,
}
impl TypeRegistry {
pub fn new(assembly_identity: AssemblyIdentity) -> Result<Self> {
let registry = TypeRegistry {
types: SkipMap::new(),
next_token: AtomicU32::new(0xF000_0020), sources: SourceRegistry::new(),
current_assembly: assembly_identity,
types_by_source: DashMap::new(),
types_by_fullname: DashMap::new(),
types_by_name: DashMap::new(),
types_by_namespace: DashMap::new(),
external_registries: DashMap::new(),
};
registry.initialize_primitives()?;
Ok(registry)
}
pub fn current_assembly(&self) -> AssemblyIdentity {
self.current_assembly.clone()
}
pub fn current_assembly_source(&self) -> TypeSource {
TypeSource::Assembly(self.current_assembly.clone())
}
fn next_token(&self) -> Token {
let next_token = self.next_token.fetch_add(1, Ordering::Relaxed);
if next_token == 0xFFFF_FFFF {
debug_assert!(
false,
"We ran out of tokens and are going overwrite existing ones"
);
self.next_token.store(0xF100_0000, Ordering::Relaxed);
}
Token::new(next_token)
}
fn initialize_primitives(&self) -> Result<()> {
for primitive in [
CilPrimitive::new(CilPrimitiveKind::Void),
CilPrimitive::new(CilPrimitiveKind::Boolean),
CilPrimitive::new(CilPrimitiveKind::Char),
CilPrimitive::new(CilPrimitiveKind::I1),
CilPrimitive::new(CilPrimitiveKind::U1),
CilPrimitive::new(CilPrimitiveKind::I2),
CilPrimitive::new(CilPrimitiveKind::U2),
CilPrimitive::new(CilPrimitiveKind::I4),
CilPrimitive::new(CilPrimitiveKind::U4),
CilPrimitive::new(CilPrimitiveKind::I8),
CilPrimitive::new(CilPrimitiveKind::U8),
CilPrimitive::new(CilPrimitiveKind::R4),
CilPrimitive::new(CilPrimitiveKind::R8),
CilPrimitive::new(CilPrimitiveKind::I),
CilPrimitive::new(CilPrimitiveKind::U),
CilPrimitive::new(CilPrimitiveKind::Object),
CilPrimitive::new(CilPrimitiveKind::String),
CilPrimitive::new(CilPrimitiveKind::TypedReference),
CilPrimitive::new(CilPrimitiveKind::ValueType),
CilPrimitive::new(CilPrimitiveKind::Var),
CilPrimitive::new(CilPrimitiveKind::MVar),
CilPrimitive::new(CilPrimitiveKind::Null),
] {
let token = primitive.token();
let flavor = primitive.to_flavor();
let new_type = Arc::new(CilType::new(
token,
primitive.namespace().to_string(),
primitive.name().to_string(),
None,
None,
0,
Arc::new(boxcar::Vec::new()),
Arc::new(boxcar::Vec::new()),
Some(flavor),
));
self.register_type_internal(&new_type, TypeSource::Primitive);
}
let object_token = CilPrimitive::new(CilPrimitiveKind::Object).token();
let value_type_token = CilPrimitive::new(CilPrimitiveKind::ValueType).token();
for primitive in [
CilPrimitive::new(CilPrimitiveKind::Void),
CilPrimitive::new(CilPrimitiveKind::Boolean),
CilPrimitive::new(CilPrimitiveKind::Char),
CilPrimitive::new(CilPrimitiveKind::I1),
CilPrimitive::new(CilPrimitiveKind::U1),
CilPrimitive::new(CilPrimitiveKind::I2),
CilPrimitive::new(CilPrimitiveKind::U2),
CilPrimitive::new(CilPrimitiveKind::I4),
CilPrimitive::new(CilPrimitiveKind::U4),
CilPrimitive::new(CilPrimitiveKind::I8),
CilPrimitive::new(CilPrimitiveKind::U8),
CilPrimitive::new(CilPrimitiveKind::R4),
CilPrimitive::new(CilPrimitiveKind::R8),
CilPrimitive::new(CilPrimitiveKind::I),
CilPrimitive::new(CilPrimitiveKind::U),
] {
let type_token = primitive.token();
if let (Some(type_rc), Some(value_type_rc)) = (
self.types.get(&type_token),
self.types.get(&value_type_token),
) {
type_rc
.value()
.base
.set(value_type_rc.value().clone().into())
.map_err(|_| malformed_error!("Type base already set"))?;
}
}
if let (Some(value_type_rc), Some(object_rc)) = (
self.types.get(&value_type_token),
self.types.get(&object_token),
) {
value_type_rc
.value()
.base
.set(object_rc.value().clone().into())
.map_err(|_| malformed_error!("ValueType base already set"))?;
}
if let (Some(string_rc), Some(object_rc)) = (
self.types
.get(&CilPrimitive::new(CilPrimitiveKind::String).token()),
self.types.get(&object_token),
) {
string_rc
.value()
.base
.set(object_rc.value().clone().into())
.map_err(|_| malformed_error!("String base already set"))?;
}
Ok(())
}
fn register_type_internal(&self, type_rc: &CilTypeRc, source: TypeSource) {
let token = type_rc.token;
if self.types.contains_key(&token) {
return;
}
self.types.insert(token, type_rc.clone());
self.types_by_source
.entry(source)
.or_default()
.push(type_rc.token);
if !type_rc.namespace.is_empty() {
self.types_by_namespace
.entry(type_rc.namespace.clone())
.or_default()
.push(type_rc.token);
}
self.types_by_name
.entry(type_rc.name.clone())
.or_default()
.push(type_rc.token);
self.types_by_fullname
.entry(type_rc.fullname())
.or_default()
.push(type_rc.token);
}
pub fn insert(&self, new_type: &CilTypeRc) {
let source = match new_type.get_external() {
Some(external_source) => self.register_source(external_source),
None => TypeSource::Assembly(self.current_assembly.clone()),
};
self.register_type_internal(new_type, source);
}
pub fn create_type_empty(&self) -> Result<CilTypeRc> {
let token = self.next_token();
let new_type = Arc::new(CilType::new(
token,
String::new(),
String::new(),
None,
None,
0,
Arc::new(boxcar::Vec::new()),
Arc::new(boxcar::Vec::new()),
None,
));
self.types.insert(token, new_type.clone());
Ok(new_type)
}
pub fn create_type_with_flavor(&self, flavor: CilFlavor) -> Result<CilTypeRc> {
let token = self.next_token();
let new_type = Arc::new(CilType::new(
token,
String::new(),
String::new(),
None,
None,
0,
Arc::new(boxcar::Vec::new()),
Arc::new(boxcar::Vec::new()),
Some(flavor),
));
self.types.insert(token, new_type.clone());
Ok(new_type)
}
pub fn get_primitive(&self, primitive: CilPrimitiveKind) -> Result<CilTypeRc> {
match self.types.get(&primitive.token()) {
Some(res) => Ok(res.value().clone()),
None => Err(TypeNotFound(primitive.token())),
}
}
pub fn get(&self, token: &Token) -> Option<CilTypeRc> {
self.types.get(token).map(|entry| entry.value().clone())
}
#[must_use]
pub fn module_type(&self) -> Option<CilTypeRc> {
self.get(&Token::new(0x0200_0001))
}
#[must_use]
pub fn module_cctor(&self) -> Option<Token> {
self.module_type().and_then(|module_type| {
module_type.methods.iter().find_map(|(_, method_ref)| {
method_ref
.upgrade()
.and_then(|m| if m.is_cctor() { Some(m.token) } else { None })
})
})
}
pub fn get_by_source_and_name(
&self,
source: &TypeSource,
namespace: &str,
name: &str,
) -> Option<CilTypeRc> {
let fullname = if namespace.is_empty() {
name.to_string()
} else {
format!("{namespace}.{name}")
};
if let Some(tokens) = self.types_by_source.get(source) {
for &token in tokens.value() {
if let Some(type_rc) = self.types.get(&token) {
if type_rc.value().namespace == namespace && type_rc.value().name == name {
return Some(type_rc.value().clone());
}
}
}
}
if let Some(tokens) = self.types_by_fullname.get(&fullname) {
if let Some(&token) = tokens.first() {
return self.types.get(&token).map(|res| res.value().clone());
}
}
None
}
pub fn get_by_namespace(&self, namespace: &str) -> Vec<CilTypeRc> {
if let Some(tokens) = self.types_by_namespace.get(namespace) {
tokens
.iter()
.filter_map(|token| self.types.get(token).map(|entry| entry.value().clone()))
.collect()
} else {
Vec::new()
}
}
pub fn get_by_name(&self, name: &str) -> Vec<CilTypeRc> {
if let Some(tokens) = self.types_by_name.get(name) {
tokens
.iter()
.filter_map(|token| self.types.get(token).map(|entry| entry.value().clone()))
.collect()
} else {
Vec::new()
}
}
pub fn get_by_fullname(&self, fullname: &str, external: bool) -> Option<CilTypeRc> {
if let Some(tokens) = self.types_by_fullname.get(fullname) {
for token in tokens.value() {
if let Some(entry) = self.types.get(token) {
let type_rc = entry.value().clone();
if type_rc.token.is_table(TableId::TypeDef)
|| type_rc.token.is_table(TableId::TypeSpec)
|| type_rc.token.table() == 0xF0
{
return Some(type_rc);
}
}
}
} else {
let mut candidates = Vec::new();
for key_entry in &self.types_by_fullname {
let key = key_entry.key();
let tokens = key_entry.value();
if key.ends_with(fullname) && key != fullname {
if key.contains('/') {
for token in tokens {
if let Some(entry) = self.types.get(token) {
let type_rc = entry.value().clone();
if type_rc.token.is_table(TableId::TypeDef)
|| type_rc.token.is_table(TableId::TypeSpec)
{
candidates.push(type_rc);
break; }
}
}
}
}
}
if let Some(candidate) = candidates.first() {
return Some(candidate.clone());
}
}
if external {
for external_registry_entry in &self.external_registries {
let external_registry = external_registry_entry.value();
if let Some(external_type) = external_registry.get_by_fullname(fullname, false) {
return Some(external_type);
}
}
}
None
}
pub fn get_by_fullname_list(&self, fullname: &str, include_external: bool) -> Vec<CilTypeRc> {
let mut typedef_matches = Vec::new();
if let Some(tokens) = self.types_by_fullname.get(fullname) {
for token in tokens.value() {
if let Some(entry) = self.types.get(token) {
let type_rc = entry.value().clone();
if type_rc.token.table() == 0xF0
|| (include_external && type_rc.token.is_table(TableId::TypeDef))
{
typedef_matches.push(type_rc);
}
}
}
}
if include_external {
for external_registry_entry in &self.external_registries {
let external_registry = external_registry_entry.value();
let external_types = external_registry.get_by_fullname_list(fullname, false);
typedef_matches.extend(external_types);
}
}
typedef_matches
}
#[must_use]
pub fn get_field_byte_size(&self, field_token: &Token, ptr_size: PointerSize) -> Option<usize> {
for entry in self {
let type_ref = entry.value();
for (_, field) in type_ref.fields.iter() {
if field.token == *field_token {
let sig = &field.signature;
if let Some(size) = sig.base.byte_size(ptr_size) {
return Some(size);
}
if let TypeSignature::ValueType(vt_token) = &sig.base {
if let Some(value_type) = self.get(vt_token) {
if let Some(class_size) = value_type.class_size.get() {
if *class_size > 0 {
return Some(*class_size as usize);
}
}
}
}
return None;
}
}
}
None
}
#[must_use]
pub fn get_field_signature(&self, field_token: &Token) -> Option<TypeSignature> {
for entry in self {
let type_ref = entry.value();
for (_, field) in type_ref.fields.iter() {
if field.token == *field_token {
return Some(field.signature.base.clone());
}
}
}
None
}
#[must_use]
pub fn get_field_index_in_type(
&self,
type_token: &Token,
field_token: &Token,
) -> Option<usize> {
let type_entry = self.get(type_token)?;
for (index, (_, field)) in type_entry.fields.iter().enumerate() {
if field.token == *field_token {
return Some(index);
}
}
None
}
pub fn register_source(&self, source: &CilTypeReference) -> TypeSource {
self.sources.register_source(source)
}
pub fn get_source_reference(&self, source: &TypeSource) -> Option<CilTypeReference> {
self.sources.get_source(source)
}
pub fn get_or_create_type(&self, spec: &CompleteTypeSpec) -> Result<CilTypeRc> {
let token = if let Some(init_token) = spec.token_init {
init_token
} else {
self.next_token()
};
if let Some(existing) = self.types.get(&token) {
return Ok(existing.value().clone());
}
let flags = spec.flags.unwrap_or(0);
let new_type = Arc::new(CilType::new(
token,
spec.namespace.clone(),
spec.name.clone(),
self.get_source_reference(&spec.source),
None,
flags,
Arc::new(boxcar::Vec::new()),
Arc::new(boxcar::Vec::new()),
Some(spec.flavor.clone()),
));
Self::configure_type_from_spec(&new_type, spec)?;
self.register_type_internal(&new_type, spec.source.clone());
Ok(new_type)
}
fn calculate_complete_type_hash(spec: &CompleteTypeSpec) -> u64 {
let mut hash_builder = TypeSignatureHash::new()
.add_flavor(&spec.flavor)
.add_fullname(&spec.namespace, &spec.name)
.add_source(&spec.source);
if let Some(generic_args) = &spec.generic_args {
hash_builder = hash_builder
.add_component(&generic_args.len())
.add_component(&"generic_args_marker");
for (index, arg) in generic_args.iter().enumerate() {
hash_builder = hash_builder.add_component(&index).add_token(&arg.token);
hash_builder = hash_builder
.add_fullname(&arg.namespace, &arg.name)
.add_flavor(arg.flavor());
}
} else {
hash_builder = hash_builder.add_component(&"non_generic_marker");
}
if let Some(base_type) = &spec.base_type {
hash_builder = hash_builder
.add_component(&"base_type_marker")
.add_token(&base_type.token)
.add_fullname(&base_type.namespace, &base_type.name)
.add_flavor(base_type.flavor());
} else {
hash_builder = hash_builder.add_component(&"no_base_type_marker");
}
hash_builder.finalize()
}
fn configure_type_from_spec(type_ref: &CilTypeRc, spec: &CompleteTypeSpec) -> Result<()> {
if let Some(base_type) = &spec.base_type {
if type_ref.base.get().is_none() {
type_ref
.base
.set(CilTypeRef::from(base_type.clone()))
.map_err(|_| malformed_error!("Base type already set"))?;
}
}
if let Some(generic_args) = &spec.generic_args {
for (index, arg_type) in generic_args.iter().enumerate() {
let rid = u32::try_from(index)
.map_err(|_| malformed_error!("Generic argument index too large"))?
+ 1;
let token_value = 0x2B00_0000_u32
.checked_add(
u32::try_from(index)
.map_err(|_| malformed_error!("Generic argument index too large"))?,
)
.and_then(|v| v.checked_add(1))
.ok_or_else(|| malformed_error!("Token value overflow"))?;
let method_spec = Arc::new(MethodSpec {
rid,
token: Token::new(token_value),
offset: 0,
method: CilTypeReference::None,
instantiation: SignatureMethodSpec {
generic_args: vec![],
},
custom_attributes: Arc::new(boxcar::Vec::new()),
generic_args: {
let type_ref_list = Arc::new(boxcar::Vec::with_capacity(1));
type_ref_list.push(arg_type.clone().into());
type_ref_list
},
});
type_ref.generic_args.push(method_spec);
}
}
Ok(())
}
pub fn len(&self) -> usize {
self.types.len()
}
pub fn is_empty(&self) -> bool {
self.types.is_empty()
}
pub fn iter(&self) -> crossbeam_skiplist::map::Iter<'_, Token, CilTypeRc> {
self.types.iter()
}
pub fn all_types(&self) -> Vec<CilTypeRc> {
self.types
.iter()
.map(|entry| entry.value().clone())
.collect()
}
pub fn types_from_source(&self, source: &TypeSource) -> Vec<CilTypeRc> {
if let Some(tokens) = self.types_by_source.get(source) {
tokens
.iter()
.filter_map(|token| self.types.get(token).map(|entry| entry.value().clone()))
.collect()
} else {
Vec::new()
}
}
pub fn registry_link(&self, assembly_identity: AssemblyIdentity, registry: Arc<TypeRegistry>) {
self.external_registries.insert(assembly_identity, registry);
}
pub fn registry_unlink(
&self,
assembly_identity: &AssemblyIdentity,
) -> Option<Arc<TypeRegistry>> {
self.external_registries
.remove(assembly_identity)
.map(|(_, registry)| registry)
}
pub fn resolve_type_global(&self, fullname: &str) -> Option<CilTypeRc> {
self.get_by_fullname(fullname, true)
}
pub fn external_assemblies(&self) -> Vec<AssemblyIdentity> {
self.external_registries
.iter()
.map(|entry| entry.key().clone())
.collect()
}
pub fn get_external_registry(
&self,
assembly_identity: &AssemblyIdentity,
) -> Option<Arc<TypeRegistry>> {
self.external_registries
.get(assembly_identity)
.map(|entry| entry.value().clone())
}
pub fn external_registry_count(&self) -> usize {
self.external_registries.len()
}
pub fn redirect_typeref_to_typedef(
&self,
typeref_token: Token,
resolved_typedef: &CilTypeRc,
) -> bool {
let original_typeref = if let Some(entry) = self.types.get(&typeref_token) {
entry.value().clone()
} else {
return false; };
if let Some(external) = original_typeref.get_external() {
let source = self.register_source(external);
if let Some(mut list) = self.types_by_source.get_mut(&source) {
list.retain(|&token| token != typeref_token);
}
} else {
let current_source = self.current_assembly_source();
if let Some(mut list) = self.types_by_source.get_mut(¤t_source) {
list.retain(|&token| token != typeref_token);
}
}
if !original_typeref.namespace.is_empty() {
if let Some(mut list) = self.types_by_namespace.get_mut(&original_typeref.namespace) {
list.retain(|&token| token != typeref_token);
}
}
if let Some(mut list) = self.types_by_name.get_mut(&original_typeref.name) {
list.retain(|&token| token != typeref_token);
}
let old_fullname = original_typeref.fullname();
if let Some(mut list) = self.types_by_fullname.get_mut(&old_fullname) {
list.retain(|&token| token != typeref_token);
}
if let Some(external) = resolved_typedef.get_external() {
let source = self.register_source(external);
self.types_by_source
.entry(source)
.or_default()
.push(typeref_token);
}
if !resolved_typedef.namespace.is_empty() {
self.types_by_namespace
.entry(resolved_typedef.namespace.clone())
.or_default()
.push(typeref_token);
}
self.types_by_name
.entry(resolved_typedef.name.clone())
.or_default()
.push(typeref_token);
self.types_by_fullname
.entry(resolved_typedef.fullname())
.or_default()
.push(typeref_token);
self.types.insert(typeref_token, resolved_typedef.clone());
true
}
pub fn build_fullnames(&self) {
self.types_by_fullname.clear();
for entry in &self.types {
let type_rc = entry.value();
let current_fullname = type_rc.fullname();
self.types_by_fullname
.entry(current_fullname)
.or_default()
.push(type_rc.token);
}
}
}
impl<'a> IntoIterator for &'a TypeRegistry {
type Item = crossbeam_skiplist::map::Entry<'a, Token, CilTypeRc>;
type IntoIter = crossbeam_skiplist::map::Iter<'a, Token, CilTypeRc>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[cfg(test)]
mod tests {
use uguid::guid;
use super::*;
use crate::metadata::tables::{AssemblyRef, AssemblyRefHash, File, Module, ModuleRef};
#[test]
fn test_registry_primitives() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let bool_type = registry.get_primitive(CilPrimitiveKind::Boolean).unwrap();
assert_eq!(bool_type.name, "Boolean");
assert_eq!(bool_type.namespace, "System");
let int_type = registry.get_primitive(CilPrimitiveKind::I4).unwrap();
assert_eq!(int_type.name, "Int32");
assert_eq!(int_type.namespace, "System");
let object_type = registry.get_primitive(CilPrimitiveKind::Object).unwrap();
let string_type = registry.get_primitive(CilPrimitiveKind::String).unwrap();
assert_eq!(
string_type.base.get().unwrap().token().unwrap(),
object_type.token
);
let value_type = registry.get_primitive(CilPrimitiveKind::ValueType).unwrap();
assert_eq!(
value_type.base.get().unwrap().token().unwrap(),
object_type.token
);
assert_eq!(
int_type.base.get().unwrap().token().unwrap(),
value_type.token
);
let all_primitives = [
CilPrimitiveKind::Void,
CilPrimitiveKind::Boolean,
CilPrimitiveKind::Char,
CilPrimitiveKind::I1,
CilPrimitiveKind::U1,
CilPrimitiveKind::I2,
CilPrimitiveKind::U2,
CilPrimitiveKind::I4,
CilPrimitiveKind::U4,
CilPrimitiveKind::I8,
CilPrimitiveKind::U8,
CilPrimitiveKind::R4,
CilPrimitiveKind::R8,
CilPrimitiveKind::I,
CilPrimitiveKind::U,
CilPrimitiveKind::Object,
CilPrimitiveKind::String,
CilPrimitiveKind::TypedReference,
CilPrimitiveKind::ValueType,
CilPrimitiveKind::Var,
CilPrimitiveKind::MVar,
CilPrimitiveKind::Null,
];
for primitive in all_primitives.iter() {
let prim_type = registry.get_primitive(*primitive);
assert!(prim_type.is_ok(), "Failed to get primitive: {primitive:?}");
}
}
#[test]
fn test_create_and_lookup() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let list_type = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::Class,
namespace: "System.Collections.Generic".to_string(),
name: "List`1".to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
assert_eq!(list_type.name, "List`1");
assert_eq!(list_type.namespace, "System.Collections.Generic");
let found = registry.get_by_name("List`1");
assert_eq!(found.len(), 1);
assert_eq!(found[0].token, list_type.token);
let found = registry.get_by_namespace("System.Collections.Generic");
assert_eq!(found.len(), 1);
assert_eq!(found[0].token, list_type.token);
let found = registry.get_by_fullname_list("System.Collections.Generic.List`1", false);
assert_eq!(found.len(), 1);
assert_eq!(found[0].token, list_type.token);
let found = registry.get(&list_type.token);
assert!(found.is_some());
assert_eq!(found.unwrap().token, list_type.token);
let found = registry.get_by_source_and_name(
&TypeSource::Unknown,
"System.Collections.Generic",
"List`1",
);
assert!(found.is_some());
assert_eq!(found.unwrap().token, list_type.token);
}
#[test]
fn test_multiple_types_with_same_name() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let point1 = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::ValueType,
namespace: "System.Drawing".to_string(),
name: "Point".to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
let point2 = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::ValueType,
namespace: "System.Windows".to_string(),
name: "Point".to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
assert_ne!(point1.token, point2.token);
let found = registry.get_by_name("Point");
assert_eq!(found.len(), 2);
let found = registry.get_by_fullname_list("System.Drawing.Point", false);
assert_eq!(found.len(), 1);
assert_eq!(found[0].token, point1.token);
let found = registry.get_by_fullname_list("System.Windows.Point", false);
assert_eq!(found.len(), 1);
assert_eq!(found[0].token, point2.token);
}
#[test]
fn test_create_type_empty() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let empty_type = registry.create_type_empty().unwrap();
assert_eq!(empty_type.namespace, "");
assert_eq!(empty_type.name, "");
assert!(matches!(*empty_type.flavor(), CilFlavor::Class)); }
#[test]
fn test_create_type_with_flavor() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let class_type = registry.create_type_with_flavor(CilFlavor::Class).unwrap();
assert_eq!(class_type.namespace, "");
assert_eq!(class_type.name, "");
assert!(matches!(*class_type.flavor(), CilFlavor::Class));
}
#[test]
fn test_insert() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity.clone()).unwrap();
let token = Token::new(0x01000123);
let new_type = Arc::new(CilType::new(
token,
"MyNamespace".to_string(),
"MyClass".to_string(),
None,
None,
0,
Arc::new(boxcar::Vec::new()),
Arc::new(boxcar::Vec::new()),
Some(CilFlavor::Class),
));
registry.insert(&new_type);
let found = registry.get(&token);
assert!(found.is_some());
assert_eq!(found.unwrap().token, token);
registry.insert(&new_type);
let user_types = registry.types_from_source(&TypeSource::Assembly(test_identity));
assert_eq!(user_types.len(), 1);
}
#[test]
fn test_source_registry() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let module = Arc::new(Module {
token: Token::new(0x00000001),
name: "MainModule".to_string(),
mvid: guid!("01234567-89ab-cdef-0123-456789abcdef"),
encid: None,
rid: 1,
offset: 1,
generation: 0,
encbaseid: None,
imports: Vec::new(),
custom_attributes: Arc::new(boxcar::Vec::new()),
});
let module_ref = Arc::new(ModuleRef {
token: Token::new(0x1A000001),
name: "ReferenceModule".to_string(),
rid: 0,
offset: 0,
custom_attributes: Arc::new(boxcar::Vec::new()),
});
let assembly_ref = Arc::new(AssemblyRef {
token: Token::new(0x23000001),
flags: 0,
name: "ReferenceAssembly".to_string(),
culture: Some("".to_string()),
rid: 0,
offset: 0,
major_version: 1,
minor_version: 0,
build_number: 0,
revision_number: 1,
identifier: None,
hash: None,
os_platform_id: AtomicU32::new(0),
os_major_version: AtomicU32::new(0),
os_minor_version: AtomicU32::new(0),
processor: AtomicU32::new(0),
custom_attributes: Arc::new(boxcar::Vec::new()),
});
let file = Arc::new(File {
token: Token::new(0x26000001),
flags: 0,
name: "ExternalFile.dll".to_string(),
rid: 0,
offset: 0,
hash_value: AssemblyRefHash::new(&[0xCC, 0xCC]).unwrap(),
custom_attributes: Arc::new(boxcar::Vec::new()),
});
let module_source = registry.register_source(&CilTypeReference::Module(module.clone()));
let module_ref_source =
registry.register_source(&CilTypeReference::ModuleRef(module_ref.clone()));
let assembly_ref_source =
registry.register_source(&CilTypeReference::AssemblyRef(assembly_ref.clone()));
let file_source = registry.register_source(&CilTypeReference::File(file.clone()));
assert!(matches!(module_source, TypeSource::Module(_)));
assert!(matches!(module_ref_source, TypeSource::ModuleRef(_)));
assert!(matches!(assembly_ref_source, TypeSource::AssemblyRef(_)));
assert!(matches!(file_source, TypeSource::File(_)));
if let TypeSource::Module(token) = module_source {
if let CilTypeReference::Module(ref m) =
registry.get_source_reference(&module_source).unwrap()
{
assert_eq!(m.token, token);
} else {
panic!("Expected Module reference");
}
}
if let TypeSource::ModuleRef(token) = module_ref_source {
if let CilTypeReference::ModuleRef(ref m) =
registry.get_source_reference(&module_ref_source).unwrap()
{
assert_eq!(m.token, token);
} else {
panic!("Expected ModuleRef reference");
}
}
if let TypeSource::AssemblyRef(token) = assembly_ref_source {
if let CilTypeReference::AssemblyRef(ref a) =
registry.get_source_reference(&assembly_ref_source).unwrap()
{
assert_eq!(a.token, token);
} else {
panic!("Expected AssemblyRef reference");
}
}
if let TypeSource::File(token) = file_source {
if let CilTypeReference::File(ref f) =
registry.get_source_reference(&file_source).unwrap()
{
assert_eq!(f.token, token);
} else {
panic!("Expected File reference");
}
}
let type1 = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::Class,
namespace: "System.Collections".to_string(),
name: "ArrayList".to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
let type2 = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::Class,
namespace: "System.Collections".to_string(),
name: "ArrayList".to_string(),
source: module_ref_source.clone(),
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
let type3 = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::Class,
namespace: "System.Collections".to_string(),
name: "ArrayList".to_string(),
source: assembly_ref_source.clone(),
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
assert_ne!(type1.token, type2.token);
assert_ne!(type1.token, type3.token);
assert_ne!(type2.token, type3.token);
let types_from_module_ref = registry.types_from_source(&module_ref_source);
assert_eq!(types_from_module_ref.len(), 1);
assert_eq!(types_from_module_ref[0].token, type2.token);
let types_from_assembly_ref = registry.types_from_source(&assembly_ref_source);
assert_eq!(types_from_assembly_ref.len(), 1);
assert_eq!(types_from_assembly_ref[0].token, type3.token);
}
#[test]
fn test_registry_count_and_all_types() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let initial_count = registry.len();
let _ = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::Class,
namespace: "MyNamespace".to_string(),
name: "MyClass1".to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
let _ = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::Class,
namespace: "MyNamespace".to_string(),
name: "MyClass2".to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
assert_eq!(registry.len(), initial_count + 2);
let all_types = registry.all_types();
assert!(all_types.len() >= initial_count + 2);
let class1_count = all_types
.iter()
.filter(|t| t.name == "MyClass1" && t.namespace == "MyNamespace")
.count();
let class2_count = all_types
.iter()
.filter(|t| t.name == "MyClass2" && t.namespace == "MyNamespace")
.count();
assert_eq!(class1_count, 1);
assert_eq!(class2_count, 1);
}
#[test]
fn test_type_signature_hash() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let source1 = TypeSource::Unknown;
let source2 = TypeSource::AssemblyRef(Token::new(0x23000001));
let type1 = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::Class,
namespace: "System.Collections".to_string(),
name: "ArrayList".to_string(),
source: source1,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
let type2 = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::Class,
namespace: "System.Collections".to_string(),
name: "ArrayList".to_string(),
source: source2,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
assert_ne!(type1.token, type2.token);
}
#[test]
fn test_class_vs_generic_instance_hash_debug() {
let class_hash = TypeSignatureHash::new()
.add_flavor(&CilFlavor::Class)
.add_fullname("System.Collections.Generic", "Dictionary`2")
.add_source(&TypeSource::Unknown)
.finalize();
let generic_instance_hash = TypeSignatureHash::new()
.add_flavor(&CilFlavor::GenericInstance)
.add_fullname("System.Collections.Generic", "Dictionary`2")
.add_source(&TypeSource::Unknown)
.finalize();
assert_ne!(
class_hash, generic_instance_hash,
"Class and GenericInstance with same name should have different hashes"
);
}
#[test]
fn test_enhanced_generic_instance_creation() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let string_type = registry.get_primitive(CilPrimitiveKind::String).unwrap();
let int_type = registry.get_primitive(CilPrimitiveKind::I4).unwrap();
let _object_type = registry.get_primitive(CilPrimitiveKind::Object).unwrap();
let list_string_1 = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::GenericInstance,
namespace: "System.Collections.Generic".to_string(),
name: "List`1".to_string(),
source: TypeSource::Unknown,
generic_args: Some(vec![string_type.clone()]),
base_type: None,
flags: None,
})
.unwrap();
let list_string_2 = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::GenericInstance,
namespace: "System.Collections.Generic".to_string(),
name: "List`1".to_string(),
source: TypeSource::Unknown,
generic_args: Some(vec![string_type.clone()]),
base_type: None,
flags: None,
})
.unwrap();
let list_int = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::GenericInstance,
namespace: "System.Collections.Generic".to_string(),
name: "List`1".to_string(),
source: TypeSource::Unknown,
generic_args: Some(vec![int_type.clone()]),
base_type: None,
flags: None,
})
.unwrap();
assert_ne!(
list_string_1.token, list_string_2.token,
"Without deduplication, tokens should be different"
);
assert!(
!Arc::ptr_eq(&list_string_1, &list_string_2),
"Without deduplication, instances should be different"
);
assert!(
!list_string_1.is_structurally_equivalent(&list_int),
"List<string> and List<int> should NOT be structurally equivalent"
);
assert_eq!(list_string_1.namespace, "System.Collections.Generic");
assert_eq!(list_string_1.name, "List`1");
assert!(matches!(
*list_string_1.flavor(),
CilFlavor::GenericInstance
));
}
#[test]
fn test_token_generation() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let token1 = registry.create_type_empty().unwrap().token;
let token2 = registry.create_type_empty().unwrap().token;
let token3 = registry.create_type_empty().unwrap().token;
assert_eq!(token2.value(), token1.value() + 1);
assert_eq!(token3.value(), token2.value() + 1);
}
#[test]
fn test_get_and_lookup_methods() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let bad_token = Token::new(0x01999999);
assert!(registry.get(&bad_token).is_none());
let bad_name = registry.get_by_name("DoesNotExist");
assert!(bad_name.is_empty());
let bad_namespace = registry.get_by_namespace("NonExistent.Namespace");
assert!(bad_namespace.is_empty());
let bad_fullname = registry.get_by_fullname_list("NonExistent.Namespace.Type", false);
assert!(bad_fullname.is_empty());
let bad_source_name =
registry.get_by_source_and_name(&TypeSource::Unknown, "NonExistent.Namespace", "Type");
assert!(bad_source_name.is_none());
}
#[test]
fn test_improved_hash_collision_resistance() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let types = [
("System", "String", TypeSource::Unknown),
("System", "Object", TypeSource::Unknown),
("System.Collections", "ArrayList", TypeSource::Unknown),
("System.Collections.Generic", "List`1", TypeSource::Unknown),
(
"MyApp",
"Helper",
TypeSource::AssemblyRef(Token::new(0x23000001)),
),
(
"MyApp",
"Helper",
TypeSource::AssemblyRef(Token::new(0x23000002)),
),
];
let mut created_types = Vec::new();
for (namespace, name, source) in &types {
let type_ref = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::Class,
namespace: namespace.to_string(),
name: name.to_string(),
source: source.clone(),
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
created_types.push(type_ref);
}
assert_eq!(created_types.len(), types.len());
let mut tokens = std::collections::HashSet::new();
for type_ref in &created_types {
assert!(
tokens.insert(type_ref.token),
"Duplicate token found: {:?}",
type_ref.token
);
}
let duplicate_request = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::Class,
namespace: "System".to_string(),
name: "String".to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
assert_ne!(
duplicate_request.token, created_types[0].token,
"Each request should create a unique type instance"
);
}
#[test]
fn test_hash_different_flavors() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let class_type = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::Class,
namespace: "MyNamespace".to_string(),
name: "MyType".to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
let interface_type = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::Interface,
namespace: "MyNamespace".to_string(),
name: "MyType".to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
let value_type = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::ValueType,
namespace: "MyNamespace".to_string(),
name: "MyType".to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
assert_ne!(class_type.token, interface_type.token);
assert_ne!(class_type.token, value_type.token);
assert_ne!(interface_type.token, value_type.token);
assert_eq!(*class_type.flavor(), CilFlavor::Class);
assert_eq!(*interface_type.flavor(), CilFlavor::Interface);
assert_eq!(*value_type.flavor(), CilFlavor::ValueType);
}
#[test]
fn test_hash_collision_chain_functionality() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = TypeRegistry::new(test_identity).unwrap();
let similar_types = [
"Type1", "Type2", "Type3", "Type4", "Type5", "Type11", "Type12", "Type13", "Type14",
"Type15", "TypeA", "TypeB", "TypeC", "TypeD", "TypeE",
];
let mut created_tokens = std::collections::HashSet::new();
for type_name in &similar_types {
let type_ref = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: None,
flavor: CilFlavor::Class,
namespace: "TestNamespace".to_string(),
name: type_name.to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
assert!(
created_tokens.insert(type_ref.token),
"Token collision for type: {}",
type_name
);
assert_eq!(type_ref.name, *type_name);
assert_eq!(type_ref.namespace, "TestNamespace");
}
assert_eq!(created_tokens.len(), similar_types.len());
}
#[test]
fn test_signature_hash_ordering_independence() {
let hash1 = TypeSignatureHash::new()
.add_fullname("System", "String")
.add_source(&TypeSource::Unknown)
.add_flavor(&CilFlavor::Class)
.finalize();
let hash2 = TypeSignatureHash::new()
.add_flavor(&CilFlavor::Class)
.add_fullname("System", "String")
.add_source(&TypeSource::Unknown)
.finalize();
let hash3 = TypeSignatureHash::new()
.add_source(&TypeSource::Unknown)
.add_flavor(&CilFlavor::Class)
.add_fullname("System", "String")
.finalize();
assert_ne!(hash1, hash2, "Hash should be order-sensitive");
assert_ne!(hash1, hash3, "Hash should be order-sensitive");
assert_ne!(hash2, hash3, "Hash should be order-sensitive");
}
#[test]
fn test_signature_hash_component_uniqueness() {
let base_hash = TypeSignatureHash::new()
.add_fullname("System", "String")
.add_source(&TypeSource::Unknown)
.finalize();
let class_hash = TypeSignatureHash::new()
.add_fullname("System", "String")
.add_source(&TypeSource::Unknown)
.add_flavor(&CilFlavor::Class)
.finalize();
let interface_hash = TypeSignatureHash::new()
.add_fullname("System", "String")
.add_source(&TypeSource::Unknown)
.add_flavor(&CilFlavor::Interface)
.finalize();
let different_source_hash = TypeSignatureHash::new()
.add_fullname("System", "String")
.add_source(&TypeSource::AssemblyRef(Token::new(0x23000001)))
.add_flavor(&CilFlavor::Class)
.finalize();
assert_ne!(base_hash, class_hash);
assert_ne!(class_hash, interface_hash);
assert_ne!(class_hash, different_source_hash);
assert_ne!(interface_hash, different_source_hash);
}
#[test]
fn test_class_vs_generic_instance_hash_collision() {
let class_hash = TypeSignatureHash::new()
.add_flavor(&CilFlavor::Class)
.add_fullname("System.Collections.Generic", "Dictionary`2")
.add_source(&TypeSource::Unknown)
.finalize();
let generic_instance_hash = TypeSignatureHash::new()
.add_flavor(&CilFlavor::GenericInstance)
.add_fullname("System.Collections.Generic", "Dictionary`2")
.add_source(&TypeSource::Unknown)
.finalize();
assert_ne!(
class_hash, generic_instance_hash,
"CRITICAL: Class and GenericInstance are generating hash collisions! \
This proves the original hash collision issue still exists."
);
}
}