use std::collections::{BTreeSet, HashSet};
use crate::{
cilassembly::{
cleanup::{
references::PreDeletionRefs,
references::{
collect_referenced_standalonesig_rids,
collect_typerefs_from_deleted_memberref_sigs, remove_unreferenced_memberrefs,
remove_unreferenced_typerefs, remove_unreferenced_typespecs,
},
utils::{list_range, remove_candidates_not_alive, try_remove},
CleanupStats,
},
CilAssembly,
},
metadata::{
streams::TablesHeader,
tables::{
ClassLayoutRaw, ConstantRaw, CustomAttributeRaw, DeclSecurityRaw, EventMapRaw,
EventRaw, ExportedTypeRaw, FieldLayoutRaw, FieldMarshalRaw, FieldRvaRaw,
GenericParamConstraintRaw, GenericParamRaw, ImplMapRaw, InterfaceImplRaw,
ManifestResourceRaw, MemberRefRaw, MethodDefRaw, MethodImplRaw, MethodSemanticsRaw,
MethodSpecRaw, NestedClassRaw, ParamRaw, PropertyMapRaw, PropertyRaw, RowReadable,
StandAloneSigRaw, TableAccess, TableId, TypeRefRaw,
},
token::Token,
},
};
#[derive(Debug, Clone, Copy)]
pub(super) struct DeletionContext<'a> {
types: &'a HashSet<Token>,
methods: &'a HashSet<Token>,
fields: &'a HashSet<Token>,
}
impl<'a> DeletionContext<'a> {
#[must_use]
pub(super) fn new(
deleted_types: &'a HashSet<Token>,
deleted_methods: &'a HashSet<Token>,
deleted_fields: &'a HashSet<Token>,
) -> Self {
Self {
types: deleted_types,
methods: deleted_methods,
fields: deleted_fields,
}
}
#[must_use]
pub(super) fn is_deleted(&self, token: Token) -> bool {
self.types.contains(&token) || self.methods.contains(&token) || self.fields.contains(&token)
}
#[must_use]
pub(super) fn is_type_deleted(&self, token: Token) -> bool {
self.types.contains(&token)
}
#[must_use]
pub(super) fn is_method_deleted(&self, token: Token) -> bool {
self.methods.contains(&token)
}
#[must_use]
pub(super) fn is_field_deleted(&self, token: Token) -> bool {
self.fields.contains(&token)
}
}
pub(super) fn remove_orphan_entries<T>(
assembly: &mut CilAssembly,
is_orphan: impl Fn(&T) -> bool,
) -> usize
where
T: RowReadable,
for<'a> TablesHeader<'a>: TableAccess<'a, T>,
{
let orphan_rids: Vec<u32> = {
let view = assembly.view();
let Some(tables) = view.tables() else {
return 0;
};
let Some(table) = tables.table::<T>() else {
return 0;
};
(1..=table.row_count)
.filter_map(|rid| table.get(rid).filter(|r| is_orphan(r)).map(|_| rid))
.collect()
};
let mut removed_count = 0;
for rid in orphan_rids.into_iter().rev() {
if try_remove(assembly, T::TABLE_ID, rid) {
removed_count += 1;
}
}
removed_count
}
pub(super) fn remove_orphan_params(assembly: &mut CilAssembly, ctx: &DeletionContext) -> usize {
let mut orphan_params: Vec<u32> = {
let view = assembly.view();
let Some(tables) = view.tables() else {
return 0;
};
let Some(methoddef_table) = tables.table::<MethodDefRaw>() else {
return 0;
};
if tables.table::<ParamRaw>().is_none() {
return 0;
}
let method_count = methoddef_table.row_count;
let param_count = tables.table::<ParamRaw>().map_or(0, |t| t.row_count);
let mut params = Vec::new();
for method_rid in 1..=method_count {
let method_token = Token::from_parts(TableId::MethodDef, method_rid);
if !ctx.is_method_deleted(method_token) {
continue;
}
let range = list_range(method_rid, method_count, param_count, |rid| {
methoddef_table.get(rid).map(|m| m.param_list)
});
params.extend(range);
}
params
};
orphan_params.sort_unstable_by(|a, b| b.cmp(a));
orphan_params.dedup();
let mut removed_count = 0;
for rid in orphan_params {
if try_remove(assembly, TableId::Param, rid) {
removed_count += 1;
}
}
removed_count
}
pub(super) fn remove_orphan_attributes(assembly: &mut CilAssembly, ctx: &DeletionContext) -> usize {
remove_orphan_entries::<CustomAttributeRaw>(assembly, |attr| {
if ctx.is_deleted(attr.parent.token) {
return true;
}
ctx.is_deleted(attr.constructor.token)
})
}
pub(super) fn remove_orphan_classlayouts(
assembly: &mut CilAssembly,
ctx: &DeletionContext,
) -> usize {
remove_orphan_entries::<ClassLayoutRaw>(assembly, |layout| {
let parent_token = Token::from_parts(TableId::TypeDef, layout.parent);
ctx.is_type_deleted(parent_token)
})
}
pub(super) fn remove_orphan_fieldrvas(assembly: &mut CilAssembly, ctx: &DeletionContext) -> usize {
remove_orphan_entries::<FieldRvaRaw>(assembly, |rva| {
let field_token = Token::from_parts(TableId::Field, rva.field);
ctx.is_field_deleted(field_token)
})
}
pub(super) fn remove_orphan_nestedclass(
assembly: &mut CilAssembly,
ctx: &DeletionContext,
) -> usize {
remove_orphan_entries::<NestedClassRaw>(assembly, |nested| {
let nested_token = Token::from_parts(TableId::TypeDef, nested.nested_class);
let enclosing_token = Token::from_parts(TableId::TypeDef, nested.enclosing_class);
ctx.is_type_deleted(nested_token) || ctx.is_type_deleted(enclosing_token)
})
}
pub(super) fn collect_orphaned_nested_types(
assembly: &CilAssembly,
ctx: &DeletionContext,
) -> Vec<Token> {
let view = assembly.view();
let Some(tables) = view.tables() else {
return Vec::new();
};
let Some(nested_table) = tables.table::<NestedClassRaw>() else {
return Vec::new();
};
let mut orphaned = Vec::new();
for nested in nested_table.iter() {
let enclosing_token = Token::from_parts(TableId::TypeDef, nested.enclosing_class);
let nested_token = Token::from_parts(TableId::TypeDef, nested.nested_class);
if ctx.is_type_deleted(enclosing_token) && !ctx.is_type_deleted(nested_token) {
orphaned.push(nested_token);
}
}
orphaned
}
pub(super) fn remove_orphan_interfaceimpl(
assembly: &mut CilAssembly,
ctx: &DeletionContext,
) -> usize {
remove_orphan_entries::<InterfaceImplRaw>(assembly, |impl_| {
let class_token = Token::from_parts(TableId::TypeDef, impl_.class);
ctx.is_type_deleted(class_token)
})
}
pub(super) fn remove_orphan_methodimpl(assembly: &mut CilAssembly, ctx: &DeletionContext) -> usize {
remove_orphan_entries::<MethodImplRaw>(assembly, |impl_| {
let class_token = Token::from_parts(TableId::TypeDef, impl_.class);
ctx.is_type_deleted(class_token)
|| ctx.is_deleted(impl_.method_body.token)
|| ctx.is_deleted(impl_.method_declaration.token)
})
}
pub(super) fn remove_orphan_methodsemantics(
assembly: &mut CilAssembly,
ctx: &DeletionContext,
deleted_events: &HashSet<Token>,
deleted_properties: &HashSet<Token>,
) -> usize {
remove_orphan_entries::<MethodSemanticsRaw>(assembly, |sem| {
let method_token = Token::from_parts(TableId::MethodDef, sem.method);
ctx.is_method_deleted(method_token)
|| deleted_events.contains(&sem.association.token)
|| deleted_properties.contains(&sem.association.token)
})
}
pub(super) fn remove_orphan_constant(assembly: &mut CilAssembly, ctx: &DeletionContext) -> usize {
remove_orphan_entries::<ConstantRaw>(assembly, |constant| ctx.is_deleted(constant.parent.token))
}
pub(super) fn remove_orphan_fieldmarshal(
assembly: &mut CilAssembly,
ctx: &DeletionContext,
) -> usize {
remove_orphan_entries::<FieldMarshalRaw>(assembly, |marshal| {
ctx.is_deleted(marshal.parent.token)
})
}
pub(super) fn remove_orphan_fieldlayout(
assembly: &mut CilAssembly,
ctx: &DeletionContext,
) -> usize {
remove_orphan_entries::<FieldLayoutRaw>(assembly, |layout| {
let field_token = Token::from_parts(TableId::Field, layout.field);
ctx.is_field_deleted(field_token)
})
}
pub(super) fn remove_orphan_genericparam(
assembly: &mut CilAssembly,
ctx: &DeletionContext,
) -> (usize, HashSet<u32>) {
let orphan_rids: Vec<u32> = {
let view = assembly.view();
let Some(tables) = view.tables() else {
return (0, HashSet::new());
};
let Some(table) = tables.table::<GenericParamRaw>() else {
return (0, HashSet::new());
};
(1..=table.row_count)
.filter_map(|rid| {
table.get(rid).and_then(|param| {
if ctx.is_deleted(param.owner.token) {
Some(rid)
} else {
None
}
})
})
.collect()
};
let removed_rids: HashSet<u32> = orphan_rids.iter().copied().collect();
let mut removed_count = 0;
for rid in orphan_rids.into_iter().rev() {
if try_remove(assembly, TableId::GenericParam, rid) {
removed_count += 1;
}
}
(removed_count, removed_rids)
}
pub(super) fn remove_orphan_genericparamconstraint(
assembly: &mut CilAssembly,
removed_genericparams: &HashSet<u32>,
) -> usize {
remove_orphan_entries::<GenericParamConstraintRaw>(assembly, |constraint| {
removed_genericparams.contains(&constraint.owner)
})
}
pub(super) fn remove_orphan_methodspec(assembly: &mut CilAssembly, ctx: &DeletionContext) -> usize {
remove_orphan_entries::<MethodSpecRaw>(assembly, |spec| ctx.is_deleted(spec.method.token))
}
pub(super) fn remove_orphan_declsecurity(
assembly: &mut CilAssembly,
ctx: &DeletionContext,
) -> usize {
remove_orphan_entries::<DeclSecurityRaw>(assembly, |security| {
ctx.is_deleted(security.parent.token)
})
}
pub(super) fn remove_orphan_implmap(assembly: &mut CilAssembly, ctx: &DeletionContext) -> usize {
remove_orphan_entries::<ImplMapRaw>(assembly, |implmap| {
ctx.is_deleted(implmap.member_forwarded.token)
})
}
pub(super) fn remove_orphan_eventmap(assembly: &mut CilAssembly, ctx: &DeletionContext) -> usize {
remove_orphan_entries::<EventMapRaw>(assembly, |eventmap| {
let parent_token = Token::from_parts(TableId::TypeDef, eventmap.parent);
ctx.is_type_deleted(parent_token)
})
}
pub(super) fn remove_orphan_propertymap(
assembly: &mut CilAssembly,
ctx: &DeletionContext,
) -> usize {
remove_orphan_entries::<PropertyMapRaw>(assembly, |propmap| {
let parent_token = Token::from_parts(TableId::TypeDef, propmap.parent);
ctx.is_type_deleted(parent_token)
})
}
pub(super) fn remove_orphan_events(
assembly: &mut CilAssembly,
ctx: &DeletionContext,
) -> (usize, HashSet<Token>) {
let mut orphan_events: Vec<u32> = {
let view = assembly.view();
let Some(tables) = view.tables() else {
return (0, HashSet::new());
};
let Some(eventmap_table) = tables.table::<EventMapRaw>() else {
return (0, HashSet::new());
};
if tables.table::<EventRaw>().is_none() {
return (0, HashSet::new());
}
let map_count = eventmap_table.row_count;
let event_count = tables.table::<EventRaw>().map_or(0, |t| t.row_count);
let mut events = Vec::new();
for map_rid in 1..=map_count {
let Some(eventmap) = eventmap_table.get(map_rid) else {
continue;
};
let parent_token = Token::from_parts(TableId::TypeDef, eventmap.parent);
if !ctx.is_type_deleted(parent_token) {
continue;
}
let range = list_range(map_rid, map_count, event_count, |rid| {
eventmap_table.get(rid).map(|m| m.event_list)
});
events.extend(range);
}
events
};
orphan_events.sort_unstable_by(|a, b| b.cmp(a));
orphan_events.dedup();
let mut removed = 0;
let mut deleted_tokens = HashSet::new();
for rid in orphan_events {
if try_remove(assembly, TableId::Event, rid) {
removed += 1;
deleted_tokens.insert(Token::from_parts(TableId::Event, rid));
}
}
(removed, deleted_tokens)
}
pub(super) fn remove_orphan_properties(
assembly: &mut CilAssembly,
ctx: &DeletionContext,
) -> (usize, HashSet<Token>) {
let mut orphan_properties: Vec<u32> = {
let view = assembly.view();
let Some(tables) = view.tables() else {
return (0, HashSet::new());
};
let Some(propertymap_table) = tables.table::<PropertyMapRaw>() else {
return (0, HashSet::new());
};
if tables.table::<PropertyRaw>().is_none() {
return (0, HashSet::new());
}
let map_count = propertymap_table.row_count;
let property_count = tables.table::<PropertyRaw>().map_or(0, |t| t.row_count);
let mut props = Vec::new();
for map_rid in 1..=map_count {
let Some(propertymap) = propertymap_table.get(map_rid) else {
continue;
};
let parent_token = Token::from_parts(TableId::TypeDef, propertymap.parent);
if !ctx.is_type_deleted(parent_token) {
continue;
}
let range = list_range(map_rid, map_count, property_count, |rid| {
propertymap_table.get(rid).map(|m| m.property_list)
});
props.extend(range);
}
props
};
orphan_properties.sort_unstable_by(|a, b| b.cmp(a));
orphan_properties.dedup();
let mut removed = 0;
let mut deleted_tokens = HashSet::new();
for rid in orphan_properties {
if try_remove(assembly, TableId::Property, rid) {
removed += 1;
deleted_tokens.insert(Token::from_parts(TableId::Property, rid));
}
}
(removed, deleted_tokens)
}
pub(super) fn remove_orphan_standalonesigs(
assembly: &mut CilAssembly,
candidates: &BTreeSet<u32>,
) -> usize {
if candidates.is_empty() {
return 0;
}
let alive = collect_referenced_standalonesig_rids(assembly);
let mut removed = 0;
for &rid in candidates.iter().rev() {
if !alive.contains(&rid) && try_remove(assembly, TableId::StandAloneSig, rid) {
removed += 1;
}
}
removed
}
pub(super) fn remove_orphan_modulerefs(
assembly: &mut CilAssembly,
candidates: &BTreeSet<u32>,
) -> usize {
if candidates.is_empty() {
return 0;
}
let alive: HashSet<u32> = {
let view = assembly.view();
let Some(tables) = view.tables() else {
return 0;
};
let mut alive = HashSet::new();
if let Some(implmap_table) = tables.table::<ImplMapRaw>() {
for rid in 1..=implmap_table.row_count {
if assembly.changes().is_row_deleted(TableId::ImplMap, rid) {
continue;
}
if let Some(implmap) = implmap_table.get(rid) {
alive.insert(implmap.import_scope);
}
}
}
if let Some(typeref_table) = tables.table::<TypeRefRaw>() {
for rid in 1..=typeref_table.row_count {
if assembly.changes().is_row_deleted(TableId::TypeRef, rid) {
continue;
}
if let Some(typeref) = typeref_table.get(rid) {
if typeref.resolution_scope.tag == TableId::ModuleRef {
alive.insert(typeref.resolution_scope.row);
}
}
}
}
alive
};
let (removed, _) =
remove_candidates_not_alive(assembly, TableId::ModuleRef, candidates, &alive);
removed
}
pub(super) fn remove_orphan_assemblyrefs(
assembly: &mut CilAssembly,
candidates: &BTreeSet<u32>,
) -> usize {
if candidates.is_empty() {
return 0;
}
let alive: HashSet<u32> = {
let view = assembly.view();
let Some(tables) = view.tables() else {
return 0;
};
let mut alive = HashSet::new();
if let Some(typeref_table) = tables.table::<TypeRefRaw>() {
for rid in 1..=typeref_table.row_count {
if assembly.changes().is_row_deleted(TableId::TypeRef, rid) {
continue;
}
if let Some(typeref) = typeref_table.get(rid) {
if typeref.resolution_scope.tag == TableId::AssemblyRef {
alive.insert(typeref.resolution_scope.row);
}
}
}
}
alive
};
let (removed, _) =
remove_candidates_not_alive(assembly, TableId::AssemblyRef, candidates, &alive);
removed
}
pub(super) fn remove_orphan_exportedtypes(assembly: &mut CilAssembly) -> (usize, HashSet<u32>) {
let orphan_rids: Vec<u32> = {
let view = assembly.view();
let Some(tables) = view.tables() else {
return (0, HashSet::new());
};
let Some(table) = tables.table::<ExportedTypeRaw>() else {
return (0, HashSet::new());
};
(1..=table.row_count)
.filter_map(|rid| {
let entry = table.get(rid)?;
if entry.implementation.row == 0 {
return None;
}
if assembly
.changes()
.is_row_deleted(entry.implementation.tag, entry.implementation.row)
{
Some(rid)
} else {
None
}
})
.collect()
};
let mut deleted_rids = HashSet::new();
let mut removed = 0;
for rid in orphan_rids.into_iter().rev() {
if try_remove(assembly, TableId::ExportedType, rid) {
removed += 1;
deleted_rids.insert(rid);
}
}
(removed, deleted_rids)
}
pub(super) fn remove_orphan_manifestresources(assembly: &mut CilAssembly) -> (usize, HashSet<u32>) {
let orphan_rids: Vec<u32> = {
let view = assembly.view();
let Some(tables) = view.tables() else {
return (0, HashSet::new());
};
let Some(table) = tables.table::<ManifestResourceRaw>() else {
return (0, HashSet::new());
};
(1..=table.row_count)
.filter_map(|rid| {
let entry = table.get(rid)?;
if entry.implementation.row == 0 {
return None;
}
if assembly
.changes()
.is_row_deleted(entry.implementation.tag, entry.implementation.row)
{
Some(rid)
} else {
None
}
})
.collect()
};
let mut deleted_rids = HashSet::new();
let mut removed = 0;
for rid in orphan_rids.into_iter().rev() {
if try_remove(assembly, TableId::ManifestResource, rid) {
removed += 1;
deleted_rids.insert(rid);
}
}
(removed, deleted_rids)
}
pub(super) fn remove_orphan_files(assembly: &mut CilAssembly, candidates: &BTreeSet<u32>) -> usize {
if candidates.is_empty() {
return 0;
}
let alive: HashSet<u32> = {
let view = assembly.view();
let Some(tables) = view.tables() else {
return 0;
};
let mut alive = HashSet::new();
if let Some(table) = tables.table::<ExportedTypeRaw>() {
for rid in 1..=table.row_count {
if assembly
.changes()
.is_row_deleted(TableId::ExportedType, rid)
{
continue;
}
if let Some(entry) = table.get(rid) {
if entry.implementation.token.is_table(TableId::File) {
alive.insert(entry.implementation.token.row());
}
}
}
}
if let Some(table) = tables.table::<ManifestResourceRaw>() {
for rid in 1..=table.row_count {
if assembly
.changes()
.is_row_deleted(TableId::ManifestResource, rid)
{
continue;
}
if let Some(entry) = table.get(rid) {
if entry.implementation.token.is_table(TableId::File) {
alive.insert(entry.implementation.token.row());
}
}
}
}
alive
};
let (removed, _) = remove_candidates_not_alive(assembly, TableId::File, candidates, &alive);
removed
}
pub(super) fn remove_type_dependents(
assembly: &mut CilAssembly,
ctx: &DeletionContext,
) -> CleanupStats {
let mut stats = CleanupStats::new();
stats.add(
TableId::NestedClass,
remove_orphan_nestedclass(assembly, ctx),
);
stats.add(
TableId::InterfaceImpl,
remove_orphan_interfaceimpl(assembly, ctx),
);
stats.add(
TableId::CustomAttribute,
remove_orphan_attributes(assembly, ctx),
);
stats.add(
TableId::ClassLayout,
remove_orphan_classlayouts(assembly, ctx),
);
stats.add(TableId::EventMap, remove_orphan_eventmap(assembly, ctx));
stats.add(
TableId::PropertyMap,
remove_orphan_propertymap(assembly, ctx),
);
let (events_removed, deleted_events) = remove_orphan_events(assembly, ctx);
stats.add(TableId::Event, events_removed);
let (properties_removed, deleted_properties) = remove_orphan_properties(assembly, ctx);
stats.add(TableId::Property, properties_removed);
stats.add(
TableId::DeclSecurity,
remove_orphan_declsecurity(assembly, ctx),
);
stats.add(TableId::MethodImpl, remove_orphan_methodimpl(assembly, ctx));
stats.add(
TableId::MethodSemantics,
remove_orphan_methodsemantics(assembly, ctx, &deleted_events, &deleted_properties),
);
let (gp, gp_rids) = remove_orphan_genericparam(assembly, ctx);
stats.add(TableId::GenericParam, gp);
stats.add(
TableId::GenericParamConstraint,
remove_orphan_genericparamconstraint(assembly, &gp_rids),
);
stats
}
pub(super) fn remove_parent_child_dependents(
assembly: &mut CilAssembly,
ctx: &DeletionContext,
_pre_refs: &PreDeletionRefs,
) -> CleanupStats {
let mut stats = CleanupStats::new();
stats.add(TableId::Param, remove_orphan_params(assembly, ctx));
stats.add(
TableId::CustomAttribute,
remove_orphan_attributes(assembly, ctx),
);
stats.add(
TableId::ClassLayout,
remove_orphan_classlayouts(assembly, ctx),
);
stats.add(
TableId::NestedClass,
remove_orphan_nestedclass(assembly, ctx),
);
stats.add(
TableId::InterfaceImpl,
remove_orphan_interfaceimpl(assembly, ctx),
);
stats.add(TableId::EventMap, remove_orphan_eventmap(assembly, ctx));
stats.add(
TableId::PropertyMap,
remove_orphan_propertymap(assembly, ctx),
);
let (events_removed, deleted_events) = remove_orphan_events(assembly, ctx);
stats.add(TableId::Event, events_removed);
let (properties_removed, deleted_properties) = remove_orphan_properties(assembly, ctx);
stats.add(TableId::Property, properties_removed);
stats.add(TableId::MethodImpl, remove_orphan_methodimpl(assembly, ctx));
stats.add(
TableId::MethodSemantics,
remove_orphan_methodsemantics(assembly, ctx, &deleted_events, &deleted_properties),
);
stats.add(TableId::ImplMap, remove_orphan_implmap(assembly, ctx));
stats.add(
TableId::DeclSecurity,
remove_orphan_declsecurity(assembly, ctx),
);
stats.add(TableId::MethodSpec, remove_orphan_methodspec(assembly, ctx));
{
let all_sas_rids: BTreeSet<u32> = {
let view = assembly.view();
view.tables()
.and_then(|t| t.table::<StandAloneSigRaw>())
.map(|table| {
(1..=table.row_count)
.filter(|&rid| {
!assembly
.changes()
.is_row_deleted(TableId::StandAloneSig, rid)
})
.collect()
})
.unwrap_or_default()
};
stats.add(
TableId::StandAloneSig,
remove_orphan_standalonesigs(assembly, &all_sas_rids),
);
}
stats.add(TableId::FieldRVA, remove_orphan_fieldrvas(assembly, ctx));
stats.add(
TableId::FieldLayout,
remove_orphan_fieldlayout(assembly, ctx),
);
stats.add(
TableId::FieldMarshal,
remove_orphan_fieldmarshal(assembly, ctx),
);
stats.add(TableId::Constant, remove_orphan_constant(assembly, ctx));
let (genericparams, removed_gp_rids) = remove_orphan_genericparam(assembly, ctx);
stats.add(TableId::GenericParam, genericparams);
stats.add(
TableId::GenericParamConstraint,
remove_orphan_genericparamconstraint(assembly, &removed_gp_rids),
);
stats
}
pub(super) fn cascade_reference_cleanup(
assembly: &mut CilAssembly,
pre_refs: &PreDeletionRefs,
body_tokens: &HashSet<Token>,
) -> CleanupStats {
let mut stats = CleanupStats::new();
let mut memberref_candidates: BTreeSet<u32> = pre_refs
.il_tokens
.iter()
.filter(|t| t.is_table(TableId::MemberRef))
.map(|t| t.row())
.collect();
{
let view = assembly.view();
if let Some(tables) = view.tables() {
if let Some(memberref_table) = tables.table::<MemberRefRaw>() {
for memberref in memberref_table {
if !assembly
.changes()
.is_row_deleted(TableId::MemberRef, memberref.rid)
{
memberref_candidates.insert(memberref.rid);
}
}
}
}
}
let (memberrefs_removed, deleted_memberref_rids) =
remove_unreferenced_memberrefs(assembly, &memberref_candidates, body_tokens);
stats.add(TableId::MemberRef, memberrefs_removed);
let mut typespec_candidates: BTreeSet<u32> = pre_refs
.il_tokens
.iter()
.filter(|t| t.is_table(TableId::TypeSpec))
.map(|t| t.row())
.collect();
{
let view = assembly.view();
if let Some(tables) = view.tables() {
if let Some(typespec_table) = tables.table::<crate::metadata::tables::TypeSpecRaw>() {
for typespec in typespec_table {
if !assembly
.changes()
.is_row_deleted(TableId::TypeSpec, typespec.rid)
{
typespec_candidates.insert(typespec.rid);
}
}
}
}
}
let (typespecs_removed, _deleted_typespec_rids) =
remove_unreferenced_typespecs(assembly, &typespec_candidates, body_tokens);
stats.add(TableId::TypeSpec, typespecs_removed);
let mut typeref_candidates: BTreeSet<u32> = pre_refs
.il_tokens
.iter()
.filter(|t| t.is_table(TableId::TypeRef))
.map(|t| t.row())
.chain(pre_refs.typeref_rids.iter().copied())
.collect();
{
let view = assembly.view();
if let Some(tables) = view.tables() {
if let Some(memberref_table) = tables.table::<MemberRefRaw>() {
for &memberref_rid in &deleted_memberref_rids {
if let Some(memberref) = memberref_table.get(memberref_rid) {
if memberref.class.token.is_table(TableId::TypeRef) {
typeref_candidates.insert(memberref.class.token.row());
}
}
}
}
}
}
typeref_candidates.extend(collect_typerefs_from_deleted_memberref_sigs(
assembly,
&deleted_memberref_rids,
));
{
let view = assembly.view();
if let Some(tables) = view.tables() {
if let Some(typeref_table) = tables.table::<TypeRefRaw>() {
for typeref in typeref_table {
if !assembly
.changes()
.is_row_deleted(TableId::TypeRef, typeref.rid)
{
typeref_candidates.insert(typeref.rid);
}
}
}
}
}
let (typerefs_removed, deleted_typeref_rids) =
remove_unreferenced_typerefs(assembly, &typeref_candidates, body_tokens);
stats.add(TableId::TypeRef, typerefs_removed);
let mut moduleref_candidates = BTreeSet::new();
let mut assemblyref_candidates = BTreeSet::new();
{
let view = assembly.view();
if let Some(tables) = view.tables() {
if let Some(typeref_table) = tables.table::<TypeRefRaw>() {
for &typeref_rid in &deleted_typeref_rids {
if let Some(typeref) = typeref_table.get(typeref_rid) {
match typeref.resolution_scope.tag {
TableId::AssemblyRef => {
assemblyref_candidates.insert(typeref.resolution_scope.row);
}
TableId::ModuleRef => {
moduleref_candidates.insert(typeref.resolution_scope.row);
}
_ => {}
}
}
}
}
if let Some(implmap_table) = tables.table::<ImplMapRaw>() {
for implmap in implmap_table {
if assembly
.changes()
.is_row_deleted(TableId::ImplMap, implmap.rid)
{
moduleref_candidates.insert(implmap.import_scope);
}
}
}
}
}
{
let view = assembly.view();
if let Some(tables) = view.tables() {
if let Some(table) = tables.table::<crate::metadata::tables::ModuleRefRaw>() {
for row in table {
if !assembly
.changes()
.is_row_deleted(TableId::ModuleRef, row.rid)
{
moduleref_candidates.insert(row.rid);
}
}
}
if let Some(table) = tables.table::<crate::metadata::tables::AssemblyRefRaw>() {
for row in table {
if !assembly
.changes()
.is_row_deleted(TableId::AssemblyRef, row.rid)
{
assemblyref_candidates.insert(row.rid);
}
}
}
}
}
stats.add(
TableId::ModuleRef,
remove_orphan_modulerefs(assembly, &moduleref_candidates),
);
stats.add(
TableId::AssemblyRef,
remove_orphan_assemblyrefs(assembly, &assemblyref_candidates),
);
let (exportedtypes_removed, deleted_exportedtype_rids) = remove_orphan_exportedtypes(assembly);
stats.add(TableId::ExportedType, exportedtypes_removed);
let (manifestresources_removed, deleted_manifestresource_rids) =
remove_orphan_manifestresources(assembly);
stats.add(TableId::ManifestResource, manifestresources_removed);
let mut file_candidates = BTreeSet::new();
{
let view = assembly.view();
if let Some(tables) = view.tables() {
if let Some(table) = tables.table::<ExportedTypeRaw>() {
for &rid in &deleted_exportedtype_rids {
if let Some(entry) = table.get(rid) {
if entry.implementation.token.is_table(TableId::File) {
file_candidates.insert(entry.implementation.token.row());
}
}
}
}
if let Some(table) = tables.table::<ManifestResourceRaw>() {
for &rid in &deleted_manifestresource_rids {
if let Some(entry) = table.get(rid) {
if entry.implementation.token.is_table(TableId::File) {
file_candidates.insert(entry.implementation.token.row());
}
}
}
}
}
}
stats.add(
TableId::File,
remove_orphan_files(assembly, &file_candidates),
);
stats
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use crate::{
cilassembly::cleanup::orphans::DeletionContext,
metadata::{tables::TableId, token::Token},
};
#[test]
fn test_deletion_context_creation() {
let types = HashSet::from([Token::from_parts(TableId::TypeDef, 1)]);
let methods = HashSet::from([Token::from_parts(TableId::MethodDef, 2)]);
let fields = HashSet::from([Token::from_parts(TableId::Field, 3)]);
let ctx = DeletionContext::new(&types, &methods, &fields);
assert!(ctx.is_type_deleted(Token::from_parts(TableId::TypeDef, 1)));
assert!(ctx.is_method_deleted(Token::from_parts(TableId::MethodDef, 2)));
assert!(ctx.is_field_deleted(Token::from_parts(TableId::Field, 3)));
assert!(!ctx.is_type_deleted(Token::from_parts(TableId::TypeDef, 99)));
}
#[test]
fn test_deletion_context_is_deleted() {
let types = HashSet::from([Token::from_parts(TableId::TypeDef, 1)]);
let empty_methods = HashSet::new();
let empty_fields = HashSet::new();
let ctx = DeletionContext::new(&types, &empty_methods, &empty_fields);
assert!(ctx.is_deleted(Token::from_parts(TableId::TypeDef, 1)));
assert!(!ctx.is_deleted(Token::from_parts(TableId::TypeDef, 2)));
}
}