use std::path::Path;
use crate::{
file::File,
metadata::{
cilassemblyview::CilAssemblyView,
exports::UnifiedExportContainer,
imports::UnifiedImportContainer,
signatures::{
encode_field_signature, encode_local_var_signature, encode_method_signature,
encode_property_signature, encode_typespec_signature, SignatureField,
SignatureLocalVariables, SignatureMethod, SignatureProperty, SignatureTypeSpec,
},
tables::{AssemblyRefRaw, CodedIndex, CodedIndexType, TableDataOwned, TableId},
token::Token,
},
CilObject, Result, ValidationConfig,
};
mod builders;
mod changes;
mod cleanup;
mod modifications;
mod operation;
mod resolver;
mod writer;
pub use builders::{
ClassBuilder, EnumBuilder, EventBuilder, InterfaceBuilder, MethodBodyBuilder, MethodBuilder,
PropertyBuilder,
};
pub use changes::{AssemblyChanges, ChangeRef, ChangeRefKind, ChangeRefRc, HeapChanges};
pub use cleanup::CleanupRequest;
pub use modifications::TableModifications;
pub use operation::{Operation, TableOperation};
pub use resolver::LastWriteWinsResolver;
pub use writer::GeneratorConfig;
pub(crate) use writer::ResolvePlaceholders;
use writer::PeGenerator;
pub struct CilAssembly {
view: CilAssemblyView,
changes: AssemblyChanges,
pending_cleanup: cleanup::CleanupRequest,
}
impl CilAssembly {
#[must_use]
pub fn new(view: CilAssemblyView) -> Self {
Self {
changes: AssemblyChanges::new(),
view,
pending_cleanup: cleanup::CleanupRequest::new(),
}
}
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let view = CilAssemblyView::from_path(path)?;
Ok(Self::new(view))
}
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
let view = CilAssemblyView::from_mem(bytes)?;
Ok(Self::new(view))
}
pub fn from_bytes_with_validation(
bytes: Vec<u8>,
validation_config: ValidationConfig,
) -> Result<Self> {
let view = CilAssemblyView::from_mem_with_validation(bytes, validation_config)?;
Ok(Self::new(view))
}
pub fn add_cleanup(&mut self, request: &cleanup::CleanupRequest) {
self.pending_cleanup.merge(request);
}
#[must_use]
pub fn pending_cleanup(&self) -> &cleanup::CleanupRequest {
&self.pending_cleanup
}
fn finalize_for_generation(&mut self) -> Result<cleanup::CleanupStats> {
if self.pending_cleanup.is_empty() {
return Ok(cleanup::CleanupStats::new());
}
let request = std::mem::take(&mut self.pending_cleanup);
let stats = cleanup::execute_cleanup(self, &request)?;
Ok(stats)
}
pub fn string_add(&mut self, value: &str) -> Result<ChangeRefRc> {
let change_ref = self.changes.string_heap_changes.append(value.to_string());
Ok(change_ref)
}
pub fn blob_add(&mut self, data: &[u8]) -> Result<ChangeRefRc> {
let change_ref = self.changes.blob_heap_changes.append(data.to_vec());
Ok(change_ref)
}
pub fn guid_add(&mut self, guid: &[u8; 16]) -> Result<ChangeRefRc> {
let change_ref = self.changes.guid_heap_changes.append(*guid);
Ok(change_ref)
}
pub fn userstring_add(&mut self, value: &str) -> Result<ChangeRefRc> {
let change_ref = self
.changes
.userstring_heap_changes
.append(value.to_string());
Ok(change_ref)
}
pub fn resource_data_add(&mut self, data: &[u8]) -> u32 {
let new_offset = self.changes.store_resource_data(data);
let original_size = self.view.cor20header().resource_size;
new_offset + original_size
}
pub fn string_update(&mut self, index: u32, new_value: &str) -> Result<()> {
self.changes
.string_heap_changes
.add_modification(index, new_value.to_string());
Ok(())
}
pub fn string_remove(&mut self, index: u32) -> Result<()> {
let original_heap_size = self
.view()
.streams()
.iter()
.find(|s| s.name == "#Strings")
.map_or(0, |s| s.size);
if index >= original_heap_size {
self.changes
.string_heap_changes
.mark_appended_for_removal(index);
} else {
self.changes.string_heap_changes.add_removal(index);
}
Ok(())
}
pub fn blob_update(&mut self, index: u32, new_data: &[u8]) -> Result<()> {
self.changes
.blob_heap_changes
.add_modification(index, new_data.to_vec());
Ok(())
}
pub fn blob_remove(&mut self, index: u32) -> Result<()> {
self.changes.blob_heap_changes.add_removal(index);
Ok(())
}
pub fn guid_update(&mut self, index: u32, new_guid: &[u8; 16]) -> Result<()> {
let lookup_key = if ChangeRef::is_placeholder(index) {
index
} else {
index.saturating_sub(1).saturating_mul(16)
};
self.changes
.guid_heap_changes
.add_modification(lookup_key, *new_guid);
Ok(())
}
pub fn guid_remove(&mut self, index: u32) -> Result<()> {
let lookup_key = if ChangeRef::is_placeholder(index) {
index
} else {
index.saturating_sub(1).saturating_mul(16)
};
self.changes.guid_heap_changes.add_removal(lookup_key);
Ok(())
}
pub fn userstring_update(&mut self, index: u32, new_value: &str) -> Result<()> {
self.changes
.userstring_heap_changes
.add_modification(index, new_value.to_string());
Ok(())
}
pub fn userstring_remove(&mut self, index: u32) -> Result<()> {
self.changes.userstring_heap_changes.add_removal(index);
Ok(())
}
pub fn string_add_heap(&mut self, heap_data: Vec<u8>) -> Result<()> {
self.changes.string_heap_changes.replace_heap(heap_data);
Ok(())
}
pub fn blob_add_heap(&mut self, heap_data: Vec<u8>) -> Result<()> {
self.changes.blob_heap_changes.replace_heap(heap_data);
Ok(())
}
pub fn guid_add_heap(&mut self, heap_data: Vec<u8>) -> Result<()> {
self.changes.guid_heap_changes.replace_heap(heap_data);
Ok(())
}
pub fn userstring_add_heap(&mut self, heap_data: Vec<u8>) -> Result<()> {
self.changes.userstring_heap_changes.replace_heap(heap_data);
Ok(())
}
pub fn table_row_update(
&mut self,
table_id: TableId,
rid: u32,
new_row: TableDataOwned,
) -> Result<()> {
let original_count = self.original_table_row_count(table_id);
let table_changes = self
.changes
.table_changes
.entry(table_id)
.or_insert_with(|| TableModifications::new_sparse(original_count + 1));
let operation = Operation::Update(rid, new_row);
let table_operation = TableOperation::new(operation);
table_changes.apply_operation(table_operation)?;
Ok(())
}
pub fn table_row_remove(&mut self, table_id: TableId, rid: u32) -> Result<()> {
let original_count = self.original_table_row_count(table_id);
let table_changes = self
.changes
.table_changes
.entry(table_id)
.or_insert_with(|| TableModifications::new_sparse(original_count + 1));
let operation = Operation::Delete(rid);
let table_operation = TableOperation::new(operation);
table_changes.apply_operation(table_operation)?;
Ok(())
}
pub fn table_row_add(&mut self, table_id: TableId, row: TableDataOwned) -> Result<ChangeRefRc> {
let original_count = self.original_table_row_count(table_id);
let table_changes = self
.changes
.table_changes
.entry(table_id)
.or_insert_with(|| TableModifications::new_sparse(original_count + 1));
let new_rid = table_changes.next_rid()?;
let operation = Operation::Insert(new_rid, row);
let table_operation = TableOperation::new(operation);
table_changes.apply_operation(table_operation)?;
let change_ref = ChangeRef::new_table_row(table_id);
let token = Token::from_parts(table_id, new_rid);
change_ref.resolve_to_token(token);
let change_ref_rc = change_ref.into_rc();
table_changes.register_change_ref(new_rid, change_ref_rc.clone());
Ok(change_ref_rc)
}
pub fn to_file<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<()> {
self.finalize_for_generation()?;
PeGenerator::new(self).to_file(path)
}
pub fn to_file_with_config<P: AsRef<std::path::Path>>(
&mut self,
path: P,
config: GeneratorConfig,
) -> Result<()> {
self.finalize_for_generation()?;
PeGenerator::with_config(self, config).to_file(path)
}
pub fn to_memory(&mut self) -> Result<Vec<u8>> {
self.finalize_for_generation()?;
PeGenerator::new(self).to_memory()
}
pub fn to_memory_with_config(&mut self, config: GeneratorConfig) -> Result<Vec<u8>> {
self.finalize_for_generation()?;
PeGenerator::with_config(self, config).to_memory()
}
pub fn into_cilobject(mut self) -> Result<CilObject> {
self.finalize_for_generation()?;
let bytes = PeGenerator::new(&self).to_memory()?;
CilObject::from_mem_with_validation(bytes, ValidationConfig::production())
}
pub fn into_cilobject_with(
mut self,
validation_config: ValidationConfig,
generator_config: GeneratorConfig,
) -> Result<CilObject> {
self.finalize_for_generation()?;
let bytes = PeGenerator::with_config(&self, generator_config).to_memory()?;
CilObject::from_mem_with_validation(bytes, validation_config)
}
pub fn original_table_row_count(&self, table_id: TableId) -> u32 {
if let Some(tables) = self.view.tables() {
tables.table_row_count(table_id)
} else {
0
}
}
pub fn next_rid(&self, table_id: TableId) -> Result<u32> {
if let Some(modifications) = self.changes.table_changes.get(&table_id) {
modifications.next_rid()
} else {
Ok(self.original_table_row_count(table_id) + 1)
}
}
pub fn view(&self) -> &CilAssemblyView {
&self.view
}
pub fn file(&self) -> &File {
self.view.file()
}
pub fn changes(&self) -> &AssemblyChanges {
&self.changes
}
pub fn changes_mut(&mut self) -> &mut AssemblyChanges {
&mut self.changes
}
pub fn add_native_import_dll(&mut self, dll_name: &str) -> Result<()> {
let imports = self.changes.native_imports_mut();
imports.native_mut().add_dll(dll_name)
}
pub fn add_native_import_function(
&mut self,
dll_name: &str,
function_name: &str,
) -> Result<()> {
let imports = self.changes.native_imports_mut();
imports.add_native_function(dll_name, function_name)
}
pub fn add_native_import_function_by_ordinal(
&mut self,
dll_name: &str,
ordinal: u16,
) -> Result<()> {
let imports = self.changes.native_imports_mut();
imports.add_native_function_by_ordinal(dll_name, ordinal)
}
pub fn add_native_export_function(
&mut self,
function_name: &str,
ordinal: u16,
address: u32,
) -> Result<()> {
let exports = self.changes.native_exports_mut();
exports.add_native_function(function_name, ordinal, address)
}
pub fn add_native_export_function_by_ordinal(
&mut self,
ordinal: u16,
address: u32,
) -> Result<()> {
let exports = self.changes.native_exports_mut();
exports.add_native_function_by_ordinal(ordinal, address)
}
pub fn add_native_export_forwarder(
&mut self,
function_name: &str,
ordinal: u16,
target: &str,
) -> Result<()> {
let exports = self.changes.native_exports_mut();
exports.add_native_forwarder(function_name, ordinal, target)
}
pub fn native_imports(&self) -> &UnifiedImportContainer {
self.changes.native_imports()
}
pub fn native_exports(&self) -> &UnifiedExportContainer {
self.changes.native_exports()
}
pub fn store_method_body(&mut self, body_bytes: Vec<u8>) -> u32 {
self.changes.store_method_body(body_bytes)
}
pub fn store_field_data(&mut self, data: Vec<u8>) -> u32 {
self.changes.store_field_data(data)
}
pub fn string_get_or_add(&mut self, value: &str) -> Result<ChangeRefRc> {
if let Some(existing_ref) = self.string_find(value) {
return Ok(existing_ref);
}
self.string_add(value)
}
fn string_find(&self, value: &str) -> Option<ChangeRefRc> {
let heap_changes = &self.changes.string_heap_changes;
for (existing_string, change_ref) in heap_changes.appended_iter() {
if existing_string == value {
return Some(change_ref.clone());
}
}
None
}
pub fn find_assembly_ref_by_name(&self, name: &str) -> Option<CodedIndex> {
if let (Some(assembly_ref_table), Some(strings)) = (
self.view.tables()?.table::<AssemblyRefRaw>(),
self.view.strings(),
) {
for (index, assemblyref) in assembly_ref_table.iter().enumerate() {
if let Ok(assembly_name) = strings.get(assemblyref.name as usize) {
if assembly_name == name {
return Some(CodedIndex::new(
TableId::AssemblyRef,
u32::try_from(index + 1).unwrap_or(u32::MAX),
CodedIndexType::Implementation,
));
}
}
}
}
None
}
pub fn find_core_library_ref(&self) -> Option<CodedIndex> {
self.find_assembly_ref_by_name("mscorlib")
.or_else(|| self.find_assembly_ref_by_name("System.Runtime"))
.or_else(|| self.find_assembly_ref_by_name("System.Private.CoreLib"))
}
pub fn add_method_signature(&mut self, signature: &SignatureMethod) -> Result<ChangeRefRc> {
let encoded_data = encode_method_signature(signature)?;
self.blob_add(&encoded_data)
}
pub fn add_field_signature(&mut self, signature: &SignatureField) -> Result<ChangeRefRc> {
let encoded_data = encode_field_signature(signature)?;
self.blob_add(&encoded_data)
}
pub fn add_property_signature(&mut self, signature: &SignatureProperty) -> Result<ChangeRefRc> {
let encoded_data = encode_property_signature(signature)?;
self.blob_add(&encoded_data)
}
pub fn add_local_var_signature(
&mut self,
signature: &SignatureLocalVariables,
) -> Result<ChangeRefRc> {
let encoded_data = encode_local_var_signature(signature)?;
self.blob_add(&encoded_data)
}
pub fn add_typespec_signature(&mut self, signature: &SignatureTypeSpec) -> Result<ChangeRefRc> {
let encoded_data = encode_typespec_signature(signature)?;
self.blob_add(&encoded_data)
}
}
impl From<CilAssemblyView> for CilAssembly {
fn from(view: CilAssemblyView) -> Self {
Self::new(view)
}
}
impl std::fmt::Debug for CilAssembly {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CilAssembly")
.field("original_view", &"<CilAssemblyView>")
.field("has_changes", &self.changes.has_changes())
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
use crate::test::factories::table::cilassembly::create_test_typedef_row;
use crate::Error;
#[test]
fn test_convert_from_view() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(_assembly) = CilAssembly::from_path(&path) {
}
}
#[test]
fn test_add_string() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let ref1 = assembly.string_add("Hello").unwrap();
let ref2 = assembly.string_add("World").unwrap();
assert_ne!(ref1.placeholder(), ref2.placeholder());
assert!(ref2.placeholder() > ref1.placeholder());
}
}
#[test]
fn test_add_blob() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let ref1 = assembly.blob_add(&[1, 2, 3]).unwrap();
let ref2 = assembly.blob_add(&[4, 5, 6]).unwrap();
assert_ne!(ref1.placeholder(), ref2.placeholder());
assert!(ref2.placeholder() > ref1.placeholder());
}
}
#[test]
fn test_add_guid() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let guid1 = [
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
0x77, 0x88,
];
let guid2 = [
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99,
];
let ref1 = assembly.guid_add(&guid1).unwrap();
let ref2 = assembly.guid_add(&guid2).unwrap();
assert_ne!(ref1.placeholder(), ref2.placeholder());
assert!(ref2.placeholder() > ref1.placeholder());
}
}
#[test]
fn test_add_userstring() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let ref1 = assembly.userstring_add("Hello").unwrap();
let ref2 = assembly.userstring_add("World").unwrap();
assert_ne!(ref1.placeholder(), ref2.placeholder());
assert!(ref2.placeholder() > ref1.placeholder());
}
}
#[test]
fn test_table_row_assignment_uses_correct_rid() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
if let Ok(typedef_row) = create_test_typedef_row() {
if let Ok(change_ref) = assembly.table_row_add(TableId::TypeDef, typedef_row) {
assert!(
change_ref.kind().is_table(),
"ChangeRef should be a table row reference"
);
assert_eq!(
change_ref.kind().table_id(),
Some(TableId::TypeDef),
"ChangeRef should be for TypeDef table"
);
if let Ok(typedef_row2) = create_test_typedef_row() {
if let Ok(change_ref2) =
assembly.table_row_add(TableId::TypeDef, typedef_row2)
{
assert_ne!(
change_ref.id(),
change_ref2.id(),
"Different rows should have different ChangeRef IDs"
);
}
}
}
}
}
}
#[test]
fn test_heap_changes_initialized() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(assembly) = CilAssembly::from_path(&path) {
assert_eq!(assembly.changes.string_heap_changes.additions_count(), 0);
assert_eq!(assembly.changes.blob_heap_changes.additions_count(), 0);
assert_eq!(assembly.changes.guid_heap_changes.additions_count(), 0);
assert_eq!(
assembly.changes.userstring_heap_changes.additions_count(),
0
);
}
}
mod mono_tests {
use super::*;
use crate::metadata::signatures::{
encode_method_signature, SignatureMethod, SignatureParameter, TypeSignature,
};
use crate::metadata::tables::{
CodedIndex, CodedIndexType, MemberRefBuilder, TableId, TypeRefBuilder,
};
use crate::metadata::token::Token;
use crate::test::mono::*;
#[test]
fn test_mono_runtime_compatibility() -> Result<()> {
let runner = TestRunner::new()?;
let modify_assembly = |assembly: &mut CilAssembly| -> Result<()> {
let _method_token = MethodBuilder::new("DotScopeAddedMethod")
.public()
.static_method()
.parameter("a", TypeSignature::I4)
.parameter("b", TypeSignature::I4)
.returns(TypeSignature::I4)
.implementation(|body| {
body.implementation(|asm| {
asm.ldarg_0()?.ldarg_1()?.add()?.ret()?;
Ok(())
})
})
.build(assembly)?;
Ok(())
};
let results = run_complete_test(
&runner,
compilation::templates::HELLO_WORLD,
modify_assembly,
)?;
for result in &results {
assert!(
result.compilation_success,
"Compilation failed for {}: {:?}",
result.architecture.name, result.errors
);
assert!(
result.modification_success,
"Assembly modification failed for {}: {:?}",
result.architecture.name, result.errors
);
assert!(
result.execution_success,
"Execution failed for {}: {:?}",
result.architecture.name, result.errors
);
assert!(
result.disassembly_success,
"Disassembly verification failed for {}: {:?}",
result.architecture.name, result.errors
);
assert!(
result.reflection_success,
"Reflection test failed for {}: {:?}",
result.architecture.name, result.errors
);
assert!(
result.is_fully_successful(),
"Overall test failed for {} architecture with errors: {:?}",
result.architecture.name,
result.errors
);
}
let expected_arch_count = runner.architectures().len();
assert_eq!(
results.len(),
expected_arch_count,
"Expected to test {} architectures",
expected_arch_count
);
Ok(())
}
#[test]
fn test_mono_enhanced_modifications() -> Result<()> {
let runner = TestRunner::new()?;
let modify_assembly = |assembly: &mut CilAssembly| -> Result<()> {
let new_string = "MODIFIED: Hello from enhanced dotscope test!";
let new_string_ref = assembly.userstring_add(new_string)?;
let new_string_token = Token::new(0x70000000 | new_string_ref.placeholder());
let console_assembly_ref = assembly
.find_assembly_ref_by_name("System.Console")
.or_else(|| assembly.find_core_library_ref())
.ok_or_else(|| {
Error::TypeError(
"Could not find System.Console or core library reference".to_string(),
)
})?;
let console_assembly_token =
Token::new((TableId::AssemblyRef as u32) << 24 | console_assembly_ref.row);
let console_writeline_ref =
create_console_writeline_ref(assembly, console_assembly_token)?;
let new_string_token_copy = new_string_token;
let console_writeline_ref_copy = console_writeline_ref;
let _method_token = MethodBuilder::new("PrintModifiedMessage")
.public()
.static_method()
.returns(TypeSignature::Void)
.implementation(move |body| {
body.implementation(move |asm| {
asm.ldstr(new_string_token_copy)?
.call(console_writeline_ref_copy)?
.ret()?;
Ok(())
})
})
.build(assembly)?;
Ok(())
};
let results = run_complete_test(
&runner,
compilation::templates::HELLO_WORLD,
modify_assembly,
)?;
for result in &results {
assert!(
result.is_fully_successful(),
"String test execution failed for {}: {:?}",
result.architecture.name,
result.errors
);
}
Ok(())
}
fn create_writeline_signature() -> Result<Vec<u8>> {
let signature = SignatureMethod {
has_this: false, explicit_this: false,
default: true, vararg: false,
cdecl: false,
stdcall: false,
thiscall: false,
fastcall: false,
param_count_generic: 0,
param_count: 1, return_type: SignatureParameter {
modifiers: Vec::new(),
by_ref: false,
base: TypeSignature::Void, },
params: vec![SignatureParameter {
modifiers: Vec::new(),
by_ref: false,
base: TypeSignature::String, }],
varargs: Vec::new(),
};
encode_method_signature(&signature)
}
fn create_console_writeline_ref(
assembly: &mut CilAssembly,
mscorlib_ref: Token,
) -> Result<Token> {
let console_typeref_ref = TypeRefBuilder::new()
.name("Console")
.namespace("System")
.resolution_scope(CodedIndex::new(
TableId::AssemblyRef,
mscorlib_ref.row(),
CodedIndexType::ResolutionScope,
))
.build(assembly)?;
let console_typeref_token = console_typeref_ref.placeholder_token().unwrap();
let writeline_signature = create_writeline_signature()?;
let memberref_ref = MemberRefBuilder::new()
.name("WriteLine")
.class(CodedIndex::new(
TableId::TypeRef,
console_typeref_token.row(),
CodedIndexType::MemberRefParent,
))
.signature(&writeline_signature)
.build(assembly)?;
Ok(memberref_ref.placeholder_token().unwrap())
}
#[test]
fn test_mono_mathematical_operations() -> Result<()> {
let runner = TestRunner::new()?;
let modify_assembly = |assembly: &mut CilAssembly| -> Result<()> {
let _complex_math_method = MethodBuilder::new("ComplexMath")
.public()
.static_method()
.parameter("x", TypeSignature::I4)
.parameter("y", TypeSignature::I4)
.returns(TypeSignature::I4)
.implementation(|body| {
body.implementation(|asm| {
asm.ldarg_0()?
.ldarg_1()?
.mul()?
.ldarg_0()?
.ldarg_1()?
.sub()?
.ldc_i4_2()?
.mul()?
.add()?
.ret()?;
Ok(())
})
})
.build(assembly)?;
let _division_method = MethodBuilder::new("DivideAndRemainder")
.public()
.static_method()
.parameter("dividend", TypeSignature::I4)
.parameter("divisor", TypeSignature::I4)
.returns(TypeSignature::I4)
.implementation(|body| {
body.implementation(|asm| {
asm.ldarg_0()?
.ldarg_1()?
.div()?
.ldarg_0()?
.ldarg_1()?
.rem()?
.add()?
.ret()?;
Ok(())
})
})
.build(assembly)?;
Ok(())
};
let results = run_complete_test_with_reflection(
&runner,
compilation::templates::HELLO_WORLD,
modify_assembly,
|_assembly_path| {
vec![
MethodTest::new("ComplexMath")
.arg_int(5)
.arg_int(3)
.expect_int(19)
.describe("ComplexMath(5, 3) = 19"),
MethodTest::new("ComplexMath")
.arg_int(10)
.arg_int(4)
.expect_int(52)
.describe("ComplexMath(10, 4) = 52"),
MethodTest::new("DivideAndRemainder")
.arg_int(17)
.arg_int(5)
.expect_int(5)
.describe("DivideAndRemainder(17, 5) = 5"),
MethodTest::new("DivideAndRemainder")
.arg_int(20)
.arg_int(4)
.expect_int(5)
.describe("DivideAndRemainder(20, 4) = 5"),
]
},
)?;
for result in &results {
assert!(
result.is_fully_successful(),
"Mathematical operations test failed for {} architecture: {:?}",
result.architecture.name,
result.errors
);
}
Ok(())
}
#[test]
fn test_mono_local_variables_and_stack_operations() -> Result<()> {
let runner = TestRunner::new()?;
let modify_assembly = |assembly: &mut CilAssembly| -> Result<()> {
let _local_vars_method = MethodBuilder::new("TestLocalVariables")
.public()
.static_method()
.parameter("input", TypeSignature::I4)
.returns(TypeSignature::I4)
.implementation(|body| {
body.local("temp1", TypeSignature::I4)
.local("temp2", TypeSignature::I4)
.implementation(|asm| {
asm.ldarg_0()?
.ldc_i4_2()?
.mul()?
.stloc_0()?
.ldloc_0()?
.ldc_i4_5()?
.add()?
.stloc_1()?
.ldloc_1()?
.ldloc_0()?
.sub()?
.ret()?;
Ok(())
})
})
.build(assembly)?;
let _stack_ops_method = MethodBuilder::new("StackOperations")
.public()
.static_method()
.parameter("a", TypeSignature::I4)
.parameter("b", TypeSignature::I4)
.returns(TypeSignature::I4)
.implementation(|body| {
body.implementation(|asm| {
asm.ldarg_0()?.dup()?.ldarg_1()?.add()?.add()?.ret()?;
Ok(())
})
})
.build(assembly)?;
Ok(())
};
let results = run_complete_test_with_reflection(
&runner,
compilation::templates::HELLO_WORLD,
modify_assembly,
|_assembly_path| {
vec![
MethodTest::new("TestLocalVariables")
.arg_int(10)
.expect_int(5)
.describe("TestLocalVariables(10) = 5"),
MethodTest::new("TestLocalVariables")
.arg_int(7)
.expect_int(5)
.describe("TestLocalVariables(7) = 5"),
MethodTest::new("StackOperations")
.arg_int(3)
.arg_int(4)
.expect_int(10)
.describe("StackOperations(3, 4) = 10"),
MethodTest::new("StackOperations")
.arg_int(5)
.arg_int(7)
.expect_int(17)
.describe("StackOperations(5, 7) = 17"),
]
},
)?;
for result in &results {
assert!(
result.is_fully_successful(),
"Local variables test failed for {} architecture: {:?}",
result.architecture.name,
result.errors
);
}
Ok(())
}
#[test]
fn test_mono_multiple_method_cross_references() -> Result<()> {
let runner = TestRunner::new()?;
let modify_assembly = |assembly: &mut CilAssembly| -> Result<()> {
let double_method = MethodBuilder::new("DoubleNumber")
.public()
.static_method()
.parameter("value", TypeSignature::I4)
.returns(TypeSignature::I4)
.implementation(|body| {
body.implementation(|asm| {
asm.ldarg_0()?.ldc_i4_2()?.mul()?.ret()?;
Ok(())
})
})
.build(assembly)?;
let add_ten_method = MethodBuilder::new("AddTen")
.public()
.static_method()
.parameter("value", TypeSignature::I4)
.returns(TypeSignature::I4)
.implementation(|body| {
body.implementation(|asm| {
asm.ldarg_0()?.ldc_i4_s(10)?.add()?.ret()?;
Ok(())
})
})
.build(assembly)?;
let double_token = double_method.placeholder_token().unwrap();
let add_ten_token = add_ten_method.placeholder_token().unwrap();
let _main_method = MethodBuilder::new("ProcessNumber")
.public()
.static_method()
.parameter("input", TypeSignature::I4)
.returns(TypeSignature::I4)
.implementation(move |body| {
body.implementation(move |asm| {
asm.ldarg_0()?
.call(double_token)?
.call(add_ten_token)?
.ret()?;
Ok(())
})
})
.build(assembly)?;
let _factorial_method = MethodBuilder::new("Factorial")
.public()
.static_method()
.parameter("n", TypeSignature::I4)
.returns(TypeSignature::I4)
.implementation(|body| {
body.local("result", TypeSignature::I4)
.local("counter", TypeSignature::I4)
.implementation(|asm| {
asm.ldc_i4_1()?
.stloc_0()?
.ldc_i4_1()?
.stloc_1()?
.label("loop_start")?
.ldloc_1()?
.ldarg_0()?
.bgt_s("loop_end")?
.ldloc_0()?
.ldloc_1()?
.mul()?
.stloc_0()?
.ldloc_1()?
.ldc_i4_1()?
.add()?
.stloc_1()?
.br_s("loop_start")?
.label("loop_end")?
.ldloc_0()?
.ret()?;
Ok(())
})
})
.build(assembly)?;
Ok(())
};
let results = run_complete_test_with_reflection(
&runner,
compilation::templates::HELLO_WORLD,
modify_assembly,
|_assembly_path| {
vec![
MethodTest::new("DoubleNumber")
.arg_int(5)
.expect_int(10)
.describe("DoubleNumber(5) = 10"),
MethodTest::new("AddTen")
.arg_int(7)
.expect_int(17)
.describe("AddTen(7) = 17"),
MethodTest::new("ProcessNumber")
.arg_int(5)
.expect_int(20)
.describe("ProcessNumber(5) = 20"),
MethodTest::new("ProcessNumber")
.arg_int(15)
.expect_int(40)
.describe("ProcessNumber(15) = 40"),
MethodTest::new("Factorial")
.arg_int(5)
.expect_int(120)
.describe("Factorial(5) = 120"),
MethodTest::new("Factorial")
.arg_int(6)
.expect_int(720)
.describe("Factorial(6) = 720"),
MethodTest::new("Factorial")
.arg_int(1)
.expect_int(1)
.describe("Factorial(1) = 1"),
]
},
)?;
for result in &results {
assert!(
result.is_fully_successful(),
"Cross-reference test failed for {} architecture: {:?}",
result.architecture.name,
result.errors
);
}
let expected_arch_count = runner.architectures().len();
assert_eq!(
results.len(),
expected_arch_count,
"Expected to test {} architectures",
expected_arch_count
);
Ok(())
}
#[test]
fn test_mono_blob_heap_and_complex_signatures() -> Result<()> {
let runner = TestRunner::new()?;
let results = run_complete_test_with_reflection(
&runner,
compilation::templates::HELLO_WORLD,
|assembly: &mut CilAssembly| -> Result<()> {
let _complex_method = MethodBuilder::new("ComplexMethod")
.public()
.static_method()
.parameter("intParam", TypeSignature::I4)
.parameter("stringParam", TypeSignature::String)
.parameter("boolParam", TypeSignature::Boolean)
.returns(TypeSignature::String)
.implementation(|body| {
body.local("result", TypeSignature::String)
.implementation(|asm| {
asm.ldstr(Token::new(0x70000001))?
.stloc_0()?
.ldloc_0()?
.ret()?;
Ok(())
})
})
.build(assembly)?;
let result_string_ref =
assembly.userstring_add("ComplexMethod executed successfully")?;
let _result_string_token =
Token::new(0x70000000 | result_string_ref.placeholder());
let _param_test_method = MethodBuilder::new("TestParameters")
.public()
.static_method()
.parameter("a", TypeSignature::I4)
.parameter("b", TypeSignature::I4)
.parameter("c", TypeSignature::I4)
.returns(TypeSignature::I4)
.implementation(|body| {
body.implementation(|asm| {
asm.ldarg_0()?.ldarg_1()?.add()?.ldarg_2()?.mul()?.ret()?;
Ok(())
})
})
.build(assembly)?;
let _bool_method = MethodBuilder::new("BooleanLogic")
.public()
.static_method()
.parameter("flag1", TypeSignature::Boolean)
.parameter("flag2", TypeSignature::Boolean)
.returns(TypeSignature::Boolean)
.implementation(|body| {
body.implementation(|asm| {
asm.ldarg_0()?.ldarg_1()?.and()?.ret()?;
Ok(())
})
})
.build(assembly)?;
Ok(())
},
|_assembly_path| {
vec![
MethodTest::new("TestParameters")
.arg_int(2)
.arg_int(3)
.arg_int(4)
.expect_int(20)
.describe("TestParameters(2, 3, 4) = 20"),
MethodTest::new("TestParameters")
.arg_int(10)
.arg_int(5)
.arg_int(2)
.expect_int(30)
.describe("TestParameters(10, 5, 2) = 30"),
MethodTest::new("BooleanLogic")
.arg_bool(true)
.arg_bool(false)
.expect_bool(false)
.describe("BooleanLogic(true, false) = false"),
MethodTest::new("BooleanLogic")
.arg_bool(true)
.arg_bool(true)
.expect_bool(true)
.describe("BooleanLogic(true, true) = true"),
MethodTest::new("BooleanLogic")
.arg_bool(false)
.arg_bool(false)
.expect_bool(false)
.describe("BooleanLogic(false, false) = false"),
]
},
)?;
for result in &results {
assert!(
result.is_fully_successful(),
"Blob heap/Complex signature test failed for {} architecture: {:?}",
result.architecture.name,
result.errors
);
}
let expected_arch_count = runner.architectures().len();
assert_eq!(
results.len(),
expected_arch_count,
"Expected to test {} architectures",
expected_arch_count
);
Ok(())
}
#[test]
fn test_mono_reflection_detects_wrong_results() -> Result<()> {
let runner = TestRunner::new()?;
let results = run_complete_test_with_reflection(
&runner,
compilation::templates::HELLO_WORLD,
|assembly: &mut CilAssembly| -> Result<()> {
let _add_method = MethodBuilder::new("Add")
.public()
.static_method()
.parameter("a", TypeSignature::I4)
.parameter("b", TypeSignature::I4)
.returns(TypeSignature::I4)
.implementation(|body| {
body.implementation(|asm| {
asm.ldarg_0()?.ldarg_1()?.add()?.ret()?;
Ok(())
})
})
.build(assembly)?;
Ok(())
},
|_assembly_path| {
vec![
MethodTest::new("Add")
.arg_int(2)
.arg_int(3)
.expect_int(999)
.describe("Add(2, 3) should NOT equal 999"),
]
},
)?;
for result in &results {
assert!(
!result.reflection_success,
"Reflection should have FAILED for {} because we expected wrong result",
result.architecture.name
);
let has_mismatch_error = result
.errors
.iter()
.any(|e| e.contains("Expected") || e.contains("999") || e.contains("5"));
assert!(
has_mismatch_error,
"Error should mention result mismatch for {}: {:?}",
result.architecture.name, result.errors
);
}
Ok(())
}
}
}