use std::collections::{BTreeSet, HashSet};
use crate::{
cilassembly::{
cleanup::{
compaction::mark_unreferenced_heap_entries,
orphans::{remove_all_orphans, OrphanContext},
CleanupRequest, CleanupStats,
},
CilAssembly,
},
metadata::{
tables::{FieldRaw, MethodDefRaw, TableId, TypeDefRaw},
token::Token,
},
Result,
};
pub fn execute_cleanup(
assembly: &mut CilAssembly,
request: &CleanupRequest,
) -> Result<CleanupStats> {
let mut stats = CleanupStats::new();
if request.is_empty() {
return Ok(stats);
}
let (type_methods, type_fields) = expand_type_members(assembly, request);
let mut all_methods: BTreeSet<Token> = request.methods().copied().collect();
all_methods.extend(type_methods);
let mut all_fields: BTreeSet<Token> = request.fields().copied().collect();
all_fields.extend(type_fields);
let mut removed_types: HashSet<Token> = HashSet::new();
let mut removed_methods: HashSet<Token> = HashSet::new();
let mut removed_fields: HashSet<Token> = HashSet::new();
for method_token in all_methods.iter().rev() {
if assembly
.table_row_remove(TableId::MethodDef, method_token.row())
.is_ok()
{
removed_methods.insert(*method_token);
stats.methods_removed += 1;
}
}
for spec_token in request.methodspecs() {
if assembly
.table_row_remove(TableId::MethodSpec, spec_token.row())
.is_ok()
{
stats.methodspecs_removed += 1;
}
}
for field_token in all_fields.iter().rev() {
if assembly
.table_row_remove(TableId::Field, field_token.row())
.is_ok()
{
removed_fields.insert(*field_token);
stats.fields_removed += 1;
}
}
for type_token in request.types() {
if assembly
.table_row_remove(TableId::TypeDef, type_token.row())
.is_ok()
{
removed_types.insert(*type_token);
stats.types_removed += 1;
}
}
for attr_token in request.attributes() {
if assembly
.table_row_remove(TableId::CustomAttribute, attr_token.row())
.is_ok()
{
stats.attributes_removed += 1;
}
}
if request.remove_orphans() {
let ctx = OrphanContext::new(&removed_types, &removed_methods, &removed_fields);
let orphan_stats = remove_all_orphans(assembly, &ctx);
stats.merge(&orphan_stats);
}
if request.remove_empty_types() {
let empty_removed = remove_empty_types(assembly);
stats.types_removed += empty_removed;
}
let compaction_stats = mark_unreferenced_heap_entries(assembly)?;
stats.blobs_compacted = compaction_stats.blobs;
stats.guids_compacted = compaction_stats.guids;
stats.strings_compacted = compaction_stats.strings;
stats.sections_excluded = request.excluded_sections().len();
Ok(stats)
}
fn expand_type_members(
assembly: &CilAssembly,
request: &CleanupRequest,
) -> (HashSet<Token>, HashSet<Token>) {
let mut methods = HashSet::new();
let mut fields = HashSet::new();
let view = assembly.view();
let Some(tables) = view.tables() else {
return (methods, fields);
};
let Some(typedef_table) = tables.table::<TypeDefRaw>() else {
return (methods, fields);
};
let methoddef_table = tables.table::<MethodDefRaw>();
let field_table = tables.table::<FieldRaw>();
let type_count = typedef_table.row_count;
for type_token in request.types() {
let type_rid = type_token.row();
let Some(typedef) = typedef_table.get(type_rid) else {
continue;
};
if let Some(methoddef_table) = &methoddef_table {
let method_start = typedef.method_list;
let method_end = if type_rid < type_count {
typedef_table
.get(type_rid + 1)
.map_or(methoddef_table.row_count + 1, |t| t.method_list)
} else {
methoddef_table.row_count + 1
};
for method_rid in method_start..method_end {
methods.insert(Token::from_parts(TableId::MethodDef, method_rid));
}
}
if let Some(field_table) = &field_table {
let field_start = typedef.field_list;
let field_end = if type_rid < type_count {
typedef_table
.get(type_rid + 1)
.map_or(field_table.row_count + 1, |t| t.field_list)
} else {
field_table.row_count + 1
};
for field_rid in field_start..field_end {
fields.insert(Token::from_parts(TableId::Field, field_rid));
}
}
}
(methods, fields)
}
fn remove_empty_types(assembly: &mut CilAssembly) -> usize {
let empty_types: Vec<u32> = {
let view = assembly.view();
let Some(tables) = view.tables() else {
return 0;
};
let Some(typedef_table) = tables.table::<TypeDefRaw>() else {
return 0;
};
let methoddef_count = tables.table::<MethodDefRaw>().map_or(0, |t| t.row_count);
let field_count = tables.table::<FieldRaw>().map_or(0, |t| t.row_count);
let type_count = typedef_table.row_count;
let mut empty = Vec::new();
for type_rid in 1..=type_count {
let Some(typedef) = typedef_table.get(type_rid) else {
continue;
};
if type_rid == 1 {
continue;
}
let method_start = typedef.method_list;
let method_end = if type_rid < type_count {
typedef_table
.get(type_rid + 1)
.map_or(methoddef_count + 1, |t| t.method_list)
} else {
methoddef_count + 1
};
let method_count = method_end.saturating_sub(method_start);
let field_start = typedef.field_list;
let field_end = if type_rid < type_count {
typedef_table
.get(type_rid + 1)
.map_or(field_count + 1, |t| t.field_list)
} else {
field_count + 1
};
let field_count_type = field_end.saturating_sub(field_start);
if method_count == 0 && field_count_type == 0 {
empty.push(type_rid);
}
}
empty
};
let mut removed = 0;
for rid in empty_types.into_iter().rev() {
if assembly.table_row_remove(TableId::TypeDef, rid).is_ok() {
removed += 1;
}
}
removed
}
#[cfg(test)]
mod tests {
use crate::{
cilassembly::cleanup::CleanupRequest,
metadata::{tables::TableId, token::Token},
};
#[test]
fn test_execute_cleanup_empty_request() {
let request = CleanupRequest::new();
assert!(request.is_empty());
}
#[test]
fn test_cleanup_request_with_types() {
let mut request = CleanupRequest::new();
request.add_type(Token::from_parts(TableId::TypeDef, 5));
request.add_method(Token::from_parts(TableId::MethodDef, 10));
assert!(!request.is_empty());
assert_eq!(request.types_len(), 1);
assert_eq!(request.methods_len(), 1);
}
}