use std::collections::{BTreeSet, HashSet};
use crate::metadata::{tables::TableId, token::Token};
#[derive(Debug, Clone)]
pub struct CleanupRequest {
types: BTreeSet<Token>,
methods: BTreeSet<Token>,
methodspecs: BTreeSet<Token>,
fields: BTreeSet<Token>,
attributes: BTreeSet<Token>,
assemblyrefs: BTreeSet<Token>,
modulerefs: BTreeSet<Token>,
manifest_resources: BTreeSet<Token>,
rewrite_orphaned_tokens: HashSet<Token>,
excluded_sections: HashSet<String>,
remove_orphans: bool,
remove_empty_types: bool,
protected_tokens: HashSet<Token>,
}
impl Default for CleanupRequest {
fn default() -> Self {
Self {
types: BTreeSet::new(),
methods: BTreeSet::new(),
methodspecs: BTreeSet::new(),
fields: BTreeSet::new(),
attributes: BTreeSet::new(),
assemblyrefs: BTreeSet::new(),
modulerefs: BTreeSet::new(),
manifest_resources: BTreeSet::new(),
rewrite_orphaned_tokens: HashSet::new(),
excluded_sections: HashSet::new(),
remove_orphans: true,
remove_empty_types: true,
protected_tokens: HashSet::new(),
}
}
}
impl CleanupRequest {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_settings(remove_orphans: bool, remove_empty_types: bool) -> Self {
Self {
remove_orphans,
remove_empty_types,
..Self::default()
}
}
pub fn add_type(&mut self, token: Token) -> &mut Self {
self.types.insert(token);
self
}
pub fn add_types(&mut self, tokens: impl IntoIterator<Item = Token>) -> &mut Self {
self.types.extend(tokens);
self
}
pub fn types(&self) -> impl Iterator<Item = &Token> + '_ {
self.types.iter().rev()
}
#[must_use]
pub fn types_len(&self) -> usize {
self.types.len()
}
pub fn add_method(&mut self, token: Token) -> &mut Self {
self.methods.insert(token);
self
}
pub fn add_methods(&mut self, tokens: impl IntoIterator<Item = Token>) -> &mut Self {
self.methods.extend(tokens);
self
}
pub fn methods(&self) -> impl Iterator<Item = &Token> + '_ {
self.methods.iter().rev()
}
#[must_use]
pub fn methods_len(&self) -> usize {
self.methods.len()
}
pub fn add_methodspec(&mut self, token: Token) -> &mut Self {
self.methodspecs.insert(token);
self
}
pub fn add_methodspecs(&mut self, tokens: impl IntoIterator<Item = Token>) -> &mut Self {
self.methodspecs.extend(tokens);
self
}
pub fn methodspecs(&self) -> impl Iterator<Item = &Token> + '_ {
self.methodspecs.iter().rev()
}
#[must_use]
pub fn methodspecs_len(&self) -> usize {
self.methodspecs.len()
}
pub fn add_field(&mut self, token: Token) -> &mut Self {
if !token.is_table(TableId::Field) {
log::warn!(
"CleanupRequest::add_field rejected non-Field token {token:?} \
(only FieldDef rows are deletable here)"
);
return self;
}
self.fields.insert(token);
self
}
pub fn add_fields(&mut self, tokens: impl IntoIterator<Item = Token>) -> &mut Self {
for tok in tokens {
self.add_field(tok);
}
self
}
pub fn fields(&self) -> impl Iterator<Item = &Token> + '_ {
self.fields.iter().rev()
}
#[must_use]
pub fn fields_len(&self) -> usize {
self.fields.len()
}
pub fn add_attribute(&mut self, token: Token) -> &mut Self {
self.attributes.insert(token);
self
}
pub fn add_attributes(&mut self, tokens: impl IntoIterator<Item = Token>) -> &mut Self {
self.attributes.extend(tokens);
self
}
pub fn attributes(&self) -> impl Iterator<Item = &Token> + '_ {
self.attributes.iter().rev()
}
#[must_use]
pub fn attributes_len(&self) -> usize {
self.attributes.len()
}
pub fn add_assemblyref(&mut self, token: Token) -> &mut Self {
self.assemblyrefs.insert(token);
self
}
pub fn add_assemblyrefs(&mut self, tokens: impl IntoIterator<Item = Token>) -> &mut Self {
self.assemblyrefs.extend(tokens);
self
}
pub fn assemblyrefs(&self) -> impl Iterator<Item = &Token> + '_ {
self.assemblyrefs.iter().rev()
}
#[must_use]
pub fn assemblyrefs_len(&self) -> usize {
self.assemblyrefs.len()
}
pub fn add_moduleref(&mut self, token: Token) -> &mut Self {
self.modulerefs.insert(token);
self
}
pub fn add_modulerefs(&mut self, tokens: impl IntoIterator<Item = Token>) -> &mut Self {
self.modulerefs.extend(tokens);
self
}
pub fn modulerefs(&self) -> impl Iterator<Item = &Token> + '_ {
self.modulerefs.iter().rev()
}
#[must_use]
pub fn modulerefs_len(&self) -> usize {
self.modulerefs.len()
}
pub fn add_manifest_resource(&mut self, token: Token) -> &mut Self {
self.manifest_resources.insert(token);
self
}
pub fn add_manifest_resources(&mut self, tokens: impl IntoIterator<Item = Token>) -> &mut Self {
self.manifest_resources.extend(tokens);
self
}
pub fn manifest_resources(&self) -> impl Iterator<Item = &Token> + '_ {
self.manifest_resources.iter().rev()
}
#[must_use]
pub fn manifest_resources_len(&self) -> usize {
self.manifest_resources.len()
}
pub fn add_rewrite_orphaned_tokens(
&mut self,
tokens: impl IntoIterator<Item = Token>,
) -> &mut Self {
self.rewrite_orphaned_tokens.extend(tokens);
self
}
#[must_use]
pub fn rewrite_orphaned_tokens(&self) -> &HashSet<Token> {
&self.rewrite_orphaned_tokens
}
pub fn exclude_section(&mut self, name: impl Into<String>) -> &mut Self {
self.excluded_sections.insert(name.into());
self
}
pub fn exclude_sections(&mut self, names: impl IntoIterator<Item = String>) -> &mut Self {
self.excluded_sections.extend(names);
self
}
#[must_use]
pub fn excluded_sections(&self) -> &HashSet<String> {
&self.excluded_sections
}
pub fn set_remove_orphans(&mut self, remove: bool) -> &mut Self {
self.remove_orphans = remove;
self
}
#[must_use]
pub fn remove_orphans(&self) -> bool {
self.remove_orphans
}
pub fn set_remove_empty_types(&mut self, remove: bool) -> &mut Self {
self.remove_empty_types = remove;
self
}
#[must_use]
pub fn remove_empty_types(&self) -> bool {
self.remove_empty_types
}
pub fn protect_token(&mut self, token: Token) -> &mut Self {
self.protected_tokens.insert(token);
self
}
pub fn protect_tokens(&mut self, tokens: impl IntoIterator<Item = Token>) -> &mut Self {
self.protected_tokens.extend(tokens);
self
}
#[must_use]
pub fn is_protected(&self, token: Token) -> bool {
self.protected_tokens.contains(&token)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.types.is_empty()
&& self.methods.is_empty()
&& self.methodspecs.is_empty()
&& self.fields.is_empty()
&& self.attributes.is_empty()
&& self.assemblyrefs.is_empty()
&& self.modulerefs.is_empty()
&& self.manifest_resources.is_empty()
&& self.excluded_sections.is_empty()
&& self.rewrite_orphaned_tokens.is_empty()
}
#[must_use]
pub fn has_deletions(&self) -> bool {
!self.is_empty()
}
#[must_use]
pub fn deletion_count(&self) -> usize {
self.types.len()
+ self.methods.len()
+ self.methodspecs.len()
+ self.fields.len()
+ self.attributes.len()
+ self.assemblyrefs.len()
+ self.modulerefs.len()
+ self.manifest_resources.len()
}
#[must_use]
pub fn is_deleted(&self, token: Token) -> bool {
if self.protected_tokens.contains(&token) {
return false;
}
self.types.contains(&token)
|| self.methods.contains(&token)
|| self.methodspecs.contains(&token)
|| self.fields.contains(&token)
|| self.attributes.contains(&token)
|| self.assemblyrefs.contains(&token)
|| self.modulerefs.contains(&token)
|| self.manifest_resources.contains(&token)
}
pub fn add_methods_from(&mut self, tokens: &boxcar::Vec<Token>) -> &mut Self {
for (_, token) in tokens {
self.methods.insert(*token);
}
self
}
pub fn add_types_from(&mut self, tokens: &boxcar::Vec<Token>) -> &mut Self {
for (_, token) in tokens {
self.types.insert(*token);
}
self
}
pub fn add_fields_from(&mut self, tokens: &boxcar::Vec<Token>) -> &mut Self {
for (_, token) in tokens {
self.add_field(*token);
}
self
}
pub fn add_attributes_from(&mut self, tokens: &boxcar::Vec<Token>) -> &mut Self {
for (_, token) in tokens {
self.attributes.insert(*token);
}
self
}
pub fn merge(&mut self, other: &CleanupRequest) -> &mut Self {
self.types.extend(other.types.iter().copied());
self.methods.extend(other.methods.iter().copied());
self.methodspecs.extend(other.methodspecs.iter().copied());
self.fields.extend(other.fields.iter().copied());
self.attributes.extend(other.attributes.iter().copied());
self.assemblyrefs.extend(other.assemblyrefs.iter().copied());
self.modulerefs.extend(other.modulerefs.iter().copied());
self.manifest_resources
.extend(other.manifest_resources.iter().copied());
self.rewrite_orphaned_tokens
.extend(other.rewrite_orphaned_tokens.iter().copied());
self.excluded_sections
.extend(other.excluded_sections.iter().cloned());
self.protected_tokens
.extend(other.protected_tokens.iter().copied());
self
}
#[must_use]
pub fn all_tokens(&self) -> HashSet<Token> {
let mut all = HashSet::new();
all.extend(&self.types);
all.extend(&self.methods);
all.extend(&self.methodspecs);
all.extend(&self.fields);
all.extend(&self.attributes);
all.extend(&self.assemblyrefs);
all.extend(&self.modulerefs);
all.extend(&self.manifest_resources);
all
}
}
#[cfg(test)]
mod tests {
use crate::{
cilassembly::cleanup::CleanupRequest,
metadata::{tables::TableId, token::Token},
};
#[test]
fn test_cleanup_request_default() {
let request = CleanupRequest::default();
assert!(request.is_empty());
assert!(!request.has_deletions());
assert_eq!(request.deletion_count(), 0);
assert!(request.remove_orphans());
assert!(request.remove_empty_types());
}
#[test]
fn test_add_type() {
let mut request = CleanupRequest::new();
let token = Token::from_parts(TableId::TypeDef, 5);
request.add_type(token);
assert!(!request.is_empty());
assert!(request.has_deletions());
assert_eq!(request.deletion_count(), 1);
assert!(request.is_deleted(token));
assert_eq!(request.types_len(), 1);
}
#[test]
fn test_add_method() {
let mut request = CleanupRequest::new();
let token = Token::from_parts(TableId::MethodDef, 10);
request.add_method(token);
assert!(request.is_deleted(token));
assert_eq!(request.methods_len(), 1);
}
#[test]
fn test_exclude_section() {
let mut request = CleanupRequest::new();
request.exclude_section(".confuser");
request.exclude_section(".packed");
assert_eq!(request.excluded_sections().len(), 2);
assert!(request.excluded_sections().contains(".confuser"));
assert!(request.excluded_sections().contains(".packed"));
}
#[test]
fn test_merge() {
let mut request1 = CleanupRequest::new();
request1.add_type(Token::from_parts(TableId::TypeDef, 1));
let mut request2 = CleanupRequest::new();
request2.add_method(Token::from_parts(TableId::MethodDef, 2));
request2.exclude_section(".test");
request1.merge(&request2);
assert_eq!(request1.deletion_count(), 2);
assert!(request1.excluded_sections().contains(".test"));
}
#[test]
fn test_with_settings() {
let request = CleanupRequest::with_settings(false, false);
assert!(!request.remove_orphans());
assert!(!request.remove_empty_types());
}
#[test]
fn test_method_chaining() {
let token1 = Token::from_parts(TableId::TypeDef, 1);
let token2 = Token::from_parts(TableId::MethodDef, 2);
let token3 = Token::from_parts(TableId::Field, 3);
let mut request = CleanupRequest::new();
request
.add_type(token1)
.add_method(token2)
.add_field(token3)
.exclude_section(".data")
.set_remove_orphans(true);
assert_eq!(request.deletion_count(), 3);
assert!(request.excluded_sections().contains(".data"));
}
#[test]
fn test_types_iterator_descending_order() {
let mut request = CleanupRequest::new();
request.add_type(Token::from_parts(TableId::TypeDef, 1));
request.add_type(Token::from_parts(TableId::TypeDef, 5));
request.add_type(Token::from_parts(TableId::TypeDef, 3));
let tokens: Vec<&Token> = request.types().collect();
assert_eq!(tokens.len(), 3);
assert_eq!(tokens[0].row(), 5);
assert_eq!(tokens[1].row(), 3);
assert_eq!(tokens[2].row(), 1);
}
#[test]
fn test_methods_iterator_descending_order() {
let mut request = CleanupRequest::new();
request.add_method(Token::from_parts(TableId::MethodDef, 10));
request.add_method(Token::from_parts(TableId::MethodDef, 2));
request.add_method(Token::from_parts(TableId::MethodDef, 7));
let tokens: Vec<&Token> = request.methods().collect();
assert_eq!(tokens.len(), 3);
assert_eq!(tokens[0].row(), 10);
assert_eq!(tokens[1].row(), 7);
assert_eq!(tokens[2].row(), 2);
}
}