use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use crate::{
cilassembly::changes::{
hash_blob, hash_guid, hash_string, ChangeRef, ChangeRefKind, ChangeRefRc,
},
utils::compressed_uint_size,
};
#[derive(Debug, Clone)]
pub struct HeapChanges<T> {
appended: Vec<(T, ChangeRefRc)>,
modified_items: HashMap<u32, T>,
removed_indices: HashSet<u32>,
replacement_heap: Option<Vec<u8>>,
heap_kind: ChangeRefKind,
}
impl<T> HeapChanges<T> {
fn new(heap_kind: ChangeRefKind) -> Self {
Self {
appended: Vec::new(),
modified_items: HashMap::new(),
removed_indices: HashSet::new(),
replacement_heap: None,
heap_kind,
}
}
pub fn heap_kind(&self) -> ChangeRefKind {
self.heap_kind
}
pub fn additions_count(&self) -> usize {
self.appended.len()
}
pub fn has_additions(&self) -> bool {
!self.appended.is_empty()
}
pub fn modifications_count(&self) -> usize {
self.modified_items.len()
}
pub fn has_modifications(&self) -> bool {
!self.modified_items.is_empty()
}
pub fn removals_count(&self) -> usize {
self.removed_indices.len()
}
pub fn has_removals(&self) -> bool {
!self.removed_indices.is_empty()
}
pub fn has_changes(&self) -> bool {
self.has_additions()
|| self.has_modifications()
|| self.has_removals()
|| self.has_replacement()
}
pub fn has_replacement(&self) -> bool {
self.replacement_heap.is_some()
}
pub fn replace_heap(&mut self, heap_data: Vec<u8>) {
self.replacement_heap = Some(heap_data);
self.appended.clear();
self.modified_items.clear();
self.removed_indices.clear();
}
pub fn replacement_heap(&self) -> Option<&Vec<u8>> {
self.replacement_heap.as_ref()
}
pub fn add_modification(&mut self, index: u32, new_value: T) {
self.modified_items.insert(index, new_value);
}
pub fn add_removal(&mut self, index: u32) {
self.removed_indices.insert(index);
}
pub fn get_modification(&self, index: u32) -> Option<&T> {
self.modified_items.get(&index)
}
pub fn is_removed(&self, index: u32) -> bool {
self.removed_indices.contains(&index)
}
pub fn appended_iter(&self) -> impl Iterator<Item = &(T, ChangeRefRc)> {
self.appended.iter()
}
pub fn modified_items_iter(&self) -> impl Iterator<Item = (&u32, &T)> {
self.modified_items.iter()
}
pub fn removed_indices_iter(&self) -> impl Iterator<Item = &u32> {
self.removed_indices.iter()
}
pub fn mark_ref_removed(&mut self, change_ref: &ChangeRefRc) {
let pseudo_index = (change_ref.id() & 0xFFFF_FFFF) as u32 | 0x8000_0000;
self.removed_indices.insert(pseudo_index);
}
pub fn is_ref_removed(&self, change_ref: &ChangeRefRc) -> bool {
let pseudo_index = (change_ref.id() & 0xFFFF_FFFF) as u32 | 0x8000_0000;
self.removed_indices.contains(&pseudo_index)
}
pub fn mark_appended_for_removal(&mut self, index: u32) {
self.removed_indices.insert(index | 0x8000_0000);
}
pub fn is_appended_removed(&self, index: u32) -> bool {
self.removed_indices.contains(&(index | 0x8000_0000))
}
}
impl HeapChanges<String> {
pub fn new_strings() -> Self {
Self::new(ChangeRefKind::String)
}
pub fn new_userstrings() -> Self {
Self::new(ChangeRefKind::UserString)
}
pub fn append(&mut self, value: String) -> ChangeRefRc {
let content_hash = hash_string(&value);
let change_ref = Arc::new(ChangeRef::new_heap(self.heap_kind, content_hash));
self.appended.push((value, Arc::clone(&change_ref)));
change_ref
}
pub fn binary_string_heap_size(&self) -> usize {
self.appended.iter().map(|(s, _)| s.len() + 1).sum()
}
pub fn binary_userstring_heap_size(&self) -> usize {
self.appended
.iter()
.map(|(s, _)| {
let utf16_bytes = s.encode_utf16().count() * 2;
let total_length = utf16_bytes + 1;
#[allow(clippy::cast_possible_truncation)]
let compressed_length_size = compressed_uint_size(total_length) as usize;
compressed_length_size + total_length
})
.sum()
}
}
impl HeapChanges<Vec<u8>> {
pub fn new_blobs() -> Self {
Self::new(ChangeRefKind::Blob)
}
pub fn append(&mut self, value: Vec<u8>) -> ChangeRefRc {
let content_hash = hash_blob(&value);
let change_ref = Arc::new(ChangeRef::new_heap(self.heap_kind, content_hash));
self.appended.push((value, Arc::clone(&change_ref)));
change_ref
}
pub fn binary_blob_heap_size(&self) -> usize {
self.appended
.iter()
.map(|(blob, _)| {
let length = blob.len();
#[allow(clippy::cast_possible_truncation)]
let compressed_length_size = compressed_uint_size(length) as usize;
compressed_length_size + length
})
.sum()
}
}
impl HeapChanges<[u8; 16]> {
pub fn new_guids() -> Self {
Self::new(ChangeRefKind::Guid)
}
pub fn append(&mut self, value: [u8; 16]) -> ChangeRefRc {
let content_hash = hash_guid(&value);
let change_ref = Arc::new(ChangeRef::new_heap(self.heap_kind, content_hash));
self.appended.push((value, Arc::clone(&change_ref)));
change_ref
}
pub fn binary_guid_heap_size(&self) -> usize {
self.appended.len() * 16
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_heap_changes() {
let mut changes = HeapChanges::<String>::new_strings();
assert!(!changes.has_additions());
assert!(!changes.has_changes());
let ref1 = changes.append("hello".to_string());
let ref2 = changes.append("world".to_string());
assert!(changes.has_additions());
assert!(changes.has_changes());
assert_eq!(changes.additions_count(), 2);
assert_ne!(ref1.id(), ref2.id());
assert_eq!(changes.binary_string_heap_size(), 12);
}
#[test]
fn test_blob_heap_changes() {
let mut changes = HeapChanges::<Vec<u8>>::new_blobs();
let ref1 = changes.append(vec![1, 2, 3]);
let ref2 = changes.append(vec![4, 5, 6, 7]);
assert_eq!(changes.additions_count(), 2);
assert_ne!(ref1.id(), ref2.id());
assert_eq!(changes.binary_blob_heap_size(), 9);
}
#[test]
fn test_guid_heap_changes() {
let mut changes = HeapChanges::<[u8; 16]>::new_guids();
let guid1 = [0u8; 16];
let guid2 = [1u8; 16];
let ref1 = changes.append(guid1);
let ref2 = changes.append(guid2);
assert_eq!(changes.additions_count(), 2);
assert_ne!(ref1.id(), ref2.id());
assert_eq!(changes.binary_guid_heap_size(), 32);
}
#[test]
fn test_userstring_heap_changes() {
let mut changes = HeapChanges::<String>::new_userstrings();
assert_eq!(changes.heap_kind(), ChangeRefKind::UserString);
let _ref1 = changes.append("test".to_string());
assert_eq!(changes.additions_count(), 1);
assert_eq!(changes.binary_userstring_heap_size(), 10);
}
#[test]
fn test_modifications() {
let mut changes = HeapChanges::<String>::new_strings();
assert!(!changes.has_modifications());
changes.add_modification(50, "modified".to_string());
assert!(changes.has_modifications());
assert_eq!(changes.modifications_count(), 1);
assert_eq!(changes.get_modification(50), Some(&"modified".to_string()));
assert_eq!(changes.get_modification(99), None);
}
#[test]
fn test_removals() {
let mut changes = HeapChanges::<String>::new_strings();
assert!(!changes.has_removals());
changes.add_removal(25);
assert!(changes.has_removals());
assert_eq!(changes.removals_count(), 1);
assert!(changes.is_removed(25));
assert!(!changes.is_removed(30));
}
#[test]
fn test_replacement_heap() {
let mut changes = HeapChanges::<String>::new_strings();
let _ref1 = changes.append("will be cleared".to_string());
assert_eq!(changes.additions_count(), 1);
changes.replace_heap(vec![0, b'h', b'i', 0]);
assert!(changes.has_replacement());
assert_eq!(changes.additions_count(), 0); assert_eq!(changes.replacement_heap().unwrap(), &vec![0, b'h', b'i', 0]);
}
#[test]
fn test_mark_ref_removed() {
let mut changes = HeapChanges::<String>::new_strings();
let ref1 = changes.append("test".to_string());
assert!(!changes.is_ref_removed(&ref1));
changes.mark_ref_removed(&ref1);
assert!(changes.is_ref_removed(&ref1));
}
#[test]
fn test_appended_iter() {
let mut changes = HeapChanges::<String>::new_strings();
changes.append("one".to_string());
changes.append("two".to_string());
let items: Vec<_> = changes.appended_iter().collect();
assert_eq!(items.len(), 2);
assert_eq!(items[0].0, "one");
assert_eq!(items[1].0, "two");
}
}