mod arrays;
mod collections;
mod crypto;
mod reflection;
mod streams;
use std::{
collections::{HashMap, HashSet as StdHashSet, VecDeque},
fmt,
sync::{
atomic::{AtomicU64, AtomicUsize, Ordering},
Arc, Mutex, RwLock,
},
};
use imbl::HashMap as ImHashMap;
use crate::{
assembly::InstructionAssembler,
emulation::{engine::EmulationError, tokens, EmValue, HeapRef},
metadata::{signatures::TypeSignature, token::Token, typesystem::CilFlavor},
Result,
};
pub type SymmetricAlgorithmInfo = (Arc<str>, Option<Vec<u8>>, Option<Vec<u8>>, u8, u8);
pub type CryptoTransformInfo = (Arc<str>, Vec<u8>, Vec<u8>, bool, u8, u8);
pub type KeyDerivationInfo = (Vec<u8>, Vec<u8>, u32, Arc<str>);
pub type ReflectionPropertyInfo = (Arc<str>, Token, Option<Token>, Option<Token>);
pub struct HeapIter {
items: std::vec::IntoIter<(HeapRef, HeapObject)>,
}
impl Iterator for HeapIter {
type Item = (HeapRef, HeapObject);
fn next(&mut self) -> Option<Self::Item> {
self.items.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.items.size_hint()
}
}
impl ExactSizeIterator for HeapIter {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EncodingType {
Utf8,
Ascii,
Utf16Le,
Utf16Be,
Utf32,
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum DictionaryKey {
Integer(i64),
String(Arc<str>),
Bool(bool),
Char(char),
ObjectRef(HeapRef),
Null,
}
impl DictionaryKey {
#[must_use]
pub fn from_emvalue(value: &EmValue, heap: &ManagedHeap) -> Option<Self> {
match value {
EmValue::I32(v) => Some(DictionaryKey::Integer(i64::from(*v))),
EmValue::I64(v) => Some(DictionaryKey::Integer(*v)),
EmValue::NativeInt(v) => Some(DictionaryKey::Integer(*v)),
EmValue::NativeUInt(v) => Some(DictionaryKey::Integer(*v as i64)),
EmValue::Bool(v) => Some(DictionaryKey::Bool(*v)),
EmValue::Char(v) => Some(DictionaryKey::Char(*v)),
EmValue::Null => Some(DictionaryKey::Null),
EmValue::ObjectRef(href) => {
if let Ok(s) = heap.get_string(*href) {
Some(DictionaryKey::String(s))
} else {
Some(DictionaryKey::ObjectRef(*href))
}
}
_ => None,
}
}
#[must_use]
pub fn to_emvalue(&self, heap: &ManagedHeap) -> EmValue {
match self {
DictionaryKey::Integer(v) => EmValue::I32(*v as i32),
DictionaryKey::String(s) => {
if let Ok(href) = heap.alloc_string(s) {
EmValue::ObjectRef(href)
} else {
EmValue::Null
}
}
DictionaryKey::Bool(v) => EmValue::Bool(*v),
DictionaryKey::Char(v) => EmValue::Char(*v),
DictionaryKey::ObjectRef(href) => EmValue::ObjectRef(*href),
DictionaryKey::Null => EmValue::Null,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeWrapper {
ByRef,
SzArray,
Pointer,
GenericInst(Vec<Token>),
}
#[derive(Clone, Debug)]
pub struct DelegateEntry {
pub target: Option<HeapRef>,
pub method_token: Token,
}
#[derive(Clone, Debug)]
pub enum HeapObject {
String(Arc<str>),
Array {
element_type: CilFlavor,
elements: Vec<EmValue>,
},
MultiArray {
element_type: CilFlavor,
dimensions: Vec<usize>,
elements: Vec<EmValue>,
},
Object {
type_token: Token,
fields: HashMap<Token, EmValue>,
},
BoxedValue {
type_token: Token,
value: Box<EmValue>,
},
Delegate {
type_token: Token,
invocation_list: Vec<DelegateEntry>,
},
TypedReference {
type_token: Token,
address: EmValue,
},
Encoding {
encoding_type: EncodingType,
},
CryptoAlgorithm {
algorithm_type: Arc<str>,
accumulated_data: Vec<u8>,
hash_result: Option<Vec<u8>>,
rsa_public_key: Option<(Vec<u8>, Vec<u8>)>,
hmac_key: Option<Vec<u8>>,
},
SymmetricAlgorithm {
algorithm_type: Arc<str>,
key: Option<Vec<u8>>,
iv: Option<Vec<u8>>,
mode: u8,
padding: u8,
},
CryptoTransform {
algorithm: Arc<str>,
key: Vec<u8>,
iv: Vec<u8>,
is_encryptor: bool,
mode: u8,
padding: u8,
},
ReflectionMethod {
method_token: Token,
method_type_args: Option<Vec<Token>>,
},
ReflectionType {
type_token: Token,
wrapper: Option<TypeWrapper>,
},
ReflectionField {
field_token: Token,
declaring_type_token: Token,
is_static: bool,
},
ReflectionProperty {
property_name: Arc<str>,
declaring_type_token: Token,
getter_token: Option<Token>,
setter_token: Option<Token>,
},
ReflectionParameter {
method_token: Token,
position: u32,
parameter_type: TypeSignature,
},
DynamicMethod {
il_generator: Option<HeapRef>,
is_static: bool,
param_types: Vec<Token>,
return_type: Option<Token>,
},
ILGenerator {
dynamic_method: HeapRef,
assembler: Arc<Mutex<InstructionAssembler>>,
label_names: Box<boxcar::Vec<String>>,
token_map: Box<boxcar::Vec<(u32, Token)>>,
local_types: Box<boxcar::Vec<Token>>,
},
KeyDerivation {
password: Vec<u8>,
salt: Vec<u8>,
iterations: u32,
hash_algorithm: Arc<str>,
},
Dictionary {
entries: HashMap<DictionaryKey, EmValue>,
},
List {
elements: Vec<EmValue>,
},
Stream {
data: Vec<u8>,
position: usize,
},
StringBuilder {
buffer: String,
capacity: usize,
},
Stack {
elements: Vec<EmValue>,
},
Queue {
elements: VecDeque<EmValue>,
},
HashSet {
elements: StdHashSet<DictionaryKey>,
},
CryptoStream {
underlying_stream: HeapRef,
transform: HeapRef,
mode: u8,
transformed_data: Option<Vec<u8>>,
transformed_pos: usize,
write_buffer: Vec<u8>,
},
CompressedStream {
underlying_stream: HeapRef,
compression_type: u8,
mode: u8,
decompressed_data: Option<Vec<u8>>,
read_position: usize,
},
}
impl HeapObject {
#[must_use]
pub fn kind(&self) -> &'static str {
match self {
HeapObject::String(_) => "string",
HeapObject::Array { .. } => "array",
HeapObject::MultiArray { .. } => "multi-dimensional array",
HeapObject::Object { .. } => "object",
HeapObject::BoxedValue { .. } => "boxed value",
HeapObject::Delegate { .. } => "delegate",
HeapObject::TypedReference { .. } => "typed reference",
HeapObject::Encoding { .. } => "encoding",
HeapObject::CryptoAlgorithm { .. } => "crypto algorithm",
HeapObject::SymmetricAlgorithm { .. } => "symmetric algorithm",
HeapObject::CryptoTransform { .. } => "crypto transform",
HeapObject::ReflectionMethod { .. } => "reflection method",
HeapObject::ReflectionType { .. } => "reflection type",
HeapObject::ReflectionField { .. } => "reflection field",
HeapObject::ReflectionProperty { .. } => "reflection property",
HeapObject::ReflectionParameter { .. } => "reflection parameter",
HeapObject::DynamicMethod { .. } => "dynamic method",
HeapObject::ILGenerator { .. } => "IL generator",
HeapObject::Dictionary { .. } => "dictionary",
HeapObject::List { .. } => "list",
HeapObject::StringBuilder { .. } => "string builder",
HeapObject::Stack { .. } => "stack",
HeapObject::Queue { .. } => "queue",
HeapObject::HashSet { .. } => "hash set",
HeapObject::KeyDerivation { .. } => "key derivation",
HeapObject::Stream { .. } => "stream",
HeapObject::CryptoStream { .. } => "crypto stream",
HeapObject::CompressedStream { .. } => "compressed stream",
}
}
#[must_use]
pub fn estimated_size(&self) -> usize {
match self {
HeapObject::String(s) => 24 + s.len() * 2, HeapObject::Array { elements, .. } => 24 + elements.len() * 8,
HeapObject::MultiArray { elements, .. } => 32 + elements.len() * 8,
HeapObject::Object { fields, .. } => 24 + fields.len() * 16,
HeapObject::TypedReference { .. }
| HeapObject::BoxedValue { .. }
| HeapObject::CryptoAlgorithm { .. }
| HeapObject::ReflectionMethod { .. }
| HeapObject::ReflectionType { .. }
| HeapObject::ReflectionField { .. }
| HeapObject::ReflectionProperty { .. }
| HeapObject::ReflectionParameter { .. }
| HeapObject::DynamicMethod { .. } => 32,
HeapObject::ILGenerator { .. } => 128,
HeapObject::CryptoTransform { key, iv, .. } => 48 + key.len() + iv.len(),
HeapObject::Delegate { .. } => 48,
HeapObject::Encoding { .. } => 24,
HeapObject::SymmetricAlgorithm { key, iv, .. } => {
32 + key.as_ref().map_or(0, Vec::len) + iv.as_ref().map_or(0, Vec::len)
}
HeapObject::Dictionary { entries } => 48 + entries.len() * 32,
HeapObject::List { elements } => 32 + elements.len() * 8,
HeapObject::StringBuilder { buffer, .. } => 32 + buffer.len(),
HeapObject::Stack { elements } => 32 + elements.len() * 8,
HeapObject::Queue { elements } => 32 + elements.len() * 8,
HeapObject::HashSet { elements } => 48 + elements.len() * 16,
HeapObject::KeyDerivation { password, salt, .. } => 48 + password.len() + salt.len(),
HeapObject::Stream { data, .. } => 32 + data.len(),
HeapObject::CryptoStream {
transformed_data,
write_buffer,
..
} => 64 + transformed_data.as_ref().map_or(0, Vec::len) + write_buffer.len(),
HeapObject::CompressedStream {
decompressed_data, ..
} => 48 + decompressed_data.as_ref().map_or(0, Vec::len),
}
}
}
impl fmt::Display for HeapObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HeapObject::String(s) => {
if s.len() > 50 {
write!(f, "\"{}...\"", &s[..47])
} else {
write!(f, "\"{s}\"")
}
}
HeapObject::Array {
element_type,
elements,
} => {
write!(f, "{:?}[{}]", element_type, elements.len())
}
HeapObject::MultiArray {
element_type,
dimensions,
..
} => {
write!(f, "{element_type:?}[")?;
for (i, dim) in dimensions.iter().enumerate() {
if i > 0 {
write!(f, ",")?;
}
write!(f, "{dim}")?;
}
write!(f, "]")
}
HeapObject::Object { type_token, .. } => {
write!(f, "object({type_token})")
}
HeapObject::BoxedValue { type_token, value } => {
write!(f, "boxed({type_token}, {value})")
}
HeapObject::Delegate {
type_token,
invocation_list,
} => {
if let Some(entry) = invocation_list.last() {
write!(f, "delegate({type_token}, {})", entry.method_token)
} else {
write!(f, "delegate({type_token}, empty)")
}
}
HeapObject::TypedReference {
type_token,
address,
} => {
write!(
f,
"typed_reference(type=0x{:08x}, addr={address:?})",
type_token.value()
)
}
HeapObject::Encoding { encoding_type } => {
write!(f, "encoding({encoding_type:?})")
}
HeapObject::CryptoAlgorithm { algorithm_type, .. } => {
write!(f, "crypto_algorithm({algorithm_type})")
}
HeapObject::SymmetricAlgorithm { algorithm_type, .. } => {
write!(f, "symmetric_algorithm({algorithm_type})")
}
HeapObject::CryptoTransform {
algorithm,
is_encryptor,
key,
..
} => {
let mode = if *is_encryptor { "encrypt" } else { "decrypt" };
write!(
f,
"crypto_transform({} {} key={}B)",
algorithm,
mode,
key.len()
)
}
HeapObject::ReflectionMethod { method_token, .. } => {
write!(f, "reflection_method(0x{:08x})", method_token.value())
}
HeapObject::ReflectionType { type_token, .. } => {
write!(f, "reflection_type(0x{:08x})", type_token.value())
}
HeapObject::ReflectionField {
field_token,
declaring_type_token,
is_static,
} => {
let kind = if *is_static { "static" } else { "instance" };
write!(
f,
"reflection_field({kind}, 0x{:08x}, declaring=0x{:08x})",
field_token.value(),
declaring_type_token.value()
)
}
HeapObject::ReflectionProperty {
property_name,
declaring_type_token,
..
} => {
write!(
f,
"reflection_property({property_name}, declaring=0x{:08x})",
declaring_type_token.value()
)
}
HeapObject::ReflectionParameter {
method_token,
position,
..
} => {
write!(
f,
"reflection_parameter(pos={position}, method=0x{:08x})",
method_token.value()
)
}
HeapObject::DynamicMethod { .. } => {
write!(f, "dynamic_method")
}
HeapObject::ILGenerator { dynamic_method, .. } => {
write!(f, "il_generator(dm={:?})", dynamic_method)
}
HeapObject::Dictionary { entries } => {
write!(f, "dictionary({} entries)", entries.len())
}
HeapObject::List { elements } => {
write!(f, "list({} elements)", elements.len())
}
HeapObject::StringBuilder { buffer, .. } => {
if buffer.len() > 50 {
write!(
f,
"stringbuilder({}... len={})",
&buffer[..47],
buffer.len()
)
} else {
write!(f, "stringbuilder(\"{buffer}\")")
}
}
HeapObject::Stack { elements } => {
write!(f, "stack({} elements)", elements.len())
}
HeapObject::Queue { elements } => {
write!(f, "queue({} elements)", elements.len())
}
HeapObject::HashSet { elements } => {
write!(f, "hashset({} elements)", elements.len())
}
HeapObject::KeyDerivation {
hash_algorithm,
iterations,
..
} => {
write!(
f,
"key_derivation({hash_algorithm}, {iterations} iterations)"
)
}
HeapObject::Stream { data, position } => {
write!(f, "stream({} bytes, pos={})", data.len(), position)
}
HeapObject::CryptoStream {
mode,
transformed_data,
write_buffer,
..
} => {
let mode_str = if *mode == 0 { "Read" } else { "Write" };
let cached = transformed_data.as_ref().map_or(0, Vec::len);
let buffered = write_buffer.len();
write!(
f,
"crypto_stream(mode={mode_str}, cached={cached}, buffered={buffered})"
)
}
HeapObject::CompressedStream {
compression_type,
mode,
decompressed_data,
read_position,
..
} => {
let kind = if *compression_type == 0 {
"Deflate"
} else {
"GZip"
};
let mode_str = if *mode == 0 { "Decompress" } else { "Compress" };
let cached = decompressed_data.as_ref().map_or(0, Vec::len);
write!(
f,
"compressed_stream({kind}, mode={mode_str}, cached={cached}, pos={read_position})"
)
}
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct HeapState {
pub(crate) objects: ImHashMap<u64, HeapObject>,
pub(crate) original_types: ImHashMap<u64, Token>,
}
#[derive(Debug)]
pub struct ManagedHeap {
pub(crate) state: RwLock<HeapState>,
pub(crate) next_id: AtomicU64,
pub(crate) current_size: AtomicUsize,
pub(crate) max_size: usize,
}
impl ManagedHeap {
#[must_use]
pub fn new(max_size: usize) -> Self {
ManagedHeap {
state: RwLock::new(HeapState {
objects: ImHashMap::new(),
original_types: ImHashMap::new(),
}),
next_id: AtomicU64::new(1),
current_size: AtomicUsize::new(0),
max_size,
}
}
#[must_use]
pub fn default_size() -> Self {
Self::new(64 * 1024 * 1024)
}
pub(crate) fn check_allocation(&self, size: usize) -> Result<()> {
let current = self.current_size.load(Ordering::Relaxed);
if current + size > self.max_size {
return Err(EmulationError::HeapMemoryLimitExceeded {
current,
limit: self.max_size,
}
.into());
}
Ok(())
}
pub(crate) fn alloc_object_internal(
&self,
obj: HeapObject,
type_token: Option<Token>,
) -> Result<HeapRef> {
let size = obj.estimated_size();
self.check_allocation(size)?;
let id = self.next_id.fetch_add(1, Ordering::SeqCst);
let heap_ref = HeapRef::new(id);
let original_type = type_token.or(match &obj {
HeapObject::Object { type_token, .. }
| HeapObject::BoxedValue { type_token, .. }
| HeapObject::Delegate { type_token, .. } => Some(*type_token),
_ => None,
});
let mut state = self
.state
.write()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
state.objects.insert(heap_ref.id(), obj);
if let Some(token) = original_type {
state.original_types.insert(heap_ref.id(), token);
}
self.current_size.fetch_add(size, Ordering::Relaxed);
Ok(heap_ref)
}
pub fn set_type_token(&self, heap_ref: HeapRef, type_token: Token) -> Result<()> {
let mut state = self
.state
.write()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
state.original_types.insert(heap_ref.id(), type_token);
Ok(())
}
pub fn fork(&self) -> Result<Self> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
Ok(ManagedHeap {
state: RwLock::new(HeapState {
objects: state.objects.clone(),
original_types: state.original_types.clone(),
}),
next_id: AtomicU64::new(self.next_id.load(Ordering::SeqCst)),
current_size: AtomicUsize::new(self.current_size.load(Ordering::Relaxed)),
max_size: self.max_size,
})
}
pub fn alloc_string(&self, value: &str) -> Result<HeapRef> {
let arc_str: Arc<str> = value.into();
self.alloc_object_internal(HeapObject::String(arc_str), None)
}
pub fn alloc_object_with_fields(
&self,
type_token: Token,
field_types: &[(Token, CilFlavor)],
) -> Result<HeapRef> {
let mut fields = HashMap::new();
for (token, cil_flavor) in field_types {
fields.insert(*token, EmValue::default_for_flavor(cil_flavor));
}
self.alloc_object_internal(HeapObject::Object { type_token, fields }, None)
}
pub fn alloc_object(&self, type_token: Token) -> Result<HeapRef> {
self.alloc_object_internal(
HeapObject::Object {
type_token,
fields: HashMap::new(),
},
None,
)
}
pub fn alloc_boxed(&self, type_token: Token, value: EmValue) -> Result<HeapRef> {
self.alloc_object_internal(
HeapObject::BoxedValue {
type_token,
value: Box::new(value),
},
None,
)
}
pub fn alloc_typed_reference(&self, type_token: Token, address: EmValue) -> Result<HeapRef> {
self.alloc_object_internal(
HeapObject::TypedReference {
type_token,
address,
},
None,
)
}
pub fn typed_reference_type(&self, heap_ref: HeapRef) -> Result<Token> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
match state.objects.get(&heap_ref.id()) {
Some(HeapObject::TypedReference { type_token, .. }) => Ok(*type_token),
Some(other) => Err(EmulationError::TypeMismatch {
operation: "refanytype",
expected: "TypedReference",
found: other.kind(),
}
.into()),
None => Err(EmulationError::InvalidHeapReference {
reference_id: heap_ref.id(),
}
.into()),
}
}
pub fn typed_reference_value(
&self,
heap_ref: HeapRef,
expected_type: Option<Token>,
) -> Result<EmValue> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
match state.objects.get(&heap_ref.id()) {
Some(HeapObject::TypedReference {
type_token,
address,
}) => {
if let Some(expected) = expected_type {
if *type_token != expected {
return Err(EmulationError::InvalidCast {
from_type: format!("TypedReference(0x{:08X})", type_token.value()),
to_type: format!("0x{:08X}", expected.value()),
}
.into());
}
}
Ok(address.clone())
}
Some(other) => Err(EmulationError::TypeMismatch {
operation: "refanyval",
expected: "TypedReference",
found: other.kind(),
}
.into()),
None => Err(EmulationError::InvalidHeapReference {
reference_id: heap_ref.id(),
}
.into()),
}
}
pub fn alloc_delegate(
&self,
type_token: Token,
target: Option<HeapRef>,
method_token: Token,
) -> Result<HeapRef> {
self.alloc_object_internal(
HeapObject::Delegate {
type_token,
invocation_list: vec![DelegateEntry {
target,
method_token,
}],
},
None,
)
}
pub fn alloc_multicast_delegate(
&self,
type_token: Token,
entries: Vec<DelegateEntry>,
) -> Result<HeapRef> {
self.alloc_object_internal(
HeapObject::Delegate {
type_token,
invocation_list: entries,
},
None,
)
}
pub fn get(&self, heap_ref: HeapRef) -> Result<HeapObject> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
state.objects.get(&heap_ref.id()).cloned().ok_or(
EmulationError::InvalidHeapReference {
reference_id: heap_ref.id(),
}
.into(),
)
}
pub fn with_object_mut<F, R>(&self, heap_ref: HeapRef, f: F) -> Result<R>
where
F: FnOnce(&mut HeapObject) -> Result<R>,
{
let mut state = self
.state
.write()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
let obj =
state
.objects
.get_mut(&heap_ref.id())
.ok_or(EmulationError::InvalidHeapReference {
reference_id: heap_ref.id(),
})?;
f(obj)
}
pub fn get_string(&self, heap_ref: HeapRef) -> Result<Arc<str>> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
match state.objects.get(&heap_ref.id()) {
Some(HeapObject::String(s)) => Ok(Arc::clone(s)),
Some(other) => Err(EmulationError::HeapTypeMismatch {
expected: "string",
found: other.kind(),
}
.into()),
None => Err(EmulationError::InvalidHeapReference {
reference_id: heap_ref.id(),
}
.into()),
}
}
pub fn get_field(&self, heap_ref: HeapRef, field_token: Token) -> Result<EmValue> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
match state.objects.get(&heap_ref.id()) {
Some(HeapObject::Object { fields, .. }) => fields
.get(&field_token)
.cloned()
.ok_or(EmulationError::FieldNotFound { token: field_token }.into()),
Some(other) => Err(EmulationError::HeapTypeMismatch {
expected: "object",
found: other.kind(),
}
.into()),
None => Err(EmulationError::InvalidHeapReference {
reference_id: heap_ref.id(),
}
.into()),
}
}
pub fn set_field(&self, heap_ref: HeapRef, field_token: Token, value: EmValue) -> Result<()> {
let mut state = self
.state
.write()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
match state.objects.get_mut(&heap_ref.id()) {
Some(HeapObject::Object { fields, .. }) => {
fields.insert(field_token, value);
Ok(())
}
Some(other) => Err(EmulationError::HeapTypeMismatch {
expected: "object",
found: other.kind(),
}
.into()),
None => Err(EmulationError::InvalidHeapReference {
reference_id: heap_ref.id(),
}
.into()),
}
}
pub fn get_type_token(&self, heap_ref: HeapRef) -> Result<Token> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
if let Some(original_token) = state.original_types.get(&heap_ref.id()) {
return Ok(*original_token);
}
match state.objects.get(&heap_ref.id()) {
Some(
HeapObject::Object { type_token, .. }
| HeapObject::BoxedValue { type_token, .. }
| HeapObject::Delegate { type_token, .. },
) => Ok(*type_token),
Some(HeapObject::String(_)) => Ok(tokens::system::STRING),
Some(HeapObject::Array { .. } | HeapObject::MultiArray { .. }) => {
Ok(tokens::system::ARRAY)
}
Some(HeapObject::TypedReference { .. }) => Ok(tokens::system::TYPED_REFERENCE),
Some(HeapObject::Encoding { .. }) => Ok(tokens::system::ENCODING),
Some(HeapObject::CryptoAlgorithm { .. }) => Ok(tokens::crypto::CRYPTO_ALGORITHM),
Some(HeapObject::SymmetricAlgorithm { .. }) => Ok(tokens::crypto::SYMMETRIC_ALGORITHM),
Some(HeapObject::CryptoTransform { .. }) => Ok(tokens::crypto::CRYPTO_TRANSFORM),
Some(HeapObject::ReflectionMethod { .. }) => Ok(tokens::reflection::METHOD),
Some(HeapObject::ReflectionType { .. }) => Ok(tokens::reflection::TYPE),
Some(HeapObject::ReflectionField { .. }) => Ok(tokens::reflection::FIELD),
Some(HeapObject::ReflectionProperty { .. }) => Ok(tokens::reflection::PROPERTY),
Some(HeapObject::ReflectionParameter { .. }) => Ok(tokens::reflection::PARAMETER),
Some(HeapObject::Dictionary { .. }) => Ok(tokens::collections::DICTIONARY),
Some(HeapObject::List { .. }) => Ok(tokens::collections::LIST),
Some(HeapObject::StringBuilder { .. }) => Ok(tokens::system::STRING_BUILDER),
Some(HeapObject::Stack { .. }) => Ok(tokens::collections::STACK),
Some(HeapObject::Queue { .. }) => Ok(tokens::collections::QUEUE),
Some(HeapObject::HashSet { .. }) => Ok(tokens::collections::HASH_SET),
Some(HeapObject::DynamicMethod { .. }) => Ok(tokens::codegen::DYNAMIC_METHOD),
Some(HeapObject::ILGenerator { .. }) => Ok(tokens::codegen::IL_GENERATOR),
Some(HeapObject::KeyDerivation { .. }) => Ok(tokens::crypto::KEY_DERIVATION),
Some(HeapObject::Stream { .. }) => Ok(tokens::io::STREAM),
Some(HeapObject::CryptoStream { .. }) => Ok(tokens::io::CRYPTO_STREAM),
Some(HeapObject::CompressedStream { .. }) => Ok(tokens::io::COMPRESSED_STREAM),
None => Err(EmulationError::InvalidHeapReference {
reference_id: heap_ref.id(),
}
.into()),
}
}
pub fn unbox(&self, heap_ref: HeapRef) -> Result<EmValue> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
match state.objects.get(&heap_ref.id()) {
Some(HeapObject::BoxedValue { value, .. }) => Ok((**value).clone()),
Some(other) => Err(EmulationError::HeapTypeMismatch {
expected: "boxed value",
found: other.kind(),
}
.into()),
None => Err(EmulationError::InvalidHeapReference {
reference_id: heap_ref.id(),
}
.into()),
}
}
pub fn get_boxed_value(&self, heap_ref: HeapRef) -> Result<EmValue> {
self.unbox(heap_ref)
}
pub fn contains(&self, heap_ref: HeapRef) -> Result<bool> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
Ok(state.objects.contains_key(&heap_ref.id()))
}
#[must_use]
pub fn current_size(&self) -> usize {
self.current_size.load(Ordering::Relaxed)
}
#[must_use]
pub fn max_size(&self) -> usize {
self.max_size
}
pub fn object_count(&self) -> Result<usize> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
Ok(state.objects.len())
}
pub fn clear(&self) -> Result<()> {
let mut state = self
.state
.write()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
state.objects.clear();
self.current_size.store(0, Ordering::Relaxed);
Ok(())
}
pub fn to_vec(&self) -> Result<Vec<(HeapRef, HeapObject)>> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
Ok(state
.objects
.iter()
.map(|(&id, obj)| (HeapRef::new(id), obj.clone()))
.collect())
}
pub fn iter(&self) -> Result<HeapIter> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
let items: Vec<_> = state
.objects
.iter()
.map(|(&id, obj)| (HeapRef::new(id), obj.clone()))
.collect();
Ok(HeapIter {
items: items.into_iter(),
})
}
pub fn object_count_estimate(&self) -> Result<usize> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
Ok(state.objects.len())
}
#[must_use]
pub fn get_string_opt(&self, heap_ref: HeapRef) -> Option<Arc<str>> {
self.get_string(heap_ref).ok()
}
pub fn alloc_encoding(
&self,
encoding_type: EncodingType,
type_token: Option<Token>,
) -> Result<HeapRef> {
self.alloc_object_internal(HeapObject::Encoding { encoding_type }, type_token)
}
pub fn get_encoding_type(&self, heap_ref: HeapRef) -> Result<Option<EncodingType>> {
let state = self
.state
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
Ok(match state.objects.get(&heap_ref.id()) {
Some(HeapObject::Encoding { encoding_type }) => Some(*encoding_type),
_ => None,
})
}
pub(crate) fn preserve_original_type(state: &mut HeapState, id: u64) {
if state.original_types.contains_key(&id) {
return;
}
if let Some(
HeapObject::Object { type_token, .. } | HeapObject::BoxedValue { type_token, .. },
) = state.objects.get(&id)
{
state.original_types.insert(id, *type_token);
}
}
pub fn replace_object(&self, heap_ref: HeapRef, obj: HeapObject) -> Result<()> {
let mut state = self
.state
.write()
.map_err(|_| EmulationError::LockPoisoned {
description: "managed heap",
})?;
let id = heap_ref.id();
Self::preserve_original_type(&mut state, id);
if state.objects.contains_key(&id) {
state.objects.insert(id, obj);
}
Ok(())
}
}
impl Default for ManagedHeap {
fn default() -> Self {
Self::default_size()
}
}
impl fmt::Display for ManagedHeap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = self.state.read().map_err(|_| fmt::Error)?;
write!(
f,
"Heap({} objects, {}/{} bytes)",
state.objects.len(),
self.current_size.load(Ordering::Relaxed),
self.max_size
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Error;
#[test]
fn test_heap_alloc_string() {
let heap = ManagedHeap::new(1024 * 1024);
let string_ref = heap.alloc_string("Hello, World!").unwrap();
assert!(heap.contains(string_ref).unwrap());
let value = heap.get_string(string_ref).unwrap();
assert_eq!(&*value, "Hello, World!");
}
#[test]
fn test_heap_alloc_object() {
let heap = ManagedHeap::new(1024 * 1024);
let type_token = Token::new(0x0200_0001);
let field_token = Token::new(0x0400_0001);
let obj_ref = heap
.alloc_object_with_fields(type_token, &[(field_token, CilFlavor::I4)])
.unwrap();
let field = heap.get_field(obj_ref, field_token).unwrap();
assert_eq!(field, EmValue::I32(0));
heap.set_field(obj_ref, field_token, EmValue::I32(42))
.unwrap();
let field = heap.get_field(obj_ref, field_token).unwrap();
assert_eq!(field, EmValue::I32(42));
}
#[test]
fn test_heap_boxing() {
let heap = ManagedHeap::new(1024 * 1024);
let type_token = Token::new(0x0200_0001);
let boxed_ref = heap.alloc_boxed(type_token, EmValue::I32(42)).unwrap();
let value = heap.unbox(boxed_ref).unwrap();
assert_eq!(value, EmValue::I32(42));
}
#[test]
fn test_heap_out_of_memory() {
let heap = ManagedHeap::new(100);
let large_string = "A".repeat(1000);
let result = heap.alloc_string(&large_string);
assert!(matches!(
result,
Err(Error::Emulation(ref e)) if matches!(e.as_ref(), EmulationError::HeapMemoryLimitExceeded { .. })
));
}
#[test]
fn test_heap_invalid_reference() {
let heap = ManagedHeap::new(1024 * 1024);
let fake_ref = HeapRef::new(9999);
assert!(heap.get(fake_ref).is_err());
assert!(!heap.contains(fake_ref).unwrap());
}
#[test]
fn test_heap_type_mismatch() {
let heap = ManagedHeap::new(1024 * 1024);
let string_ref = heap.alloc_string("test").unwrap();
assert!(matches!(
heap.get_array_element(string_ref, 0),
Err(Error::Emulation(ref e)) if matches!(e.as_ref(), EmulationError::HeapTypeMismatch { .. })
));
}
#[test]
fn test_heap_clear() {
let heap = ManagedHeap::new(1024 * 1024);
heap.alloc_string("test").unwrap();
heap.alloc_array(CilFlavor::I4, 10).unwrap();
assert!(heap.object_count().unwrap() > 0);
heap.clear().unwrap();
assert_eq!(heap.object_count().unwrap(), 0);
assert_eq!(heap.current_size(), 0);
}
#[test]
fn test_heap_display() {
let heap = ManagedHeap::new(1024 * 1024);
heap.alloc_string("test").unwrap();
let display = format!("{heap}");
assert!(display.contains("1 objects"));
}
#[test]
fn test_heap_object_display() {
let obj = HeapObject::String("test".into());
assert!(format!("{obj}").contains("test"));
let obj = HeapObject::Array {
element_type: CilFlavor::I4,
elements: vec![EmValue::I32(0); 5],
};
assert!(format!("{obj}").contains("5"));
}
#[test]
fn test_heap_concurrent_access() {
let heap = ManagedHeap::new(1024 * 1024);
let s1 = heap.alloc_string("first").unwrap();
let s2 = heap.alloc_string("second").unwrap();
let str1 = heap.get_string(s1).unwrap();
let str2 = heap.get_string(s2).unwrap();
assert_eq!(&*str1, "first");
assert_eq!(&*str2, "second");
let s3 = heap.alloc_string("third").unwrap();
let str3 = heap.get_string(s3).unwrap();
assert_eq!(&*str3, "third");
assert_eq!(&*str1, "first");
}
#[test]
fn test_heap_fork() {
let heap = ManagedHeap::new(1024 * 1024);
let s1 = heap.alloc_string("original").unwrap();
let arr1 = heap.alloc_array(CilFlavor::I4, 5).unwrap();
heap.set_array_element(arr1, 0, EmValue::I32(42)).unwrap();
let forked = heap.fork().unwrap();
assert_eq!(heap.get_string(s1).unwrap().as_ref(), "original");
assert_eq!(forked.get_string(s1).unwrap().as_ref(), "original");
assert_eq!(heap.get_array_element(arr1, 0).unwrap(), EmValue::I32(42));
assert_eq!(forked.get_array_element(arr1, 0).unwrap(), EmValue::I32(42));
forked
.set_array_element(arr1, 0, EmValue::I32(100))
.unwrap();
let s2 = forked.alloc_string("forked").unwrap();
assert_eq!(heap.get_array_element(arr1, 0).unwrap(), EmValue::I32(42));
assert!(!heap.contains(s2).unwrap());
assert_eq!(
forked.get_array_element(arr1, 0).unwrap(),
EmValue::I32(100)
);
assert!(forked.contains(s2).unwrap());
assert_eq!(forked.get_string(s2).unwrap().as_ref(), "forked");
}
#[test]
fn test_heap_fork_isolation() {
let heap = ManagedHeap::new(1024 * 1024);
let s1 = heap.alloc_string("hello").unwrap();
let fork1 = heap.fork().unwrap();
let fork2 = heap.fork().unwrap();
let f1_str = fork1.alloc_string("fork1").unwrap();
let f2_str = fork2.alloc_string("fork2").unwrap();
assert!(fork1.contains(s1).unwrap());
assert!(fork2.contains(s1).unwrap());
assert_eq!(fork1.get_string(s1).unwrap().as_ref(), "hello");
assert_eq!(fork2.get_string(s1).unwrap().as_ref(), "hello");
assert_eq!(fork1.get_string(f1_str).unwrap().as_ref(), "fork1");
assert_eq!(fork2.get_string(f2_str).unwrap().as_ref(), "fork2");
assert!(heap.contains(s1).unwrap());
assert!(!heap.contains(f1_str).unwrap()); }
#[test]
fn test_heap_fork_cow_semantics() {
let heap = ManagedHeap::new(1024 * 1024);
let arr = heap.alloc_array(CilFlavor::I4, 3).unwrap();
heap.set_array_element(arr, 0, EmValue::I32(1)).unwrap();
heap.set_array_element(arr, 1, EmValue::I32(2)).unwrap();
heap.set_array_element(arr, 2, EmValue::I32(3)).unwrap();
let forked = heap.fork().unwrap();
assert_eq!(heap.get_array_element(arr, 0).unwrap(), EmValue::I32(1));
assert_eq!(forked.get_array_element(arr, 0).unwrap(), EmValue::I32(1));
forked.set_array_element(arr, 0, EmValue::I32(100)).unwrap();
assert_eq!(heap.get_array_element(arr, 0).unwrap(), EmValue::I32(1));
assert_eq!(heap.get_array_element(arr, 1).unwrap(), EmValue::I32(2));
assert_eq!(heap.get_array_element(arr, 2).unwrap(), EmValue::I32(3));
assert_eq!(forked.get_array_element(arr, 0).unwrap(), EmValue::I32(100));
assert_eq!(forked.get_array_element(arr, 1).unwrap(), EmValue::I32(2));
assert_eq!(forked.get_array_element(arr, 2).unwrap(), EmValue::I32(3));
}
}