use std::collections::{BTreeSet, HashSet};
use crate::metadata::token::Token;
#[derive(Debug, Clone)]
pub struct CleanupRequest {
types: BTreeSet<Token>,
methods: BTreeSet<Token>,
methodspecs: BTreeSet<Token>,
fields: BTreeSet<Token>,
attributes: BTreeSet<Token>,
excluded_sections: HashSet<String>,
remove_orphans: bool,
remove_empty_types: bool,
}
impl Default for CleanupRequest {
fn default() -> Self {
Self {
types: BTreeSet::new(),
methods: BTreeSet::new(),
methodspecs: BTreeSet::new(),
fields: BTreeSet::new(),
attributes: BTreeSet::new(),
excluded_sections: HashSet::new(),
remove_orphans: true,
remove_empty_types: true,
}
}
}
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 {
types: BTreeSet::new(),
methods: BTreeSet::new(),
methodspecs: BTreeSet::new(),
fields: BTreeSet::new(),
attributes: BTreeSet::new(),
excluded_sections: HashSet::new(),
remove_orphans,
remove_empty_types,
}
}
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 {
self.fields.insert(token);
self
}
pub fn add_fields(&mut self, tokens: impl IntoIterator<Item = Token>) -> &mut Self {
self.fields.extend(tokens);
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 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
}
#[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()
}
#[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()
}
#[must_use]
pub fn is_deleted(&self, token: Token) -> bool {
self.types.contains(&token)
|| self.methods.contains(&token)
|| self.methodspecs.contains(&token)
|| self.fields.contains(&token)
|| self.attributes.contains(&token)
}
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.excluded_sections
.extend(other.excluded_sections.iter().cloned());
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
}
}
#[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);
}
}