use std::{collections::HashMap, sync::Arc};
use crate::{emulation::HeapRef, metadata::token::Token, CilObject};
#[derive(Clone, Default)]
pub struct AppDomainState {
loaded_assemblies: HashMap<String, LoadedAssemblyInfo>,
interned_strings: HashMap<String, HeapRef>,
assembly_resolve_handlers: Vec<Token>,
type_resolve_handlers: Vec<Token>,
executing_assembly: Option<Token>,
entry_assembly: Option<Token>,
loaded_cilobjects: Vec<Arc<CilObject>>,
}
impl std::fmt::Debug for AppDomainState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AppDomainState")
.field("loaded_assemblies", &self.loaded_assemblies)
.field("interned_strings_count", &self.interned_strings.len())
.field("assembly_resolve_handlers", &self.assembly_resolve_handlers)
.field("type_resolve_handlers", &self.type_resolve_handlers)
.field("executing_assembly", &self.executing_assembly)
.field("entry_assembly", &self.entry_assembly)
.field("loaded_cilobjects_count", &self.loaded_cilobjects.len())
.finish()
}
}
#[derive(Clone, Debug)]
pub struct LoadedAssemblyInfo {
pub name: String,
pub token: Token,
pub full_name: Option<String>,
pub location: Option<String>,
pub loaded_from_bytes: bool,
}
impl AppDomainState {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register_assembly(&mut self, info: LoadedAssemblyInfo) {
self.loaded_assemblies.insert(info.name.clone(), info);
}
#[must_use]
pub fn get_assembly(&self, name: &str) -> Option<&LoadedAssemblyInfo> {
self.loaded_assemblies.get(name)
}
pub fn loaded_assemblies(&self) -> impl Iterator<Item = &LoadedAssemblyInfo> {
self.loaded_assemblies.values()
}
pub fn set_executing_assembly(&mut self, token: Token) {
self.executing_assembly = Some(token);
}
#[must_use]
pub fn executing_assembly(&self) -> Option<Token> {
self.executing_assembly
}
pub fn set_entry_assembly(&mut self, token: Token) {
self.entry_assembly = Some(token);
}
#[must_use]
pub fn entry_assembly(&self) -> Option<Token> {
self.entry_assembly
}
pub fn intern_string(&mut self, value: String, heap_ref: HeapRef) -> HeapRef {
if let Some(&existing) = self.interned_strings.get(&value) {
existing
} else {
self.interned_strings.insert(value, heap_ref);
heap_ref
}
}
#[must_use]
pub fn get_interned(&self, value: &str) -> Option<HeapRef> {
self.interned_strings.get(value).copied()
}
pub fn add_assembly_resolve_handler(&mut self, handler: Token) {
self.assembly_resolve_handlers.push(handler);
}
#[must_use]
pub fn assembly_resolve_handlers(&self) -> &[Token] {
&self.assembly_resolve_handlers
}
pub fn add_type_resolve_handler(&mut self, handler: Token) {
self.type_resolve_handlers.push(handler);
}
#[must_use]
pub fn type_resolve_handlers(&self) -> &[Token] {
&self.type_resolve_handlers
}
pub fn register_parsed_assembly(&mut self, asm: Arc<CilObject>, max: usize) -> Option<usize> {
if self.loaded_cilobjects.len() >= max {
return None;
}
let index = self.loaded_cilobjects.len();
self.loaded_cilobjects.push(asm);
Some(index)
}
#[must_use]
pub fn get_parsed_assembly(&self, index: usize) -> Option<&Arc<CilObject>> {
self.loaded_cilobjects.get(index)
}
#[must_use]
pub fn find_type_across_assemblies(
&self,
namespace: &str,
name: &str,
) -> Option<(usize, Token)> {
for (index, asm) in self.loaded_cilobjects.iter().enumerate() {
for cil_type in asm.types().all_types() {
if cil_type.namespace == namespace && cil_type.name == name {
return Some((index, cil_type.token));
}
}
}
None
}
#[must_use]
pub fn parsed_assembly_count(&self) -> usize {
self.loaded_cilobjects.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_app_domain_creation() {
let domain = AppDomainState::new();
assert!(domain.loaded_assemblies().next().is_none());
}
fn sample_assembly() -> Arc<CilObject> {
let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/samples/WindowsBase.dll");
Arc::new(CilObject::from_path(&path).expect("sample assembly must load"))
}
#[test]
fn register_parsed_assembly_refuses_past_the_cap() {
let mut domain = AppDomainState::new();
let asm = sample_assembly();
assert_eq!(
domain.register_parsed_assembly(Arc::clone(&asm), 2),
Some(0)
);
assert_eq!(
domain.register_parsed_assembly(Arc::clone(&asm), 2),
Some(1)
);
assert_eq!(domain.register_parsed_assembly(Arc::clone(&asm), 2), None);
assert_eq!(domain.parsed_assembly_count(), 2);
}
#[test]
fn register_parsed_assembly_honours_a_zero_cap() {
let mut domain = AppDomainState::new();
assert_eq!(domain.register_parsed_assembly(sample_assembly(), 0), None);
assert_eq!(domain.parsed_assembly_count(), 0);
}
#[test]
fn cloning_the_domain_keeps_later_registrations_local() {
let mut parent = AppDomainState::new();
parent.register_parsed_assembly(sample_assembly(), 8);
let mut fork = parent.clone();
fork.register_parsed_assembly(sample_assembly(), 8);
assert_eq!(fork.parsed_assembly_count(), 2);
assert_eq!(
parent.parsed_assembly_count(),
1,
"a fork's load must not be visible to the parent"
);
}
#[test]
fn test_register_assembly() {
let mut domain = AppDomainState::new();
let info = LoadedAssemblyInfo {
name: "TestAssembly".to_string(),
token: Token::new(0x20000001),
full_name: Some("TestAssembly, Version=1.0.0.0".to_string()),
location: None,
loaded_from_bytes: false,
};
domain.register_assembly(info);
let loaded = domain.get_assembly("TestAssembly");
assert!(loaded.is_some());
assert_eq!(loaded.unwrap().token, Token::new(0x20000001));
}
#[test]
fn test_string_interning() {
let mut domain = AppDomainState::new();
let ref1 = HeapRef::new(1);
let ref2 = HeapRef::new(2);
let result1 = domain.intern_string("hello".to_string(), ref1);
assert_eq!(result1, ref1);
let result2 = domain.intern_string("hello".to_string(), ref2);
assert_eq!(result2, ref1);
let result3 = domain.intern_string("world".to_string(), ref2);
assert_eq!(result3, ref2);
}
#[test]
fn test_executing_assembly() {
let mut domain = AppDomainState::new();
assert!(domain.executing_assembly().is_none());
domain.set_executing_assembly(Token::new(0x20000001));
assert_eq!(domain.executing_assembly(), Some(Token::new(0x20000001)));
}
}