use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{MethodDefRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
pub struct MethodDefBuilder {
name: Option<String>,
flags: Option<u32>,
impl_flags: Option<u32>,
signature: Option<Vec<u8>>,
rva: Option<u32>,
param_list: Option<u32>,
}
impl MethodDefBuilder {
#[must_use]
pub fn new() -> Self {
Self {
name: None,
flags: None,
impl_flags: None,
signature: None,
rva: None,
param_list: None,
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn flags(mut self, flags: u32) -> Self {
self.flags = Some(flags);
self
}
#[must_use]
pub fn impl_flags(mut self, impl_flags: u32) -> Self {
self.impl_flags = Some(impl_flags);
self
}
#[must_use]
pub fn signature(mut self, signature: &[u8]) -> Self {
self.signature = Some(signature.to_vec());
self
}
#[must_use]
pub fn rva(mut self, rva: u32) -> Self {
self.rva = Some(rva);
self
}
#[must_use]
pub fn param_list(mut self, param_list: u32) -> Self {
self.param_list = Some(param_list);
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let name = self
.name
.ok_or_else(|| Error::ModificationInvalid("Method name is required".to_string()))?;
let flags = self
.flags
.ok_or_else(|| Error::ModificationInvalid("Method flags are required".to_string()))?;
let impl_flags = self.impl_flags.ok_or_else(|| {
Error::ModificationInvalid("Method implementation flags are required".to_string())
})?;
let signature = self.signature.ok_or_else(|| {
Error::ModificationInvalid("Method signature is required".to_string())
})?;
let rva = self.rva.unwrap_or(0); let param_list = self.param_list.unwrap_or(0); let name_index = assembly.string_get_or_add(&name)?.placeholder();
let signature_index = assembly.blob_add(&signature)?.placeholder();
let rid = assembly.next_rid(TableId::MethodDef)?;
let placeholder_token = Token::from_parts(TableId::MethodDef, rid);
let method_raw = MethodDefRaw {
rid,
token: placeholder_token,
offset: 0, rva,
impl_flags,
flags,
name: name_index,
signature: signature_index,
param_list,
};
assembly.table_row_add(TableId::MethodDef, TableDataOwned::MethodDef(method_raw))
}
}
impl Default for MethodDefBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::{ChangeRefKind, CilAssembly},
metadata::method::{MethodAccessFlags, MethodImplCodeType, MethodModifiers},
};
use std::path::PathBuf;
#[test]
fn test_method_builder_basic() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let existing_method_count = assembly.original_table_row_count(TableId::MethodDef);
let _expected_rid = existing_method_count + 1;
let void_signature = &[0x00, 0x00, 0x01];
let ref_ = MethodDefBuilder::new()
.name("TestMethod")
.flags(MethodAccessFlags::PUBLIC.bits() | MethodModifiers::HIDE_BY_SIG.bits())
.impl_flags(MethodImplCodeType::IL.bits())
.signature(void_signature)
.rva(0) .build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::MethodDef));
}
}
#[test]
fn test_method_builder_static_constructor() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let static_ctor_sig = &[0x00, 0x00, 0x01];
let ref_ = MethodDefBuilder::new()
.name(".cctor")
.flags(
MethodAccessFlags::PRIVATE.bits()
| MethodModifiers::STATIC.bits()
| MethodModifiers::SPECIAL_NAME.bits()
| MethodModifiers::RTSPECIAL_NAME.bits(),
)
.impl_flags(MethodImplCodeType::IL.bits())
.signature(static_ctor_sig)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::MethodDef));
}
}
#[test]
fn test_method_builder_instance_constructor() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let instance_ctor_sig = &[0x20, 0x00, 0x01];
let ref_ = MethodDefBuilder::new()
.name(".ctor")
.flags(
MethodAccessFlags::PUBLIC.bits()
| MethodModifiers::SPECIAL_NAME.bits()
| MethodModifiers::RTSPECIAL_NAME.bits(),
)
.impl_flags(MethodImplCodeType::IL.bits())
.signature(instance_ctor_sig)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::MethodDef));
}
}
#[test]
fn test_method_builder_with_return_value() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let method_with_return_sig = &[0x00, 0x00, 0x08];
let ref_ = MethodDefBuilder::new()
.name("GetValue")
.flags(
MethodAccessFlags::PUBLIC.bits()
| MethodModifiers::STATIC.bits()
| MethodModifiers::HIDE_BY_SIG.bits(),
)
.impl_flags(MethodImplCodeType::IL.bits())
.signature(method_with_return_sig)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::MethodDef));
}
}
#[test]
fn test_method_builder_missing_name() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let result = MethodDefBuilder::new()
.flags(MethodAccessFlags::PUBLIC.bits())
.impl_flags(MethodImplCodeType::IL.bits())
.signature(&[0x00, 0x00, 0x01])
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_method_builder_missing_flags() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let result = MethodDefBuilder::new()
.name("TestMethod")
.impl_flags(MethodImplCodeType::IL.bits())
.signature(&[0x00, 0x00, 0x01])
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_method_builder_missing_impl_flags() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let result = MethodDefBuilder::new()
.name("TestMethod")
.flags(MethodAccessFlags::PUBLIC.bits())
.signature(&[0x00, 0x00, 0x01])
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_method_builder_missing_signature() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let result = MethodDefBuilder::new()
.name("TestMethod")
.flags(MethodAccessFlags::PUBLIC.bits())
.impl_flags(MethodImplCodeType::IL.bits())
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_method_builder_multiple_methods() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let void_signature = &[0x00, 0x00, 0x01];
let ref1 = MethodDefBuilder::new()
.name("Method1")
.flags(MethodAccessFlags::PRIVATE.bits())
.impl_flags(MethodImplCodeType::IL.bits())
.signature(void_signature)
.build(&mut assembly)
.unwrap();
let ref2 = MethodDefBuilder::new()
.name("Method2")
.flags(MethodAccessFlags::PUBLIC.bits())
.impl_flags(MethodImplCodeType::IL.bits())
.signature(void_signature)
.build(&mut assembly)
.unwrap();
assert!(!std::sync::Arc::ptr_eq(&ref1, &ref2));
assert_eq!(ref1.kind(), ChangeRefKind::TableRow(TableId::MethodDef));
assert_eq!(ref2.kind(), ChangeRefKind::TableRow(TableId::MethodDef));
}
}
#[test]
fn test_method_builder_default_values() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(mut assembly) = CilAssembly::from_path(&path) {
let ref_ = MethodDefBuilder::new()
.name("AbstractMethod")
.flags(MethodAccessFlags::PUBLIC.bits() | MethodModifiers::ABSTRACT.bits())
.impl_flags(MethodImplCodeType::IL.bits())
.signature(&[0x00, 0x00, 0x01])
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::MethodDef));
}
}
}